Dreamhack - CSRF Advanced

·2025년 7월 31일

Dreamhack-Writeups

목록 보기
30/52

CSRF Advanced

문제 링크

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

문제 설명

CSRF 심화 문제입니다.
admin 계정으로 로그인에 성공하면 플래그를 획득할 수 있습니다.

풀이과정

  1. app.py를 확인해 보았습니다.

        @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("user not found");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
                 token_storage[session_id] = md5((username + request.remote_addr).encode()).hexdigest()
                 resp.set_cookie('sessionid', session_id)
                 return resp 
             return '<script>alert("wrong password");history.go(-1);</script>'

    /login 라우트를 보면, 로그인 성공시 md5((username + request.remote_addr).encode()).hexdigest() 방식으로 CSRF 토큰을 부여함을 확인할 수 있습니다. 토큰 부여 형식이 정해져 있기 때문에 보안상 취약점이 있음을 알 수 있습니다.

  2. admin 계정으로 로그인해야 하지만, 해당 계정의 비밀번호는 플래그 값으로 설정되어 있어 알 수 없습니다. 따라서 사이트에 구현된 기능을 활용해 우회적으로 로그인을 시도해야 함을 유추할 수 있습니다. 마침 비밀번호를 변경할 수 있는 기능이 존재하므로, /change_password 라우트를 확인해 보았습니다.

        @app.route("/change_password")
     def change_password():
         session_id = request.cookies.get('sessionid', None)
         try:
             username = session_storage[session_id]
             csrf_token = token_storage[session_id]
         except KeyError:
             return render_template('index.html', text='please login')
         pw = request.args.get("pw", None)
         if pw == None:
             return render_template('change_password.html', csrf_token=csrf_token)
         else:
             if csrf_token != request.args.get("csrftoken", ""):
                 return '<script>alert("wrong csrf token");history.go(-1);</script>'
             users[username] = pw
             return '<script>alert("Done");history.go(-1);</script>'

    해당 라우트는 단순히 pw 파라미터만으로 비밀번호를 변경할 수 있는 것이 아니라, 내부적으로 CSRF 토큰 검증을 수행합니다. 즉, admin의 CSFR 토큰 값을 알고 있다면, 해당 계정의 비밀번호 변경이 가능합니다.

  3. md5((username + request.remote_addr).encode()).hexdigest() 을 이용해서 admin의 CSRF 토큰을 구합니다.

    • 문자열 username + request.remote_addr 을 그대로 바이트로 해석해서 MD5 해시를 계산한 결과가 CSRF 토큰입니다. ( MD5 해시는 16진수 문자열 형태로 표시됩니다.)
    • usernameadmin, request.remote_addr 은 IP주소를 뜻합니다. 이 문제는 로컬 호스트이므로 127.0.0.1 입니다.
    • MD5 해시를 계산하기 위한 파이썬 코드를 작성합니다.
       from hashlib import md5
       token = md5(b"admin127.0.0.1").hexdigest()
       print(token)
    • admin으로 로그인 했을때 제공되는 토큰값인 7505b9c72ab4aa94b1a4ed7b207b67fb 가 출력됩니다.
  4. 알아낸 토큰값을 활용하여 /flag에서 XSS 공격을 시도합니다.

    <img src="/change_password?pw=1234&csrftoken=7505b9c72ab4aa94b1a4ed7b207b67fb">
    • /vuln 라우트에서 "frame", "script", "on" 이 필터링 되어있음을 확인할 수 있습니다. 이를 우회하기 위해 img 태그를 활용합니다.
    • /change_password?pw=1234 를 통해 admin의 비밀번호를 1234로 변경합니다.
    • admin의 토큰인 csrftoken=7505b9c72ab4aa94b1a4ed7b207b67fb 을 제시하여 admin의 비밀번호가 정상적으로 바뀔 수 있게 합니다.
  5. usernameadmin, password1234를 입력하여 정상적으로 로그인에 성공하였고, 최종적으로 플래그를 획득할 수 있었습니다.


배운점

  • CSRF 토큰을 부여하는 방식이 고정되어 있다면 보안상 취약함을 알게 되었습니다.
  • 따라서 CSRF 토큰은 반드시 예상이 불가능한 난수값이여야 하며, 사용자마다 다르게 부여되어야 함을 배웠습니다.
  • MD5 해시가 16진수 문자열 형태임을 알게 되었고, 특정한 문자열을 MD5 해시처리하는 코드를 작성해볼 수 있었습니다.
  • 필터링 된 키워드를 우회하기 위해 다양한 태그를 활용할 수 있음을 배웠습니다.

Summary (English)

  • The challenge revolves around exploiting a predictable CSRF token mechanism to change the admin account password.
  • Upon login, the server generates a CSRF token using md5(username + IP), which can be predicted since both values are known (admin127.0.0.1).
  • The /change_password route requires both a new password and a correct CSRF token tied to the session.
  • By computing md5(b"admin127.0.0.1"), the attacker gets the admin's token: 7505b9c72ab4aa94b1a4ed7b207b67fb.
  • An XSS payload using an <img> tag was used to trigger a GET request to /change_password, changing the admin's password without using forbidden keywords (on, script, etc.).
  • After changing the admin's password to a known value (1234), the attacker successfully logs in and retrieves the flag.
profile
CTF 풀이 및 실습 중심 학습을 기록합니다.

0개의 댓글