28.XSS-1

3 minute read

Question info.

여러 기능과 입력받은 URL을 확인하는 봇이 구현된 서비스입니다.
XSS 취약점을 이용해 플래그를 획득하세요. 플래그는 flag.txt, FLAG 변수에 있습니다.

Function analysis

28_1

28_2

28_3

28_4

vuln(xss) page 링크를 클릭하면 Alert창이 뜨면서 1이 나온다.
vuln?param=<script>alert(1)</script>
memo 링크를 클릭하면 hello가 입력된다.
memo?memo=hello
flag 링크를 클릭하면 인자를 입력하는 페이지가 나온다.

Code analysis

#!/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)


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


@app.route("/vuln")
def vuln():
    param = request.args.get("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)


from selenium import webdriver

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

왜? Selenium을 사용했을까?
selenium은 동적 웹을 스크래핑할 때 사용하는 라이브러리이다.
chromedriver를 이용해서 크롬 창을 띄운 뒤 마우스로 스크래핑하듯 모두 긁는다.
driver.get("http://127.0.0.1:8000/")
서버 측에 브라우저를 연다.

try:
    FLAG = open("./flag.txt", "r").read()
except:
    FLAG = "[**FLAG**]"

FLAG값은 flag.txt에 들어 있다.

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)

check_xss는 flag링크에서 파라미터를 입력하고 ‘제출’을 누르면 실행되는 함수다.
보면 서버 측 브라우저에 파라미터 값이 넘어가서 실행되는 것을 알 수 있다.

Solve

이 문제는 서버 쪽에서 실행된 브라우저를 조작하여 flag값을 얻는 문제였다.
서버 측 브라우저에게 memo 페이지로 접근하게 하고 서버 측 쿠키값을 memo 페이지에 적도록 유도하는 문제이다. memo 페이지에 계속 쌓이는 값들은 html 파일에 그대로 축적되는 것이라서 서버에 그대로 적용된다.
그래서 서버나 클라이언트가 memo 페이지에 접근해도 값이 그대로 남아 있게 된다.

즉, vuln 페이지에서 파리미터로 넣은 JS 코드를 실행시켜 memo 페이지로 가게 유도하고 쿠키값을 적게 하면된다.

28_5

flag 페이지에서 파라미터로

<script>window.location.href="http://127.0.0.1:8000/memo?memo="+document.cookie;</script>

을 넣으면 된다.

28_6

memo 페이지로 가서 확인해보면 flag값이 나와 있다.

댓글남기기