본 문제는 Dreamhack을 통해서 풀어 보실 수 있습니다.

해답을 이해하며 생각을 해보면서 풀이 해보시길 바랍니다.

문제 내용

문제는 dreamhack.io를 들어가시면 확인할 수 있습니다.

접속시 please login이 나오고 로그인을 할 수 있는 링크, vuln page, flag 총 3개의 링크가 있는 것을 알 수 있다.

문제 풀이

vuln page

  • vuln(xss)page : 해당 URL을 보면 http://host3.dreamhack.games:20599/vuln?param=%3Cscript%3Ealert(1)%3C/script%3E로 되어 있다. 하지만, 어떠한 이유인지 스크립트가 작동하지 않는다. 그리고 <*>alert(1) 이라는 텍스트가 적혀 있는 것을 알 수 있다.
@app.route("/vuln")
def vuln():
    param = request.args.get("param", "").lower()
    xss_filter = ["frame", "script", "on"]
    for _ in xss_filter:
        param = param.replace(_, "*")
    return param

parameter로 받은 값은 frame, script, on필터링으로 인해 작동하지 않는 것을 알 수 있다.

login page

  • login : 해당 페이지로 이동하게 되면 위와 같이 ID와 PW를 입력할 form과 Login Button이 존재한다.
users = {
    'guest': 'guest',
    'admin': FLAG
}

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    elif request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        try:
            pw = users[username]
        except:
            return '<script>alert("not found user");history.go(-1);</script>'
        if pw == password:
            resp = make_response(redirect(url_for('index')) )
            session_id = os.urandom(8).hex()
            session_storage[session_id] = username
            resp.set_cookie('sessionid', session_id)
            return resp 
        return '<script>alert("wrong password");history.go(-1);</script>'

POST 메소드로 usernamepassword를 받고 users에서 username에 해당하는 value pw와 입력한 password가 같으면 세션을 부여하는 것으로 보인다.

지금 저장되어 있는 guest, guest를 통해서 접속해본다.

Hello guest, you are not an admin 이라는 문구가 나오고, admin 접속이 필요한 것으로 보인다.

root page (“/”)

@app.route("/")
def index():
    session_id = request.cookies.get('sessionid', None)
    try:
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')

    return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not an admin"}')

return render_template을 보면 만약 로그인한 username이 admin이라면 Hello guest가 아닌 FLAG를 출력해주는 것으로 보인다.

change password page

admin 접속을 해야 할 것으로 보이지만 현재 admin의 value는 FLAG로 되어 있다. 제공받은 app.py 파일을 보면 아래와 같이 숨겨진 페이지가 있다.

@app.route("/change_password")
def change_password():
    pw = request.args.get("pw", "")
    session_id = request.cookies.get('sessionid', None)
    try:
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')

    users[username] = pw
    return 'Done'

parameter인 pw값을 pw 변수에 저장하고 세션 ID를 username에 저장하게 된다.

입력받은 pw를 users의 key를 세션 ID로 하고 해당 value를 입력받은 pw로 변경하게 되는 것이다. guest 접속 상태에서 http://host3.dreamhack.games:20599/change_password?pw=1234 하게 되면 ID는 guest, guest의 패스워드는 1234가 되는 것이다.

그렇다면 현재 세션 ID가 admin이어야 할 텐데 아래 /flag쪽을 보면 답이 나온다.

@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "GET":
        return render_template("flag.html")
    elif request.method == "POST":
        param = request.form.get("param", "")
        session_id = os.urandom(16).hex()
        session_storage[session_id] = 'admin' # <----- here
        if not check_csrf(param, {"name":"sessionid", "value": session_id}):
            return '<script>alert("wrong??");history.go(-1);</script>'

        return '<script>alert("good");history.go(-1);</script>'

/flag통해서 접속을 하게 된다면 세션 ID 부분이 admin 권한으로 진행되는 것을 알 수 있다. 이제 change_password/flag를 통해서 password를 변경하면 된다.

<img src ="/change_password?pw=admin">

해당 페이로드로 제출하고 admin, admin으로 다시 로그인하면 아래와 같이 FLAG가 출력되는 것을 알 수 있다.