https://dreamhack.io/wargame/challenges/433
๋ฌธ์ ์ ๋ชฉ์ ๋ณด์ํ๋, xss ํํฐ๋ง์ ์ฐํํด์ ํธ๋ ๋ฌธ์ ์ธ ๊ฒ ๊ฐ๋ค.
์ผ๋จ ๋ฌธ์ ์นํ์ด์ง๋ฅผ ๋ถ์ํ๊ณ , ๊ทธ ๋ค์์ ๋ฐ์ ๋ฌธ์ ํ์ผ๋ ๋ถ์ํด๋ณด๋๋ก ํ๊ฒ ๋ค.
[1] ๋ฌธ์ ๋ถ์
# ๋ฌธ์ ํ์ด์ง ๋ถ์
์ด๊ธฐ ํ๋ฉด์ด๋ค.
(1) /vuln(xss) page
/vuln(xss) page ๋ฅผ ๋ค์ด๊ฐ๋ณด๋ฉด ๋์ค๋ ํ๋ฉด.
<img> ํ๊ทธ๊ฐ ์ฌ์ฉ๋์๋ค.
src๋ ์ด๋ฏธ์ง ํ์ผ์ URL์ ์ง์ ํ๊ณ ,
๋ฐ๋ผ์ ์น ํ์ด์ง์์ ํด๋น https://dreamhack.io/assets/img/logo.0a8aabe.svg์ ์๋ ์ด๋ฏธ์ง๋ฅผ ํ์ํ๋๋ก ํ๋ค.
(2) /memo
memo ํ์ด์ง์ ๋ค์ด๊ฐ ๋ ๋ง๋ค 'hello'๊ฐ ์ถ๋ ฅ๋๋ค.
URL์ /memo?memo=hello ๋ถ๋ถ์ ๋ณด๋ฉด GET ๋ฐฉ์์ ์ฌ์ฉํ๊ณ ์์์ ์ ์ ์๋ค.
/memo ํ์ด์ง์ ํ๋ผ๋ฏธํฐ memo์ hello๋ฅผ ์ ๋ ฅํ์ฌ ์ ์กํ๋ค๋ ์๋ฏธ๋ก ๋ฐ์๋ค์ผ ์ ์๋ค.
๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ?memo=hi๋ฅผ ์ ์กํ๋ฉด "hi"๋ผ๋ ๋ฉ์ธ์ง๊ฐ ์ด ํ์ด์ง์ ์ถ๋ ฅ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์ด ํ์ด์ง์ ์์ค ์ฝ๋๋ ์๋์ ๊ฐ๋ค,
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="/static/css/non-responsive.css">
<title>Index XSS-Filtering-Bypass</title>
<style type="text/css">
.important { color: #336699; }
</style>
</head>
<body>
<!-- Fixed navbar -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">XSS-Filtering-Bypass</a>
</div>
<div id="navbar">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container">
<pre>hello
hi
</pre>
</div> <!-- /container -->
<!-- Bootstrap core JavaScript -->
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
</body>
</html>
์... ์์๋ผ ์ ์๋ ๊ฒ์ ์์๋ค.
(3) /flag
/flag ํ์ด์ง์ ํ๋ฉด์ด๋ค.
์ง๊ธ๊น์ง ๋๋ฆผํต์์ ํ์๋ xss ๋ฌธ์ ๋ค์,
์ ๋ ฅ ์นธ์ ๋ฌด์ธ๊ฐ๋ฅผ ์ ๋ ฅํ๊ณ ์ ์ถํ๋ฉด /memo ํ์ด์ง์ ๋ฌธ์ ์ ๋ต์ธ FLAG๊ฐ ์ถ๋ ฅ๋์์๋ค.
์๋ง ์ด ๋ฌธ์ ๋ํ ๋น์ทํ ๋ฐฉ์์ผ ๊ฒ์ด๋ผ๊ณ ์์๋๋ค.
(๋ค์ด๋ฐ์ ๋ฌธ์ ํ์ผ์ ์ดํด๋ณด๋ฉด ์ ์ ์์ ๊ฒ์ด๋ค.)
๋ค์์ /memo ํ์ด์ง์ ์์ค ์ฝ๋์ด๋ค.
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="/static/css/non-responsive.css">
<title>Index XSS-Filtering-Bypass</title>
<style type="text/css">
.important { color: #336699; }
</style>
</head>
<body>
<!-- Fixed navbar -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">XSS-Filtering-Bypass</a>
</div>
<div id="navbar">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container">
<form method="POST">
http://127.0.0.1:8000/vuln?param=<input type="text" name="param"/><br/>
<input type="submit"/><br/>
</form>
</div> <!-- /container -->
<!-- Bootstrap core JavaScript -->
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
</body>
</html>
์ด ์ฝ๋์์ ํผ์ POST ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋ฉฐ,
"http://127.0.0.1:8000/vuln?param="์ผ๋ก ์์ํ๋ URL๋ก ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ธ๋ค๋ ๊ฒ์ ์ ์ ์๋ค.
# ๋ฌธ์ ํ์ผ ๋ถ์
(1) app.py
#!/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)
flag๊ฐ์ ์ฐพ์๋ด๋ ๊ฒ์ด ๋ชฉํ์ด๋ฏ๋ก, ์ฐ์ ์ด ํ์ผ์์ 'flag'๋ผ๋ ๋ฌธ์์ด์ด ๋ค์ด๊ฐ ๋ถ๋ถ์ ๋จผ์ ๋ณด์๋ค.
๊ทธ๋ฌ๋ค๋ณด๋ /flag ํ์ด์ง์ ๊ตฌ๋ ๋ฐฉ์์ ๋ํ๋ด๋ '@app.route("/flag", methods=["GET", "POST"])'๋ผ๋ ๋ถ๋ถ์ ๋จผ์ ๋ณด๊ฒ๋์๋ค.
@app.route("/flag", methods=["GET", "POST"])
์ด ์ฝ๋๋ "/flag" ๊ฒฝ๋ก(ํ์ด์ง)์ ๋ํ GET ๋ฐ POST ์์ฒญ์ ์ฒ๋ฆฌํ๋ค.
1. ๋ง์ฝ ํด๋ผ์ด์ธํธ๊ฐ GET ์์ฒญ์ ๋ณด๋ผ ๊ฒฝ์ฐ, "flag.html" ํ ํ๋ฆฟ์ ๋ ๋๋งํ์ฌ ๋ฐํํ๋ค.
2. ๋ง์ฝ ํด๋ผ์ด์ธํธ๊ฐ POST ์์ฒญ์ ๋ณด๋ผ ๊ฒฝ์ฐ, ์์ฒญ์ผ๋ก๋ถํฐ "param"์ด๋ผ๋ ์ด๋ฆ์ ํผ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์จ๋ค.
(์ฌ๊ธฐ์ "param"์ด๋ผ๋ ํผ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฑธ ๋งํ๋ ๊ฒ ๊ฐ๋ค.)
3. ๊ทธ๋ฆฌ๊ณ check_xss ํจ์๋ฅผ ํธ์ถํด XSS ๊ณต๊ฒฉ์ ํ์ธํ๋ค. ๋ง์ฝ xss ๊ณต๊ฒฉ์ด ๊ฐ์ง๋๋ฉด, "wrong??"์ด๋ผ๋ ๊ฒฝ๊ณ ๋ฉ์ธ์ง๋ฅผ ํฌํจํ ์๋ฐ ์คํฌ๋ฆฝํธ ์ฝ๋๋ฅผ ๋ฐํํ๊ณ ์ด์ ํ์ด์ง๋ก ์ด๋ํ๋๋ก ํ๋ค. ๋ง์ฝ xss ๊ณต๊ฒฉ์ด ๊ฐ์ง๋์ง ์์ผ๋ฉด "good"์ด๋ผ๋ ๊ฒฝ๊ณ ๋ฉ์์ง๋ฅผ ํฌํจํ ์๋ฐ ์คํฌ๋ฆฝํธ ์ฝ๋๋ฅผ ๋ฐํํ๊ณ ์ด์ ํ์ด์ง๋ก ์ด๋ํ๋๋ก ํ๋ค.
.
.
.
๊ทธ๋ ๋ค๊ณ ํ๋ค.
๊ทธ๋ผ xss ๊ณต๊ฒฉ์ ํ์ธํ๋ check_xss ํจ์ ๋ถ๋ถ์ ์ดํด๋ณด์.
def check_xss(param, cookie={"name": "name", "value": "value"})
check_xss ํจ์๋ param ๊ฐ์ ์ฌ์ฉํด xss ๊ณต๊ฒฉ ์ฌ๋ถ๋ฅผ ํ์ธํ๊ธฐ ์ํด
์ธ๋ถ URL (http://127.0.0.1:8000/vuln?param=<encoded_param>)๋ก ์์ฒญ์ ๋ณด๋ด๊ณ ,
return_url ํจ์๋ฅผ ํธ์ถํด ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ค.
.
.
.
์ฌ๊ธฐ์ read_url ํจ์๋ฅผ ์ดํด๋ณด์.
def read_url(url, cookie={"name": "name", "value": "value"}):
์ด ํจ์์ ๋ด์ฉ์๋ ๋๋ ๋ชจ๋ฅด๋ ๋ด์ฉ์ด ๋ง์ด ์ ํ์์ด์ ์ฐพ์๋ดค๋๋ฐ,
์์ฝํ๋ฉด read_url ํจ์๋:
์ธ๋ถ URL์ ๋ํ ์์ฒญ์ ์๋ฎฌ๋ ์ด์ ํ๊ณ , ํด๋น ํ์ด์ง์ ์ฟ ํค๋ฅผ ์ ๋ฌํ ํ XSS ๊ณต๊ฒฉ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ ๋ฐ ํ์ฉ๋๋ค๊ณ ํ๋ค.
.
.
.
์ฌ๊ธฐ์๋ ํํธ๋ฅผ ์ป์ง ๋ชปํด์ app.py ํ์ผ ์์ ๋ค๋ฅธ ๋ถ๋ถ๋ ์ดํด๋ณด์๋ค.
@app.route("/vuln")
์ด ๋ถ๋ถ์ "/vuln"(ํ์ด์ง)์ ๋ํ ๋ถ๋ถ์ด๋ค.
์ฝ๋ ๋์์ ์ค๋ช ํ์๋ฉด,
1. GET ์์ฒญ์ผ๋ก๋ถํฐ "param" ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์๋ฅผ ๊ฐ์ ธ์จ๋ค. ๋ง์ฝ "param"์ด ์์ผ๋ฉด ๋น ๋ฌธ์์ด๋ก ์ด๊ธฐํํ๋ค.
2. ๊ฐ์ ธ์จ "param" ๊ฐ์ xss_filter ํจ์๋ก ์ ๋ฌํ์ฌ XSS ๊ณต๊ฒฉ์ ๋ฐฉ์งํ๋๋ก ํํฐ๋งํ๋ค.
3. ํํฐ๋ง๋ "param" ๊ฐ์ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฐํํ๋ค.
.
.
.
์ฌ๊ธฐ์ xss_filter ํจ์์์ ์ด๋ป๊ฒ xss ๊ณต๊ฒฉ์ ๋ฐฉ์งํ๋์ง ์์๋ณด๊ธฐ ์ํด, xss filter ํจ์๋ฅผ ์ดํด๋ณด๋๋ก ํ์.
def xss_filter(text):
์ด ํจ์์์๋ ์ฃผ์ด์ง ํ ์คํธ์์ ํน์ XSS ๊ณต๊ฒฉ๊ณผ ๊ด๋ จ๋ ๋ฌธ์์ด์ ํํฐ๋งํ์ฌ ์ ๊ฑฐํ๋ ์ญํ ์ ํ๋ค.
์ข ๋ ์์ธํ ๋์์ ์ค๋ช ํ๊ฒ ๋ค,:
1. _filter = ["script", "on", "javascript:"]:
ํํฐ๋งํ ๋ฌธ์์ด ํจํด์ ๋ด๊ณ ์๋ ๋ฆฌ์คํธ๋ฅผ ์์ฑํ๋ค.
"script", "on", "javascript:"์ด ํฌํจ๋์ด ์๋ค.
2. for f in _filter::
_filter ๋ฆฌ์คํธ์ ๊ฐ ๋ฌธ์์ด์ ์ํํ๋ค.
3. if f in text.lower()::
์ฃผ์ด์ง ํ ์คํธ์ ๋๋ฌธ์ ๋๋ ์๋ฌธ์๋ก ๋ณํํ ๋ฌธ์์ด ์ค์ _filter์ ์์๊ฐ ํฌํจ๋์ด ์๋์ง ๊ฒ์ฌํ๋ค.
(๋์๋ฌธ์๋ฅผ ๋ฌด์ํ๊ณ ๊ฒ์ฌํ๊ธฐ ์ํด ํ ์คํธ๋ฅผ ์๋ฌธ์๋ก ๋ณํํ ํ ๊ฒ์ฌํ๋ ๊ฒ.)
4. text = text.replace(f, ""):
ํ ์คํธ์์ _filter์ ํด๋นํ๋ ๋ฌธ์์ด์ ์ ๊ฑฐํ๋ค.
๋ง์ฝ "script", "on", "javascript:" ๊ฐ ํฌํจ๋์ด ์๋ค๋ฉด ํด๋น ๋ฌธ์์ด์ ๋น ๋ฌธ์์ด๋ก ์นํํ์ฌ ์ ๊ฑฐํ๋ค.
5. return text:
ํํฐ๋ง๋ ํ ์คํธ๋ฅผ ๋ฐํํ๋ค.
์ ๋ฆฌํ์๋ฉด
>> text๋ฅผ ๋ชจ๋ ์๋ฌธ์๋ก ๋ณํํ์ฌ "script", "on", "javascript:"๊ฐ ํฌํจ๋์ด ์๋์ง ๊ฒ์ฌํ๊ณ , ์๋ค๋ฉด ํด๋น ๋ฌธ์์ด์ ๋น ๋ฌธ์์ด๋ก ์นํํ์ฌ ์ ๊ฑฐํ๋ ๊ฒ์ด๋ค.
.
.
.
๊ทธ๋ ๋ค๋ฉด text ๋งค๊ฐ๋ณ์์ ์ ํํ ์๋ฏธ๋ฅผ ์๊ธฐ ์ํด ๋ค๋ฅธ ๋ถ๋ถ๋ ์ดํด๋ณด์๋ค.
@app.route("/memo")
์ด ๋ถ๋ถ์ ํ์ด๋ณด๋ def memo()์์์ 'text'๊ฐ def xss_filter(text)์์ ๋งค๊ฐ๋ณ์๋ก ์ฐ์ด๋ ๊ฒ ๊ฐ์๋ค.
์ข ๋ ์์ธํ ์ค๋ช ํ์๋ฉด,
memo ํจ์์์ text ๋ณ์์ ํ ๋น๋ ๊ฐ์ ์ฌ์ฉ์๊ฐ "/memo"๋ก ์ ์กํ GET ์์ฒญ์ "memo" ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์์์ ๊ฐ์ ธ์จ ๊ฐ์ด๋ค.
์ด text ๋ณ์๋ ์ดํ memo_text ๋ณ์์ ์ถ๊ฐ๋๊ณ , ๊ทธ ๊ฒฐ๊ณผ๊ฐ ํ ํ๋ฆฟ์ผ๋ก ์ ๋ฌ๋๋ค.
๋ง์ฝ ์ด text ๊ฐ์ xss_filter ํจ์๋ก ์ ๋ฌํ๋ค๋ฉด, xss_filter ํจ์์์ ์ ์๋ ํํฐ๋ง์ด ์ ์ฉ๋์ด XSS ๊ณต๊ฒฉ์ ๋ฐฉ์งํ๋ค.
๊ทธ๋ฆฌ๊ณ ์ฌ๊ธฐ์ text ๋ณ์๋ xss_filter ํจ์์ text ๋งค๊ฐ๋ณ์๋ก ์ฌ์ฉ๋ ๊ฒ์ ๋๋ค.
[2] ๊ณต๊ฒฉ ์๋
/vuln ํ์ด์ง์์
/vuln?param=<script>alert(1);<script> URL ๋ค์ ์ ๋ ฅํด์ฃผ์๋๋
์ญ์ 'script'๊ฐ ํํฐ๋ง๋์ด์ ๋์ค๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค.
NULL(%00)์ ์ฌ์ฉํ๋๋ ๊นจ์ง ํ์์ด ๋ฐ์ํ๋ค.
์ฌ์ฉํ ์ ์๋ค.
(๋์ค์ ๋ณด๋๊น ์คํฌ๋ฆฝํธ ํ๊ทธ ๋ซ๋ ๋ถ๋ถ์ </script>์์ '/'์ด ๋น ์ก๋ค...ใ ใ ๊ทธ๋ฆฌ๊ณ %00 ๋ฐ๋ก ๋ค์์ ๋ฌธ์ ํ๋๋ฅผ ์ ๋ ฅ์์ผ์ค์ผ ํ๋ ๊ฒ๋ ๊น๋จน์๋ค... ์ด์ฐ๋์๋ NULL์ ์ฌ์ฉํ๋ฉด ๊นจ์ ธ์ script๋ ์ธ์์ด ์ ๋จ!)
์!
ํํฐ๋ง ๋๋ ํค์๋ ์ฌ์ด์ ์๋ก์ด ํค์๋๋ฅผ ๋ฃ์ด๋๋ ๊ฒฝ์ฐ ์ฐํ๊ฐ ๊ฐ๋ฅํ ๋ฌธ์์ด ์นํ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋๋ ๊ณต๊ฒฉ์ ์ฑ๊ณตํ์๋ค!
๊ทธ๋ ๋ค๋ฉด ๊ณต๊ฒฉ์ ๋ฌธ์์ด ์นํ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ฉด ๋๋ค.
์ด์ ์ ์ ํ์๋ xss ๋ฌธ์ ์์ <svg onload="location.href = '/memo?memo=' + document.cookie"> ์ฝ๋์ ๋ฌธ์์ด ์นํ ๋ฐฉ๋ฒ์ ์ ์ฉ์์ผ
์ฟ ํค ๊ฐ์ ์ ์ฅ๋ flag๊ฐ์ ์ฝ์ด์ฌ ๊ฒ์ด๋ค.
<svg oonnload="locatioonn.href = '/memo?memo=' + document.cookie"> ๋ฅผ ์ ๋ ฅํด์ฃผ๋ฉด ๋๋ค.
(์ฒ์์ location์๋ 'on'์ด ๋ค์ด๊ฐ ๊ฒ์ ๋์น๊ณ ๊ณ์ "hello"๊ฐ ์ถ๋ ฅ๋์ด์ ๋นํฉํ๋ค.)
ํ์ด ์๋ฃ!
[3]๋๋ ์
์ค๋๋ง์ ํ์ด์ฌ ์ฝ๋๋ฅผ ์ ์ฌํ ๋ค์ฌ๋ค๋ณด๋ ๋ฐฐ์ ๋ ๊ธฐ์ต์ด ๋ค์ ๋ฌ๋ค.
ํจ์์ ๋ณ์๊ฐ ์ ์ธ๋๊ณ ํธ์ถ๋๋ ์ ๊ธฐ์ ์ธ ๊ณผ์ ์ ๋ฐ๋ผ๊ฐ๋ ๊ฒ์ด ๋๋ฆ ์ฌ๋ฏธ์์๋ ๊ฒ ๊ฐ๋ค.
์ ๋ณดํต python์ ์ฌ์ฉํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ํ์ผ์ ๋ง๋๋์ง๊ฐ ๊ถ๊ธํ๋ค. (ํ์ด์ฌ์ ๊ธฐ๋ฅ์ด ๋ค์ํด์ ๊ทธ๋ฐ ๊ฒ์ผ๊น?)
'SWLUG > ์น ํดํน' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Dreamhack/๋๋ฆผํต] csrf-2 (1) | 2023.11.11 |
---|---|
[Dreamhack/๋๋ฆผํต] csrf-1 (0) | 2023.11.10 |
[webhacking.kr] Challenge old-23 (0) | 2023.11.09 |
[Dreamhack/๋๋ฆผํต] xss-2 (0) | 2023.11.06 |
[xss-game] Level 5: Breaking protocol (3) | 2023.11.06 |