해당 문제의 접속 정보 생성으로 문제를 풀이할 수 있는 페이지에 들어가보니 아래와 같은 페이지를 확인할 수 있었다.
각 카테고리별로 어떠한 내용이 들어가 있는지 알아보기 위해 하나씩 들어가보았다.
우선, vuln(xss) page 부분에는 이미지 파일이 뜨는 페이지로 보이지만 실제 이미지는 제공되어있지 않았다.
메모 부분에는 아래와 같이 'hello' 문구가 출력되고 있었고, 해당 카테고리에 접속할 때마다 hello가 추가되는 것을 확인할 수 있었다.
마지막으로 'flag' 카테고리에는 플래그 값을 얻기 위해 파라미터 값을 입력할 수 있는 공간이 있었다.
이 외에 별도로 더 확인할 수 있는 힌트가 없으므로 함께 제공된 소스코드를 확인해보자.
#!/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():
text = text.replace(f, "")
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)
위 소스코드 중에서 xss_filter(text) 함수 부분을 확인해보면 'script', 'on', 'javascript' 이 3개의 단어가 필터링되고 있는 것을 확인할 수 있다.
다음으로 vuln 부분을 확인해보면 이용자가 전달한 param 파라미터의 값을 xss_filter로 필터링 한 다음에 출력되는 형태로 구성되어 있다. 따라서 필터링을 통해 xss 공격을 막을 것같다고 우추할 수 있다.
마지막으로 memo 부분에 해당하는 코드를 보면 memo 함수에서는 사용자가 전달한 memo 파라미터 값을 render_template 함수를 통해 기록하고 출력하는 코드로 구성되어 있다.
여기서 render_template 함수는 전달된 템플릿 변수를 기록할 때 HTML 엔티티코드로 변환해 저장하기 때문에 XSS 취약점이 발생하지 않다.
그러나 vuln은 이용자가 입력한 값을 페이지에 그대로 출력하기 때문에 XSS 취약점이 발생할 수 있다.
따라서 vuln에 xss_filter(text)를 우회해서 xss공격을 한다면 memo를 통해서 flag를 get할 수 있을 것이다.
앞서 확인한 xss_filter(text) 함수는 'script', 'on', 'javascript'을 필터링하고 있기 때문에 아래 두가지 방식을 사용하여 필터링을 우회할 수 있다.
- script가 필터링 되어있기 때문에 대소문자를 적절히 섞어서 사용하기 (ex. ScRiPt)
- script 중간에 'on'을 넣어 on이 필터링 된 이후에 최종적으로 script만 남을 수 있도록 코드 변경
나는 위 두가지 방법 중에 on을 넣어서 필터링하는 방법을 선택했다.
<scronipt>document['locatio'+'n'].href = "/memo?memo=" + document.cookie;</scronipt>
위와 같이 필터링을 우회할 수 있는 코드를 작성하고 넣어주니
'good' 문구가 출력된 alert 창이 떴고
다시 메모 카테고리로 돌아가서 확인해보니 플래그 값을 발견할 수 있었다!!
'CTF' 카테고리의 다른 글
[Dreamhack] csrf-2 write-up (0) | 2023.11.15 |
---|---|
[Dreamhack] csrf-1 write-up (0) | 2023.11.15 |
[Webhacking.kr] old-23 write-up (0) | 2023.11.14 |
[Dreamhack] xss-2 write-up (0) | 2023.11.04 |
[Dreamhack] xss-1 write-up (0) | 2023.11.04 |