深度解析:Spring MVC代码审计实战
字数 1858 2025-12-04 12:16:54

Spring MVC代码审计实战教学文档

1. Spring MVC框架基础

1.1 Spring Controller注解详解

@Controller

  • 作用:标识类为Spring MVC控制器,主要用于页面跳转
  • 示例
@Controller
public class GoodsController {
    @Resource
    private NewBeeMallGoodsService newBeeMallGoodsService;
    
    @GetMapping({"/search", "/search.html"})
    public String searchPage(@RequestParam Map<String, Object> params, HttpServletRequest request) {
        if (StringUtils.isEmpty(params.get("page"))) {
            params.put("page", 1);
        }
        // 业务逻辑
    }
}

@RestController

  • 作用:@Controller + @ResponseBody组合注解,用于API接口开发
  • 示例
@RestController
public class ApiUserController {
    @GetMapping("/api/user/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
}

@RepositoryRestController

  • 作用:扩展Spring Data REST自动生成的REST接口
  • 示例
@RepositoryRestController
public class CustomProductController {
    @GetMapping("/products/search/byName")
    public ResponseEntity<?> searchByName(@RequestParam String name) {
        // 自定义逻辑
    }
}

1.2 Spring MVC请求映射注解

注解 HTTP方法 说明
@RequestMapping 任意(可指定) 通用映射注解
@GetMapping GET @RequestMapping(method = GET)的快捷方式
@PostMapping POST 提交数据(表单、JSON)
@PutMapping PUT 全量更新资源
@DeleteMapping DELETE 删除资源
@PatchMapping PATCH 部分更新资源

2. 代码审计方法论

2.1 审计思路

  1. 识别开发框架:确认项目基于Spring MVC框架
  2. 定位API接口:通过Controller类查找所有路由
  3. 参数跟踪:从接口接收参数开始,跟踪参数流转路径
  4. 漏洞识别:分析每个代码逻辑点的安全风险
  5. 权限验证:区分前后台路由,检查鉴权逻辑

2.2 关键关注点

  • 前台路由:直接面向用户访问的接口
  • 后台路由:通常位于admin目录下,需要重点检查鉴权
  • 参数处理:重点关注用户输入的数据处理逻辑

3. 常见漏洞类型及审计方法

3.1 权限绕过漏洞

漏洞原理

使用request.getRequestURI()方法进行路径判断时,攻击者可通过特殊字符绕过权限检查。

漏洞代码示例

// 漏洞代码 - 使用getRequestURI()进行权限判断
if (url.startsWith("/admin") && session.getAttribute("loginUser") == null) {
    // 要求登录
}

绕过方法

  • 使用分号;/admin;/index
  • 使用正斜杠//admin//index

修复方案

使用规范化路径进行比较:

String path = request.getRequestURI().replaceAll("/+", "/");
if (path.startsWith("/admin") && session.getAttribute("loginUser") == null) {
    // 要求登录
}

3.2 任意文件上传漏洞

漏洞特征

  • 未对文件类型进行校验
  • 文件保存路径可控
  • 后台路由但权限可绕过

漏洞代码示例

@PostMapping("/admin/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
    String originalFileName = file.getOriginalFilename(); // 获取原始文件名
    String suffix = originalFileName.substring(originalFileName.lastIndexOf(".")); // 提取后缀
    String newFileName = getNewFileName(suffix); // 生成新文件名
    // 直接保存文件,未做类型检查
    file.transferTo(new File(Constants.FILE_UPLOAD_DIC + newFileName));
    return "文件访问路径";
}

审计要点

  1. 查找文件上传相关路由(如包含upload、file等关键词)
  2. 检查文件类型验证逻辑
  3. 验证权限控制是否完备

3.3 支付逻辑漏洞

漏洞原理

支付成功状态仅凭用户传入参数判断,未验证支付真实性。

漏洞代码示例

@GetMapping("/paySuccess")
public String paySuccess(@RequestParam String orderNo) {
    // 直接根据订单号标记支付成功,未验证支付真实性
    payService.paySuccess(orderNo);
    return "支付成功";
}

服务层漏洞代码

public void paySuccess(String orderNo) {
    Order order = orderMapper.selectByOrderNo(orderNo);
    if (order.getOrderStatus() == OrderStatusEnum.ORDER_PRE_PAY.getOrderStatus()) {
        // 直接标记为已支付,无验证逻辑
        order.setOrderStatus(OrderStatusEnum.ORDER_PAID.getOrderStatus());
        orderMapper.updateByPrimaryKey(order);
    }
}

修复方案

