
Consulenza tecnica e proof of concept per CVE-2026-97286, una XSS memorizzata autenticata nel plugin WordPress Strong Testimonials, con catena di attacco, PoC e analisi della patch.
Un utente autenticato con ruolo Contributor può memorizzare metadati di foto di piattaforma controllati dall'attaccante nel plugin WordPress Strong Testimonials. Prima della versione 3.3.12, il plugin renderizzava il valore memorizzato all'interno di un attributo src di un'immagine senza escaping contestuale. Dopo che un amministratore pubblica la testimonianza creata ad hoc e un articolo che la visualizza, il payload viene eseguito per i visitatori che aprono quell'articolo, inclusi i visitatori non autenticati.
Questa ricerca è stata condotta in un laboratorio locale autorizzato ed è stata divulgata tramite Patchstack prima della pubblicazione pubblica.
| Campo | Valore |
|---|---|
| CVE | CVE-2026-97286 |
| Prodotto | Strong Testimonials |
| Fornitore | WP Chill |
| Slug del plugin | strong-testimonials |
| Vulnerabilità | Cross-site scripting memorizzato autenticato |
| Versioni interessate | <= 3.3.11 |
| Versione corretta | 3.3.12 |
| Privilegio richiesto | Contributor o superiore |
| CWE | CWE-79: Neutralizzazione impropria dell'input durante la generazione di pagine web |
| CVSS v3.1 | 6.5 Medio — CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L |
| Ricercatore | Rully Miftahur Rozaq |
| Segnalato | 16 settembre 2026 |
| Divulgazione pubblica | 30 settembre 2026 |
Patchstack classifica l'advisory come a bassa priorità anche se la severità di base CVSS v3.1 assegnata è Media.
L'editor delle testimonianze accetta un array di richiesta custom e memorizza ogni chiave inviata come metadato del post. Le chiavi non presenti nella definizione del campo personalizzato configurato passano attraverso sanitize_text_field(). Ciò consente a un Contributor di inviare metadati riservati al plugin come platform e platform_user_photo durante la modifica di una testimonianza di sua proprietà.
Per le piattaforme social supportate, il plugin recupera platform_user_photo, verifica che l'URL restituisca una risposta HTTP 200 e poi interpola la stringa originale memorizzata in un attributo <img src="https://raw.githubusercontent.com/rully2212/cve-2026-97286/main/...">. La validazione dello stato HTTP non rende una stringa sicura per un attributo HTML. Un segno di virgoletta nel frammento dell'URL può terminare src e introdurre un gestore di eventi.
La catena di attacco verificata è:
L'iniezione è autenticata. Il visitatore che attiva il payload già pubblicato non deve essere autenticato.
Lo sfruttamento riuscito fornisce l'esecuzione persistente di JavaScript nell'origine WordPress interessata sulle pagine che renderizzano la testimonianza creata ad hoc attraverso il percorso vulnerabile della miniatura. Il codice viene eseguito con i privilegi del browser e la sessione WordPress del visitatore, se presente.
Il percorso riprodotto richiede la pubblicazione da parte dell'amministratore, il che giustifica la metrica CVSS UI:R. Questa ricerca non ha dimostrato iniezione non autenticata, esecuzione automatica su ogni pagina del sito, escalation di privilegi, furto di cookie o compromissione completa del sito.
La proof of concept è stata riprodotta con la seguente configurazione:
| Componente | Configurazione |
|---|---|
| WordPress | 7.1 |
| Strong Testimonials | 3.3.7, edizione gratuita |
| Tema | Twenty Twenty-Five |
| Template di visualizzazione | default-theme |
| Miniatura | Abilitata |
| PHP / Apache | PHP 8.3.33 / Apache 2.4.68 |
| Database | MariaDB 11.4 |
| Ruolo dell'attaccante | Contributor |
| Capacità dell'attaccante | edit_posts=true, publish_posts=false, unfiltered_html=false |
La versione 3.3.7 era la versione esatta utilizzata nel laboratorio. Il record CVE pubblico identifica tutte le versioni fino alla 3.3.11 come interessate e la versione 3.3.12 come corretta.
In admin/class-strong-testimonials-post-editor.php, save_details() itera su ogni voce nell'array custom inviato. Una chiave non riconosciuta raggiunge il ramo di sanitizzazione generico e viene poi memorizzata con update_post_meta():
$custom = $_POST['custom'];
foreach ( $custom as $key => $value ) {
// ...
$sanitized_value = sanitize_text_field( $value );
update_post_meta( $post_id, $key, $sanitized_value );
}
Il gestore reale esegue controlli nonce e sanificazione specifica per campo. La scoperta non si basa sull'aggiramento di tali controlli o sulla modifica del testimonial di un altro utente. Il Contributor modifica legittimamente il proprio testimonial ma fornisce le chiavi riservate platform e platform_user_photo.
Nelle versioni vulnerabili, includes/functions-image.php legge platform_user_photo, esegue un controllo dello stato HTTP tramite wpmtst_is_valid_image_url() e inserisce la stringa originale in un attributo HTML:
$platform_user_photo = get_post_meta( $post_id, 'platform_user_photo', true );
if ( ! $platform_user_photo || ! wpmtst_is_valid_image_url( $platform_user_photo ) ) {
return $img;
}
return sprintf(
'<img src="https://raw.githubusercontent.com/rully2212/cve-2026-97286/main/%25s" %s %s/>',
$platform_user_photo,
$width ? "width='{$width}'" : '',
$height ? "height='{$height}'" : ''
);
La porzione di frammento di un URL non viene inviata al server HTTP, ma rimane nella stringa successivamente renderizzata da PHP. Il server può quindi vedere un URL innocuo che restituisce HTTP 200 mentre il browser riceve un valore contenente una sequenza di attribute-breakout.
Il template default-theme chiama wpmtst_get_thumbnail() ed emette direttamente il markup dell'immagine restituito:
<figure class="wp-block-image wpmtst-testimonial-avatar">
<?php echo $wpmtst_avatar_html; ?>
</figure>
Il template presuppone che l'helper abbia già prodotto HTML sicuro. Nelle versioni interessate, l'helper delle foto della piattaforma non effettuava l'escape dell'URL per il suo contesto src.
Riproduci questo problema solo su un sistema di tua proprietà o per cui sei esplicitamente autorizzato a eseguire test.
<= 3.3.11 su un sito WordPress di test autorizzato.default-theme con le miniature abilitate.Accedi come Contributor e crea una nuova bozza di testimonial.
Cattura la richiesta di salvataggio legittima POST /wp-admin/post.php utilizzando un proxy di intercettazione. Conserva il suo corpo del form completo, il cookie di sessione, un nonce WordPress fresco, il nonce del plugin e l'ID del testimonial di proprietà del Contributor.
Aggiungi questi parametri al corpo del form:
custom[platform]=facebook
custom[platform_user_photo]=http://wordpress/index.php?rest_route=/#" onerror="alert(1)" x="
Frammento di form codificato in URL:
&custom%5Bplatform%5D=facebook&custom%5Bplatform_user_photo%5D=http%3A%2F%2Fwordpress%2Findex.php%3Frest_route%3D%2F%23%22%20onerror%3D%22alert%281%29%22%20x%3D%22