본문 바로가기

Dreamhack/웹해킹

[LEVEL 1] csrf-2

웹 서비스에 접속하면 상단에 “CSRF-2” 제목과 Home 메뉴가 보이며, 그 아래에 vuln(csrf) page, flag, login 링크가 순서대로 배치되어 있습니다. 현재 인증이 되지 않은 상태이므로 본문에는 굵은 글씨로 “please login” 메시지가 표시됩니다.

 

로그인 페이지로 이동한 후, 가장 흔히 사용되는 기본 계정 조합인 guest : guest를 입력하여 로그인을 시도하였습니다.

 

vuln(csrf) page에 접속해보면, script 태그가 필터링되어 * 문자로 치환되는 것을 확인할 수 있었습니다.

 

 

자세히 살펴보기 위해서 코드를 살펴보겠습니다.

 

app.py

#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import urllib
import os

app = Flask(__name__)
app.secret_key = os.urandom(32)

try:
    FLAG = open("./flag.txt", "r").read()
except:
    FLAG = "[**FLAG**]"

users = {
    'guest': 'guest',
    'admin': FLAG
}

session_storage = {}

def read_url(url, cookie={"name": "name", "value": "value"}):
    cookie.update({"domain": "127.0.0.1"})
    try:
        service = Service(executable_path="/chromedriver")
        options = webdriver.ChromeOptions()
        for _ in [
            "headless",
            "window-size=1920x1080",
            "disable-gpu",
            "no-sandbox",
            "disable-dev-shm-usage",
        ]:
            options.add_argument(_)
        driver = webdriver.Chrome(service=service, options=options)
        driver.implicitly_wait(3)
        driver.set_page_load_timeout(3)
        driver.get("http://127.0.0.1:8000/")
        driver.add_cookie(cookie)
        driver.get(url)
    except Exception as e:
        driver.quit()
        print(str(e))
        # return str(e)
        return False
    driver.quit()
    return True


def check_csrf(param, cookie={"name": "name", "value": "value"}):
    url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
    return read_url(url, cookie)


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


@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


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


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


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

app.run(host="0.0.0.0", port=8000)

 

  • /vuln
    • 사용자 입력을 받아 출력하는 과정에서 script, frame, on 문자열만 *로 치환합니다.
  • /flag
    • POST 요청을 받으면 admin 세션을 가진 봇을 생성합니다.
    • 봇은 /vuln?param=... 으로 이동하며, 그 안의 스크립트를 실행하게 됩니다.
  • /change_password
    • 쿼리 파라미터 pw를 받아 현재 로그인된 사용자(세션)의 비밀번호를 변경합니다.

코드 분석 결과, /flag 페이지는 admin 권한의 봇을 통해 /vuln 페이지를 방문하도록 동작하며, 이때 삽입된 스크립트를 실행시킬 수 있다는 것을 확인했습니다. 따라서 이 스크립트를 이용해 /change_password 경로를 호출하면, 관리자 계정의 비밀번호를 임의로 변경할 수 있겠다는 생각을 했습니다. 

 

이것을 실제로 진행해보겠습니다.

 

이제 /change_password 요청을 강제로 발생시키기 위해, 아래와 같은 CSRF 페이로드를 작성해보았습니다.

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

 

 

 

  • 악성 페이로드 실행: 사용자의 웹 브라우저가 공격자가 삽입한 <img src="/change_password?pw=1234"> 태그를 렌더링합니다.
  • 자동 GET 요청 트리거: 브라우저는 <img> 태그의 src 속성 값을 로드하기 위해 /change_password?pw=1234로 자동으로 HTTP GET 요청을 보냅니다.
  • 서버 측 처리: 서버는 전송된 쿠키를 통해 요청이 admin 세션에서 왔다는 것을 확인합니다. 이후, URL의 pw 매개변수 값을 바탕으로 admin 계정의 비밀번호를 1234로 변경합니다.

 

 

 

이 과정을 통해 admin 비밀번호를 1234로 변경할 수 있으며, 이를 이용해 FLAG를 획득할 수 있다

'Dreamhack > 웹해킹' 카테고리의 다른 글

[LEVEL 1] [wargame.kr] strcmp  (1) 2025.09.08
[LEVEL 1] [wargame.kr] fly me to the moon  (0) 2025.09.08
[LEVEL 1] xss-2  (0) 2025.09.07
[LEVEL 1] mongoboard  (0) 2025.09.06
[LEVEL 1] php-1  (0) 2025.09.06