외로운 Nova의 작업실

dreamhack 웹해킹 - 5(CSRF-2) 본문

Web Penetesting/Web Vulnerability

dreamhack 웹해킹 - 5(CSRF-2)

Nova_ 2022. 11. 29. 22:17

안녕하세요. 이번 시간에는 STAGE 5 혼자실습문제 CSRF-2를 풀어보겠습니다. 답은 가려져있습니다.

 

<서버 코드>

#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
from selenium import webdriver
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:
        options = webdriver.ChromeOptions()
        for _ in [
            "headless",
            "window-size=1920x1080",
            "disable-gpu",
            "no-sandbox",
            "disable-dev-shm-usage",
        ]:
            options.add_argument(_)
        driver = webdriver.Chrome("/chromedriver", 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)

전체적으로 코드는 이전 포스팅인 XSS-2와 비슷합니다. 하지만 /change_password 데코레이터부분과 FLAG관련 코드, xss_filter부분만 확인해보겠습니다.

 

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

/change_password 경로에 접속하면 서버는 클라이언트로부터 url 매개변수로 pw(새로운 비밀번호)값을 받고 쿠키로 session_id를 받습니다. session_id로 session_storage 딕셔너리 자료형에서 username을 뺴오고 이 username에 맞는 users의 패스워드를 매개변수로받은 pw로 바꾸는 것을 확인할 수 있습니다.

 

<FLAG 관련 코드>

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

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

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

FLAG는 서버의 flag.txt 파일에서 읽어오며 admin의 패스워드로 지정되는 것을 알 수 있습니다. 또한 admin으로 로그인하게되면 / 경로에서 FLAG를 확인할 수 있는 것을 알 수 있습니다. 

 

<xss_filter함수>

@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

xss_filter는 param에서 frame, script, on 단어를 *로 변경시켜줘서 xss를 막습니다. 즉, 이를 우회할 수 있는 xss 취약점 스크립트를 작성해야한다는 것을 알 수 있습니다.

 

<CSRF 공격 설계>

하나의 admin은 flag 페이지에서 param으로 준 것을 vuln 페이지의 매개변수로 실행시킵니다. vuln 페이지는 param 매개변수로 받은 코드를 html 코드로 반환하기때문에 xss 취약점이 있습니다. 하지만 위와 같이 filter 함수가 있어서 이를 우회해야합니다. 따라서 저는 link 태그를 가지고 취약점을 파고들겠습니다. link 태그로 admin이 /change_password에 접속해서 비밀번호를 0000으로 바꾸게 하여 CSRF 공격을 하고 이후 admin, 0000을 이용해 admin으로 로그인하고 / 경로로가서 flag를 확인해보겠습니다.

 

<서버 접속 및 실습>

서버를 접속하고 /flag 경로로 들어가줍니다.

준비한 CSRF 공격 스크립트를 넣어주고 제출합니다.

<link rel="stylesheet" href="/change_password?pw=0000">

그러면 admin이 접속해서 패스워드를 0000으로 변경했을테니 그에 맞춰서 로그인해줍니다.

로그인에 성공했다면 바로 / 경로로 넘어가 아래페이지가 뜹니다.

DH로 시작하는 FLAG를 문제 페이지에 넣어주고 문제를 끝냅니다!

 

Comments