본문 바로가기
정보보안/Web Hacking

드림핵 문제풀이 - CSRF-2 // CSRF, Web hacking, 세션

by 우동이 2022. 4. 6.
300x250

 

문제 분류


난이도 : 중

 


 

작성일자 : 2022-04-04

기본 숙지 개념 : flask, Web, CSRF 취약점, 웹 인증방식(쿠키,세션)

작업환경 :  VIsual Studio Code, Web(Chrome)

사용언어 : Python_flask, HTML, JavaScript

작업도구 : -

추천 자료 : https://dreamhack.io/lecture/roadmaps/1

https://pointy-tarantula-c3b.notion.site/CSRF-Cross-Site-Request-Forgery-93d36fbda7c54f50a26022a61ea28330

 

 


1. 사전탐색&정찰 (reconnaissance)

해당 문제 자체가 CSRF 취약점에 관련된 문제이기 때문에

우리는 이 문제를 CSRF 취약점을 통해 풀이하면 된다.

먼저 사이트에 접속해보면

 

/index

"Please login" 로그인을 하라는 문구와 세개의 페이지가 보인다

 

/vuln(csrf) 

/vuln

vuln 페이지는 말 그대로 취약점을 알려준다.

url에 삽입된 스크립트 코드 중 <,>,*,alert(1) 이 코드들이 출력되는걸 알 수 있는데

이는 <script> 코드는 사용 불가능 하지만

<,> 꺽쇠를 이용한 코드 사용이 가능하다는 뜻이다.

-> 즉 html 태그를 이용한 csrf 공격을 해야한다.

 /flag 

/flag

/flag 페이지는 플래그를 얻기위한 코드를 삽입하는 곳으로

csrf 취약점을 발생시킬수 있는 코드를 찾아 넣으면 된다.(플래그 코드가 나올 수 있는 코드)

 

 /login

/login

csrf-1 문제와 다르게 이번엔 계정을 통한 로그인이 필요한듯하다.

아마 admin 계정을 탈취해야하지 않을까 싶다.


 

2. 스캐닝 및 취약점 분석(Scanning and Enumeration)

먼저 제공된 코드를 조금씩 살펴보자

#전체코드
#!/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)

전체 코드들 중에서

내가 중요하다고 생각해서 살펴본 코드들은

#15번째 줄
#계정
users = {
    'guest': 'guest',
    'admin': FLAG       
}

guest와 admin 계정 두개가 존재한다는 코드

#108번째 줄
#패스워드 교체 url

@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

패스워드를 수정하는 change_password 페이지가 있음을 알 수 있는데

해당 페이지에 pw를 변경하는 기능이 있기 때문에

flag를 통해 change_password 로 암호를 변경하는 파라미터를 보내면 된다.

해당 문제역시 csrf-1 문제와 동일하게 127.0.0.1(localhost)에서만 동작하기 때문에

flag를 통해 값을 전달해야한다.


3. 침투 (Gaining Access)

/login

change_password는 세션 id가 존재해야만 접속할수 있기에 로그인을 먼저 해야한다.

먼저 login 에서 guest(pw:guest) 계정으로 로그인한다.

/index

guest계정임을 알려줌과 동시에 admin이 아니라는 메시지가 출력된다.

 

/flag

이제 flag에 삽입할 코드를 짜보자

 

csrf가 가능한 코드는 아래와 같다.

<img src='url' width=0px height=0px>
<img src="link">
<img src=1 onerror="fetch('/link');">
<link rel="stylesheet" href="link">

아까 말했듯 자바 스크립트 태그는 전부 필터링 되므로 

html 태그만 사용해야 한다.

해당 html 태그들중 <img> 태그를 활용해 공격 코드를 짜보았다.

 

// 변조된 공격코드
<img src="/change_password?pw=admin">


// "/change_password?pw=admin"
//change_password 에 pw를 admin으로 변경한다는 파라미터 전달

 

/flag

해당 공격코드를 /flag 에 삽입한다.

 


4. 권한상승 및 탈취 (Privilege Escalation)

 

change_password로 password 변경 파라미터를 img 태그로 보냈다.

그 후 ID : admin & PW : admin

으로 로그인 하면 플래그가 출력된다.

flag

계정명은 pw와 동일하다.

아무튼 이렇게 csrf 취약점으로 admin 계정 탈취가 되었다.

 

 

 

300x250

댓글