[드림핵] 워게임 csrf-2 풀이

SCY·2023년 2월 7일
0

dreamhack

목록 보기
12/12

문제

https://dreamhack.io/wargame/challenges/269/

풀이

🌱 코드 분석

✔️ 메인 페이지

@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"}')


메인 페이지의 코드를 분석해보면 sessionid를 받고 session_storage에 넣어준 후 admin 계정이면 flag를 출력한다. 쿠키가 존재하지 않으면 'please login'을 출력한다. admin 계정으로 로그인 하는 것이 우리의 목표가 될 것이다.

✔️ vuln 앤드포인트

@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


param이라는 파라미터를 소문자로 변경한 후 저장한다. xss 공격을 피하기 위해 필터링해준 후 화면에 출력한다.

✔️ 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'
        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>'


GET 메서드일 때는 위와 같이 출력된다.
POST 메서드일 때는 입력받은 문자열을 param에 저장하고, admin의 sessionid를 생성한다. admin 계정으로 check_csrf()read_url()를 거쳐 /vuln에 접속해준다.

✔️ login 앤드포인트

@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를 입력받고 일치하는 계정이 존재하면, sessionid를 생성하고 session_storage에 저장한 후 메인 페이지로 이동한다.

✔️ change_password 앤드포인트

메인 페이지에 존재하지 않는 /change_password 페이지가 있다.

@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'

pw라는 파라미터로 변경할 비밀번호를 입력받고 해당 sessionid를 갖는 계정의 비밀번호를 변경해준다.

🌱 해결 과정

/change_password에서 admin의 비밀번호를 바꾸어 로그인하면 되겠구나 생각했다. 이 페이지에 접속하기 위해 /flag 페이지를 활용했다. /flag에서는 param을 입력하는 순간 admin의 sessionid를 생성해주기 때문에 /flag에서 /change_password에 접속하면 admin의 비밀번호를 변경할 수 있게 된다.
따라서 /flag 페이지에 접속해 <img src="/change_password?pw=admin"/> 를 입력했고, /login 페이지에서 바꾸어준 비밀번호로 로그인하여 flag를 획득했다.

🌱 정답

DH{c57d0dc12bb9ff023faf9a0e2b49e470a77271ef}

profile
성장 중독 | 서버, 데이터, 정보 보안을 공부합니다.

0개의 댓글