平台搭建

jdk :1.8
mysql:8
下载链接:https://github.com/StarSea99/starsea-mall

修改src/main/resources/application.properties
data.username/data.passwd
自己搭建数据库并连接

复现分析

截屏2025-03-15 17.44.17.png

目录结构
首先尝试分析架构

  1. 表现层(Presentation Layer)controller 和 interceptor 属于这一层,负责处理HTTP请求和响应。
  2. 业务逻辑层(Business Logic Layer)service 属于这一层,负责处理业务逻辑。
  3. 数据访问层(Data Access Layer)dao 和 entity 属于这一层,负责与数据库进行交互。
  4. 配置层(Configuration Layer)config 属于这一层,负责项目的全局配置。
  5. 通用层(Common Layer)common 和 utils 属于这一层,提供通用的工具和常量。
  6. 数据传输层(Data Transfer Layer)vo 属于这一层,负责数据传输。

首先就可以去查看controller和interceptor

interceptor

可以先查看intercepter去看鉴权的问题

截屏2025-03-15 17.57.56.png

  • AdminLoginInterceptor
    主要的处理函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {  
    String requestServletPath = request.getServletPath();
    if (requestServletPath.startsWith("/admin") && null == request.getSession().getAttribute("loginUser")) {
    request.getSession().setAttribute("errorMsg", "请登陆");
    response.sendRedirect(request.getContextPath() + "/admin/login");
    return false;
    } else {
    request.getSession().removeAttribute("errorMsg");
    return true;
    }
    }

    访问url中路由开始是admin并且从Seesion中需要获取到loginUser为空就会先去跳转到login界面

  • MallCartNumberInterceptor

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {  
    //购物车中的数量会更改,但是在这些接口中并没有对session中的数据做修改,这里统一处理一下
    if (null != request.getSession() && null != request.getSession().getAttribute(Constants.MALL_USER_SESSION_KEY)) {
    //如果当前为登陆状态,就查询数据库并设置购物车中的数量值
    UserVO newBeeMallUserVO = (UserVO) request.getSession().getAttribute(Constants.MALL_USER_SESSION_KEY);
    //设置购物车中的数量
    newBeeMallUserVO.setShopCartItemCount(shoppingCartItemMapper.selectCountByUserId(newBeeMallUserVO.getUserId()));
    request.getSession().setAttribute(Constants.MALL_USER_SESSION_KEY, newBeeMallUserVO);
    }
    return true;
    }
  • MallLoginInterceptor
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
    if (null == request.getSession().getAttribute(Constants.MALL_USER_SESSION_KEY)) {
    response.sendRedirect(request.getContextPath() + "/login");
    return false;
    } else {
    return true;
    }
    }
    Session中无信息就跳转到login

controller

截屏2025-03-15 18.08.10.png
先去测前台用户的

就先审计mall目录下的文件

水平越权访问

审计到UserController中存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    @ApiOperation(value = "修改收货地址")  
@PostMapping("/personal/updateInfo")
@ResponseBody
public Result updateInfo(@RequestBody User mallUser, HttpSession httpSession) {
UserVO mallUserTemp = userService.updateUserInfo(mallUser,httpSession);
if (mallUserTemp == null) {
Result result = ResultGenerator.genFailResult("修改失败");
return result;
} else {
//返回成功
Result result = ResultGenerator.genSuccessResult();
return result;
}
}
}

修改个人信息存在updateinfo
打断点跟进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public UserVO updateUserInfo(User mallUser, HttpSession httpSession) {  
UserVO userTemp = (UserVO) httpSession.getAttribute(Constants.MALL_USER_SESSION_KEY);
User userFromDB = userMapper.selectByPrimaryKey(userTemp.getUserId());
if (userFromDB != null) {
if (!StringUtils.isEmpty(mallUser.getNickName())) {
userFromDB.setNickName(MallUtils.cleanString(mallUser.getNickName()));
}
if (!StringUtils.isEmpty(mallUser.getAddress())) {
userFromDB.setAddress(MallUtils.cleanString(mallUser.getAddress()));
}
if (!StringUtils.isEmpty(mallUser.getIntroduceSign())) {
userFromDB.setIntroduceSign(MallUtils.cleanString(mallUser.getIntroduceSign()));
}
if (userMapper.updateByPrimaryKeySelective(userFromDB) > 0) {
UserVO newBeeMallUserVO = new UserVO();
userFromDB = userMapper.selectByPrimaryKey(mallUser.getUserId());
BeanUtil.copyProperties(userFromDB, newBeeMallUserVO);
httpSession.setAttribute(Constants.MALL_USER_SESSION_KEY, newBeeMallUserVO);
return newBeeMallUserVO;
}
}
return null;
}
1
User userFromDB = userMapper.selectByPrimaryKey(userTemp.getUserId());

这里存在直接getUserId,通过抓包,发现UserId是明文传输,可以通过修改直接达到水平越权修改其他用户的信息

0元购

在OrderController中存在支付逻辑,查看思考是否存在支付逻辑漏洞

1
2
3
4
5
6
7
8
9
10
11
@ApiOperation(value = "支付成功")  
@GetMapping("/paySuccess")
@ResponseBody
public Result paySuccess(@RequestParam("orderNo") String orderNo, @RequestParam("payType") int payType) {
String payResult = orderService.paySuccess(orderNo, payType);
if (ServiceResultEnum.SUCCESS.getResult().equals(payResult)) {
return ResultGenerator.genSuccessResult();
} else {
return ResultGenerator.genFailResult(payResult);
}
}

支付成功的逻辑是访问/paySuccess,然后就会有orderNo和payType参数,如果将未支付的单号orderNo当作值就会直接直接跳转到支付成功的界面,返回正常界面后发现已支付

越权查询订单

在OrderController中,存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  
@ApiOperation(value = "根据订单号查询订单")
@GetMapping("/orders/{orderNo}")
public String orderDetailPage(HttpServletRequest request,
@PathVariable("orderNo") String orderNo,
HttpSession httpSession) {
UserVO user = (UserVO) httpSession.getAttribute(Constants.MALL_USER_SESSION_KEY);
OrderDetailVO orderDetailVO = orderService.getOrderDetailByOrderNo(orderNo, user.getUserId());
if (orderDetailVO == null) {
return "error/error_5xx";
}
request.setAttribute("orderDetailVO", orderDetailVO);
return "mall/order-detail";
}

其中访问订单是/orders/{orderNo},并没有进行订单匹配用户,如果使用其他用户的订单号,就会存在越权访问到其他用户的单号
截屏2025-03-15 18.50.39.png

前台我查看到的内容差不多有问题的就这些,明显发现该系统严重缺乏鉴权意识

接下来查看后台是否能getshell

后台访问 /admin
账号密码 admin/111111

后台已经不用审计,直接测,有商品图片任意文件上传,和商品简介xss,但是前台访问简介xss代码并没有执行,也找不到其他可以进一步的内容了(没办法解析上传的文件),其实涉及危害并不大