  • 验证支付渠道回调签名
  • 查询第三方支付平台确认支付状态
  • 记录完整的支付流水信息

3.4 越权漏洞

3.4.1 水平越权(更新个人信息)

漏洞代码
@PostMapping("/user/update")
public Result updateUserInfo(@RequestBody MallUser mallUser, HttpSession session) {
    MallUser user = userService.getUserById(mallUser.getUserId()); // 根据传入ID查询用户
    if (user != null) {
        // 未验证当前登录用户是否有权修改此用户信息
        user.setNickName(mallUser.getNickName());
        user.setIntroduceSign(mallUser.getIntroduceSign());
        userMapper.updateByPrimaryKeySelective(user);
    }
    return ResultGenerator.genSuccessResult();
}
修复方案
@PostMapping("/user/update")
public Result updateUserInfo(@RequestBody MallUser mallUser, HttpSession session) {
    Long loginUserId = (Long) session.getAttribute("loginUserId");
    if (!loginUserId.equals(mallUser.getUserId())) {
        return ResultGenerator.genFailResult("无权限修改此用户信息");
    }
    // 安全更新逻辑
}

3.4.2 水平越权(删除订单)

漏洞代码
@DeleteMapping("/shop-cart/{newBeeMallShoppingCartItemId}")
public Result deleteItem(@PathVariable("newBeeMallShoppingCartItemId") Long itemId) {
    // 直接根据ID删除,未验证该订单是否属于当前用户
    Boolean result = newBeeMallShoppingCartService.deleteById(itemId);
    return ResultGenerator.genSuccessResult();
}
修复方案
@DeleteMapping("/shop-cart/{newBeeMallShoppingCartItemId}")
public Result deleteItem(@PathVariable("newBeeMallShoppingCartItemId") Long itemId, HttpSession session) {
    Long userId = (Long) session.getAttribute("loginUserId");
    // 验证订单归属
    if (!shoppingCartService.isItemBelongToUser(itemId, userId)) {
        return ResultGenerator.genFailResult("无权限删除此订单");
    }
    Boolean result = newBeeMallShoppingCartService.deleteById(itemId);
    return ResultGenerator.genSuccessResult();
}

3.5 SQL注入漏洞

3.5.1 MyBatis中#{}和${}的区别

占位符 安全性 处理方式
#{} 安全 预编译参数,防止SQL注入
${} 危险 直接拼接SQL,存在注入风险

3.5.2 漏洞代码示例

漏洞Mapper XML
<!-- NewBeeMallGoodsMapper.xml -->
<select id="getNewBeeMallGoodsPage" parameterType="Map" resultMap="BaseResultMap">
    SELECT * FROM tb_newbee_mall_goods
    WHERE 
        <if test="goodsName != null and goodsName != ''">
            goods_name = '${goodsName}'  <!-- 直接拼接,存在SQL注入 -->
        </if>
</select>
对应的Mapper接口
public interface NewBeeMallGoodsMapper {
    List<NewBeeMallGoods> getNewBeeMallGoodsPage(Map<String, Object> params);
}
Controller层调用
@Controller
@RequestMapping("/admin")
public class GoodsController {
    
    @GetMapping("/goods/list")
    public String list(@RequestParam Map<String, Object> params) {
        // 直接接收所有URL参数
        PageQueryUtil pageUtil = new PageQueryUtil(params);
        List<NewBeeMallGoods> goodsList = goodsService.getNewBeeMallGoodsPage(pageUtil);
        return "admin/goods";
    }
}

3.5.3 另一处SQL注入示例

搜索功能漏洞
<!-- 搜索功能的Mapper -->
<select id="findGoodsListBySearch" parameterType="Map" resultMap="BaseResultMap">
    SELECT * FROM goods 
    WHERE 
        <if test="keyword != null and keyword != ''">
            (goods_name LIKE '%${keyword}%' OR goods_intro LIKE '%${keyword}%')
        </if>
</select>
前台搜索Controller
@GetMapping({"/search", "/search.html"})
public String searchPage(@RequestParam Map<String, Object> params, HttpServletRequest request) {
    String keyword = (String) params.get("keyword");
    if (StringUtils.isNotEmpty(keyword)) {
        keyword = keyword.trim();
    }
    // 参数直接传递给Service层,存在SQL注入风险
    PageQueryUtil pageUtil = new PageQueryUtil(params);
    model.addAttribute("pageResult", goodsService.searchNewBeeMallGoods(pageUtil));
    return "mall/search";
}

3.5.4 SQL注入修复方案

方案1:使用#{}预编译
<select id="getNewBeeMallGoodsPage" parameterType="Map" resultMap="BaseResultMap">
    SELECT * FROM tb_newbee_mall_goods
    WHERE 
        <if test="goodsName != null and goodsName != ''">
            goods_name = #{goodsName}  <!-- 使用预编译 -->
        </if>
</select>
方案2:业务层过滤
public List<NewBeeMallGoods> getNewBeeMallGoodsPage(Map<String, Object> params) {
    // 对参数进行安全过滤
    String goodsName = (String) params.get("goodsName");
    if (goodsName != null) {
        goodsName = SqlFilter.filter(goodsName); // 自定义SQL过滤函数
        params.put("goodsName", goodsName);
    }
    return goodsMapper.getNewBeeMallGoodsPage(params);
}

4. 审计工具和技巧

4.1 代码搜索关键词

