
# Docker-Lab zur Reproduktion von CVE-2025-11262, einer nicht authentifizierten gespeicherten Blind-XSS-Schwachstelle im Link Whisper Free WordPress-Plugin. Enthält verwundbare und gepatchte Zielsysteme, PoC-Skript und Root-Cause-Analyse.
Dieses Repository enthält ein lokales Docker-Labor zur Reproduktion von CVE-2025-11262, einem nicht authentifizierten, gespeicherten Cross-Site-Scripting-Problem, das das WordPress-Plugin Link Whisper Free betrifft.
Das Labor vergleicht zwei Plugin-Versionen:
| Dienst | Plugin-Version | Zweck | URL |
|---|---|---|---|
vuln | 0.9.0 | Verwundbares Ziel | http://127.0.0.1:8081 |
patched | 0.9.1 | Gepatchtes Vergleichsziel | http://127.0.0.1:8082 |
Die demonstrierte Schwachstellenkette ist:
Unauthenticated REST request
→ attacker-controlled user_id is persisted
→ a privileged WordPress user opens the Link Whisper AI Subscription page
→ the stored value is rendered into an admin JavaScript context
→ alert("CVE-2025-11262-LAB") executes on the vulnerable version
Der Angreifer muss nicht angemeldet sein, um die gespeicherte Nutzlast zu platzieren. Das JavaScript wird später ausgeführt, wenn ein privilegierter WordPress-Benutzer die betroffene Admin-Seite öffnet.
Dieses Labor ist ausschließlich für kontrollierte lokale Forschung, das Verständnis auf Quellcode-Ebene und Demonstrationszwecke im Portfolio gedacht.
| Behauptung | Beleg | So überprüfen Sie es in diesem Labor |
|---|---|---|
Link Whisper Free 0.9.0 ist verwundbar. | Öffentliche Advisories identifizieren Link Whisper Free-Versionen bis einschließlich 0.9.0 als betroffen. | Führen Sie den PoC gegen http://127.0.0.1:8081 aus und öffnen Sie die ausgegebene Admin-URL. |
Link Whisper Free 0.9.1 enthält den Fix. | Öffentliche Advisory- und Changelog-Daten identifizieren 0.9.1 als gepatchte Version. | Führen Sie denselben PoC gegen http://127.0.0.1:8082 aus; es sollte kein Alert erscheinen. |
| Das Platzieren der Nutzlast erfolgt ohne Authentifizierung. | Der PoC sendet eine POST-Anfrage ohne WordPress-Cookies, Login oder Nonce. | Prüfen Sie poc/poc.py; es benötigt nur --url. |
| Der sichtbare Effekt wird im WordPress-Adminbereich ausgelöst. | Der gespeicherte Wert wird gerendert, wenn die Link-Whisper-KI-Abonnement-Seite von einem privilegierten Benutzer geöffnet wird. | Führen Sie den PoC aus, melden Sie sich dann als Admin an und öffnen Sie die ausgegebene Admin-URL. |
Das gepatchte Ziel kann auf HTTP-Ebene weiterhin "ok" zurückgeben. | Lokale Tests zeigten, dass beide Ziele "ok" zurückgeben können; der bedeutsame Unterschied ist, ob die Nutzlast gespeichert und ausgeführt wird. | Vergleichen Sie das Browserverhalten auf 8081 und 8082. |
Dies ist der Abschnitt Zusammenfassung der Grundursache zum Ersetzen im README. Er ist public-safe, erwähnt keine internen Dateien wie vuln_detail.txt und basiert auf dem Labor/Quellcode, den Sie derzeit verwenden.
CVE-2025-11262 wird durch eine gespeicherte JavaScript-Injektionskette in Link Whisper Free 0.9.0 verursacht.
Das Problem ist nicht ein einzelner fehlender Escaping-Aufruf. Es ist eine Kette mehrerer unsicherer Verhaltensweisen:
Unauthenticated REST endpoint
→ insufficient validation of user_id
→ persistent storage in wpil_ai_access_user_id
→ unsafe rendering into an admin JavaScript context
→ stored XSS when a privileged user opens the AI Subscription page
Link Whisper Free registriert einen REST-Endpunkt für die KI-Authentifizierung unter dem REST-Namespace des Plugins:
const REST_SLUG = 'link-whisper';
const AI_AUTH = 'ai-auth';
Der Endpunkt wird als POST-Route registriert:
register_rest_route(self::REST_SLUG, self::AI_AUTH, [
'methods' => 'POST',
'callback' => [
$this,
'ai_auth_handler'
],
'permission_callback' => "__return_true",
'show_in_index' => false
]);
Da der Permission-Callback __return_true ist, ist der Endpunkt ohne Authentifizierung erreichbar.
Im Labor lautet der effektive Endpunkt:
/wp-json/link-whisper/ai-auth
Das bedeutet, dass ein nicht authentifizierter Angreifer eine Anfrage an den Endpunkt senden kann, ohne eine WordPress-Sitzung, Nonce oder ein Administrator-Konto zu besitzen.
In Link Whisper Free 0.9.0 liest der Handler vom Angreifer kontrollierte Parameter aus der REST-Anfrage:
public function ai_auth_handler( WP_REST_Request $request )
{
if(!empty($request->get_param('access_token'))){
$token = $request->get_param('access_token');
$user_id = $request->get_param('user_id');
$uid = (int)$request->get_param('uid');
$uemail = $request->get_param('uemail');
if(!empty($token) && false !== strpos($token, 'ai-')){
update_option('wpil_ai_access_token', Wpil_Toolbox::encrypt($token));
update_option('wpil_ai_access_user_id', $user_id);
update_option('wpil_ai_access_user_email', $uemail);
update_user_meta($uid, 'wpil_ai_access_user_id', $user_id);
update_user_meta($uid, 'wpil_ai_access_user_email', $uemail);
update_option('wpil_ai_access_authorized', true);
}
return 'ok';
}
return new WP_Error(400, 'Bad request', [ 'status' => 404 ]);
}
Das verwundbare Verhalten ist die schwache Validierungsbedingung:
if(!empty($token) && false !== strpos($token, 'ai-')){
Diese prüft nur, ob das übermittelte Access-Token die Zeichenkette ai- enthält.
Es gibt keine strikte Validierung von user_id, bevor es gespeichert wird:
update_option('wpil_ai_access_user_id', $user_id);
Dadurch kann vom Angreifer kontrolliertes JavaScript in der WordPress-Options-Tabelle gespeichert werden.
Der vom Angreifer kontrollierte Wert user_id wird in der WordPress-Option gespeichert:
wpil_ai_access_user_id
In diesem Labor sendet der PoC die folgende nur-lokale Nutzlast:
</script><script>alert("CVE-2025-11262-LAB")</script>
Auf dem verwundbaren Dienst wird die Nutzlast als Wert von wpil_ai_access_user_id gespeichert.
Der Angreifer muss nicht angemeldet sein, um die Nutzlast zu platzieren. Die Nutzlast wird über den nicht authentifizierten REST-Endpunkt platziert.
Der gespeicherte Wert wird später über die Plugin-Einstellungslogik abgerufen:
public static function get_linkwhisper_ai_user_id(){
return get_option('wpil_ai_access_user_id', '');
}
Der Wert wird $ai_id zugewiesen und in die Admin-Seite für KI-Abonnements gerendert.
In Link Whisper Free 0.9.0 wird der Wert direkt in einen JavaScript-String eingefügt:
body: JSON.stringify({
ai_id: "<?php echo $ai_id;?>",
subscription_id: "<?php echo ((!empty($sub)) && isset($sub->subscription_id)) ? $sub->subscription_id: null;?>"
})
Da $ai_id nicht escaped wird, bevor es in den JavaScript-Kontext eingefügt wird, kann eine gespeicherte Nutzlast aus dem vorgesehenen String ausbrechen und JavaScript ausführen, wenn die Admin-Seite geöffnet wird.
Mit der Labor-Nutzlast wird die verwundbare gerenderte Ausgabe äquivalent zu:
body: JSON.stringify({
ai_id: "</script><script>alert("CVE-2025-11262-LAB")</script>",
subscription_id: ""
})
In einem Browser beendet das injizierte schließende </script>-Tag den ursprünglichen Skriptblock, und der injizierte <script>-Block wird ausgeführt.
Die Nutzlast wird von einem nicht authentifizierten Angreifer platziert, aber für die Ausführung muss ein privilegierter WordPress-Benutzer die betroffene Admin-Seite öffnen:
/wp-admin/admin.php?page=link_whisper_ai_subscription
In diesem Labor wird die betroffene Seite als WordPress-Administrator geöffnet, um den Alert-Dialog auszulösen.