IT/Wargame

[Web Hacking] xss-2

kykyky 2024. 7. 3. 15:37

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)


@app.route("/")
def index():
    return render_template("index.html")


@app.route("/vuln")
def vuln():
    return render_template("vuln.html")


@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)

 

read_url(url, cookie):
cookie -> {"domain": "127.0.0.1"} update됨
chrome 드라이버가 http://127.0.0.1:8000/에 GET 요청 보냄
cookie 더함
url에 GET 요청 보냄

check_xss(param, cookie):

cookie -> {"domain": "127.0.0.1"} update됨
chrome 드라이버가 http://127.0.0.1:8000/에 GET 요청 보냄
cookie 더함
param을 포함한 새 url에 GET 요청 보냄

/로 들어오면: 
index.html 렌더링됨

/vuln으로 들어오면:
vuln.html 렌더링됨

/flag로 들어오면: 
요청이 GET이면 -> flag.html 렌더링됨
요청이 POST이면 -> 
    check_xss(요청의 param, {"name": "flag", "value": FLAG.strip()})
    위 함수 반환이 False이면 -> '<script>alert("wrong??");history.go(-1);</script>' 반환
    '<script>alert("good");history.go(-1);</script>' 반환

/memo로 들어오면: 
요청으로부터 memo를 추출하고, 전역 변수인 memo_text에 누적
memo.html을 렌더링하여 memo_text 출력

 

vuln.html

{% extends "base.html" %}
{% block title %}Index{% endblock %}

{% block head %}
  {{ super() }}
  <style type="text/css">
    .important { color: #336699; }
  </style>
{% endblock %}

{% block content %}
    <div id='vuln'></div>
    <script>
    var x = new URLSearchParams(location.search); 
    document.getElementById('vuln').innerHTML = x.get('param');
    </script>
{% endblock %}

※ location.search: 현재 URL의 query string 부분 (URL의 ? 이후 부분)

현재 URL의 param 부분을 현재 document 내에 넣는다.

그럼 브라우저는 vuln.html 내 태그들을 다시 parse하고 (DOM update),

새로운 태그가 img, script 등일 경우 이들에 대한 GET 요청을 보낸다.

 

즉, URL의 query string 중 param에 따라 페이지의 내용이 변경될 수 있는 취약점이 있다.

 

 

flag.html

{% extends "base.html" %}
{% block title %}Index{% endblock %}

{% block head %}
  {{ super() }}
  <style type="text/css">
    .important { color: #336699; }
  </style>
{% endblock %}

{% block content %}
  <form method="POST">
    http://127.0.0.1:8000/vuln?param=<input type="text" name="param"/><br/>
    <input type="submit"/><br/>
  </form>
{% endblock %}

화면의 form에 채운 값은 POST 요청의 param이 된다.

 

Flag가 어디에 있는가? 

/flag에서 form(= URL의 param이 된다)을 @@@으로 채워 POST 요청을 보내면,

check_xss() 함수를 통해

cookie ({"name": "flag", "value": FLAG.strip()})를 add한 뒤

http://127.0.0.1:8000/vuln?param=@@@에 GET 요청을 보낸다.

 

따라서, /flag에 POST 요청을 보내면 flag값을 cookie에 담을 수 있는 것이다.

 

param에 뭘 쓸까?

param에 <script>alert(1)</script>와 같은 스크립트를 넣어 XSS를 발생시키고 싶지만, 여기서는 불가능하다.

render_template 함수를 사용하면,

전달된 템플릿 변수가 기록될 때 HTML 엔티티코드로 변환돼 저장되기 때문에,

이용자가 입력한 값을 페이지에 그대로 출력하지 않아 XSS가 발생하지 않는다.

 

그럼, 만약 param에 <img src="..." onerror="...">가 들어간다면?

img의 src인 URL에 이미지가 존재하지 않는 경우, onerror로 설정한 event handler에 의해 javascript 코드가 실행된다. 

그러니, src에 이상한 URL을 적고 onerror를 통해 /memo에 cookie를 write한다면, cookie값을 볼 수 있을 것이다.

 

/memo에 cookie를 write하기 위해서는, 

i) /memo로 redirection하고 

location.href='/memo를 통해 현재 document의 URL (location.href)을 /memo로 바꾸면 브라우저가 여기로 redirect한다.

ii) memo parameter를 document.cookie로 설정하면 된다.

?memo='+document.cookie

 

최종 exploit은 아래와 같다: 

<img src="XSS-2" onerror="location.href='/memo?memo='+document.cookie">

'IT > Wargame' 카테고리의 다른 글

[Web hacking] csrf-2  (0) 2024.07.06
[Web Hacking] csrf-1  (2) 2024.07.05
[System Hacking] fho ㅡ Hook overwrite  (1) 2024.05.17
[System Hacking] basic_rop_x64  (0) 2024.03.19
[System Hacking] ssp_001  (0) 2024.03.07