  • ${:查找MyBatis中危险的SQL拼接
  • getRequestURI():查找权限绕过风险点
  • upload/file:查找文件上传功能
  • admin:定位后台管理功能
  • pay/order:查找支付相关逻辑

4.2 依赖检查

检查pom.xml文件,识别使用的第三方组件版本,查找已知漏洞:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version> <!-- 检查版本安全性 -->
</dependency>

5. 总结

Spring MVC代码审计需要系统性地分析框架特性、路由映射、参数处理和权限控制。重点关注:

  1. 权限控制:前后台路由区分、会话管理、访问控制
  2. 输入验证:参数过滤、SQL注入防护、文件类型检查
  3. 业务逻辑:支付流程、订单处理、用户数据操作
  4. 框架配置:安全配置、组件版本、依赖管理

通过系统化的审计方法,可以有效发现和修复Spring MVC应用中的安全漏洞。

Spring MVC代码审计实战教学文档 1. Spring MVC框架基础 1.1 Spring Controller注解详解 @Controller 作用 :标识类为Spring MVC控制器,主要用于页面跳转 示例 : @RestController 作用 :@Controller + @ResponseBody组合注解,用于API接口开发 示例 : @RepositoryRestController 作用 :扩展Spring Data REST自动生成的REST接口 示例 : 1.2 Spring MVC请求映射注解 | 注解 | HTTP方法 | 说明 | |------|----------|------| | @RequestMapping | 任意(可指定) | 通用映射注解 | | @GetMapping | GET | @RequestMapping(method = GET)的快捷方式 | | @PostMapping | POST | 提交数据(表单、JSON) | | @PutMapping | PUT | 全量更新资源 | | @DeleteMapping | DELETE | 删除资源 | | @PatchMapping | PATCH | 部分更新资源 | 2. 代码审计方法论 2.1 审计思路 识别开发框架 :确认项目基于Spring MVC框架 定位API接口 :通过Controller类查找所有路由 参数跟踪 :从接口接收参数开始,跟踪参数流转路径 漏洞识别 :分析每个代码逻辑点的安全风险 权限验证 :区分前后台路由,检查鉴权逻辑 2.2 关键关注点 前台路由:直接面向用户访问的接口 后台路由:通常位于admin目录下,需要重点检查鉴权 参数处理:重点关注用户输入的数据处理逻辑 3. 常见漏洞类型及审计方法 3.1 权限绕过漏洞 漏洞原理 使用 request.getRequestURI() 方法进行路径判断时,攻击者可通过特殊字符绕过权限检查。 漏洞代码示例 绕过方法 使用分号 ; : /admin;/index 使用正斜杠 / : /admin//index 修复方案 使用规范化路径进行比较: 3.2 任意文件上传漏洞 漏洞特征 未对文件类型进行校验 文件保存路径可控 后台路由但权限可绕过 漏洞代码示例 审计要点 查找文件上传相关路由(如包含upload、file等关键词) 检查文件类型验证逻辑 验证权限控制是否完备 3.3 支付逻辑漏洞 漏洞原理 支付成功状态仅凭用户传入参数判断,未验证支付真实性。 漏洞代码示例 服务层漏洞代码 修复方案 验证支付渠道回调签名 查询第三方支付平台确认支付状态 记录完整的支付流水信息 3.4 越权漏洞 3.4.1 水平越权(更新个人信息) 漏洞代码 修复方案 3.4.2 水平越权(删除订单) 漏洞代码 修复方案 3.5 SQL注入漏洞 3.5.1 MyBatis中#{}和${}的区别 | 占位符 | 安全性 | 处理方式 | |--------|--------|----------| | #{} | 安全 | 预编译参数,防止SQL注入 | | ${} | 危险 | 直接拼接SQL,存在注入风险 | 3.5.2 漏洞代码示例 漏洞Mapper XML 对应的Mapper接口 Controller层调用 3.5.3 另一处SQL注入示例 搜索功能漏洞 前台搜索Controller 3.5.4 SQL注入修复方案 方案1:使用#{}预编译 方案2:业务层过滤 4. 审计工具和技巧 4.1 代码搜索关键词 ${ :查找MyBatis中危险的SQL拼接 getRequestURI() :查找权限绕过风险点 upload / file :查找文件上传功能 admin :定位后台管理功能 pay / order :查找支付相关逻辑 4.2 依赖检查 检查pom.xml文件,识别使用的第三方组件版本,查找已知漏洞: 5. 总结 Spring MVC代码审计需要系统性地分析框架特性、路由映射、参数处理和权限控制。重点关注: 权限控制 :前后台路由区分、会话管理、访问控制 输入验证 :参数过滤、SQL注入防护、文件类型检查 业务逻辑 :支付流程、订单处理、用户数据操作 框架配置 :安全配置、组件版本、依赖管理 通过系统化的审计方法,可以有效发现和修复Spring MVC应用中的安全漏洞。