java代码审计之常见漏洞学习
java代码审计之常见漏洞学习
原创 Z0安全 Z0安全 2025-05-12 06:24
前言
Java代码审计中常见的一些漏洞学习总结以及一些审计思路。
1. SQL注入:数据库的隐形杀手
成因
未过滤的用户输入直接拼接至SQL语句,导致恶意SQL执行。
高危场景
-
• MyBatis:使用${}
动态拼接参数(如like ‘%${title}%’
) -
• Hibernate:createQuery
拼接字符串 -
• 原生JDBC:字符串拼接SQL语句
错误示例
// MyBatis错误示例@Select("SELECT * FROM user WHERE username = '${username}'")User getUserByUsername(String username);// JDBC错误示例String sql = "SELECT * FROM product WHERE price > " + price;Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery(sql);
审计技巧
-
• 搜索输入拼接符号:全局搜索+
、concat()
、${}
、append() -
• 检查动态排序:MyBatis中是否存在order by ${time}
-
• 框架特性:Hibernate的createQuery
是否使用参数绑定
修复方案
-
• 强制使用预编译:MyBatis中优先使用#{}
,JDBC中使用PreparedStatement -
• 输入校验:对参数进行正则过滤(如仅允许数字)
正确示例
// MyBatis正确示例@Select("SELECT * FROM user WHERE username = #{username}")User getUserByUsername(String username);// JDBC正确示例String sql = "SELECT * FROM product WHERE price > ?";PreparedStatement stmt = conn.prepareStatement(sql);stmt.setDouble(1, price);
2. XSS:前端漏洞,后端之责
成因
未对用户输入输出进行过滤,导致恶意脚本执行。
高危代码示例
@GetMapping("/search")public String search(@RequestParam String keyword, Model model) { model.addAttribute("keyword", keyword); // 直接输出未过滤 return "result";}
审计技巧
-
• 输入点追踪:搜索request.getParameter()
、@RequestParam -
• 输出点检查:检查JSP/Thymeleaf模板中是否直接输出变量(如${keyword}
) -
• 过滤机制:确认是否使用StringEscapeUtils.escapeHtml()
或全局过滤器
修复方案
-
• 全局XSS过滤器:配置web.xml
过滤所有请求 -
• 输出编码:使用StringEscapeUtils.escapeHtml4()
对输出内容转义
过滤器配置示例
<filter> <filter-name>XSSFilter</filter-name> <filter-class>com.example.XSSFilter</filter-class></filter><filter-mapping> <filter-name>XSSFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping>
3. XXE:XML解析的致命陷阱
成因
允许解析外部实体,导致文件读取、SSRF等风险。
高危接口示例
// 使用DOM4J解析XML(未禁用DTD)SAXReader reader = new SAXReader();Document doc = reader.read(new File("user.xml"));
审计技巧
-
• 搜索任务XML解析器:全局搜索DocumentBuilder
、SAXReader -
• 检查安全配置:确认是否禁用DTD和外部实体
修复方案
-
• 禁用外部实体:通过设置FEATURE_SECURE_PROCESSING
-
• 使用安全库:如Jackson的XmlMapper
默认禁用外部实体
正确配置示例
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
4. SSRF:内网渗透的跳板
成因
未校验请求URL,攻击者可访问内网资源。
高危代码示例
URL url = new URL(request.getParameter("url"));HttpURLConnection conn = (HttpURLConnection) url.openConnection();
审计技巧
-
• 搜索文件网络请求方法:全局搜索URL.openConnection()
、HttpClient.execute() -
• 协议限制检查:确认是否禁止file://
、ftp://
等协议
修复方案
- • 域名/IP白名单:允许仅访问指定域名
校验域名示例
if (!url.getHost().endsWith(".example.com")) { throw new SecurityException("非法域名");}
5. 文件操作漏洞:权限失控之痛
成因
未校验文件路径,导致任意文件读写。
高危场景示例
File file = new File("/uploads/" + userInput);InputStream is = new FileInputStream(file);
审计技巧
-
• 搜索图片文件IO类:全局搜索FileInputStream
、FileOutputStream -
• 路径拼接检查:确认是否使用getCanonicalPath()
规范化路径
修复方案
- • 限制文件目录:仅允许访问固定根目录下的文件
安全路径拼接示例
File baseDir = new File("/safe/uploads/");File targetFile = new File(baseDir, userInput);if (!targetFile.getCanonicalPath().startsWith(baseDir.getCanonicalPath())) { throw new SecurityException("非法路径");}
6. 命令执行:系统级的高危操作
成因
用户输入直接拼接至系统命令。
高危代码示例
Process process = Runtime.getRuntime().exec("ping " + userInput);
审计技巧
-
• 搜索和命令执行函数:全局搜索Runtime.exec()
、ProcessBuilder.start() -
• 特殊符号过滤:检查是否过滤&
、;
、|
修复方案
- • 参数白名单:仅允许特定参数格式
正则过滤示例
if (!Pattern.matches("^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$", userInput)) { throw new SecurityException("非法IP");}
7. 反序列化:隐形代码执行
成因
反序列化不可信数据导致恶意代码执行。
高危接口示例
// Fastjson反序列化User user = JSON.parseObject(jsonStr, User.class);
审计技巧
-
• 搜索功能反序列化方法:全局搜索ObjectInputStream.readObject()
、JSON.parseObject() -
• 依赖版本检查:确认Fastjson、XStream是否使用安全版本
修复方案
-
• 升级依赖:Fastjson ≥1.2.83,XStream ≥1.4.19
-
• 启用安全模式:Fastjson使用Feature.SupportNonPublicField
8. 中间件漏洞:被忽视的底层风险
成因
使用存在已知漏洞的中间件版本。
常见漏洞
-
• Apache Shiro:反序列化漏洞(默认密钥)
-
• WebLogic:SSRF、反序列化漏洞(CVE-2020-2551)
审计技巧
-
• 检查依赖版本:通过pom.xml
确认中间件版本 -
• CVE匹配:比对已知漏洞库(如NVD)
修复方案
-
• 更新中间件:Shiro ≥1.8.0,WebLogic ≥12.2.1.4
-
• 删除无用组件:移除未使用的Struts2、Log4j 1.x
9. 业务逻辑漏洞:越权与越界
成因
未校验用户权限,导致越权访问。
典型案例
-
• 垂直越权:普通用户访问管理员接口
-
• 水平越权:用户A查看用户B的数据
审计技巧
- • 参数校验:检查接口是否验证当前用户身份(如userId
是否属于登录用户)
错误示例
@GetMapping("/user/{id}")public User getUser(@PathVariable Long id) { return userService.findById(id); // 攻击者可传入他人ID}
正确示例
@GetMapping("/user/{id}")public User getUser(@PathVariable Long id, HttpSession session) { Long currentUserId = (Long) session.getAttribute("userId"); if (!currentUserId.equals(id)) { throw new AccessDeniedException(); } return userService.findById(id);}
修复方案
-
• 强制权限校验:在Service层校验用户身份
-
• 使用框架:Spring Security配置角色权限
10. 不安全的反序列化:数据背后的毒蛇
成因
反序列化不可信数据导致远程代码执行。
高危场景
-
• HTTP接口反序列化:接收外部JSON/XML数据并反序列化
-
• 日志存储反序列化:从文件或数据库加载序列化对象
审计技巧
-
• 黑盒测试:发送恶意序列化数据(ysoserial工具)
-
• 代码检查:确认反序列化前是否校验数据来源
修复方案
-
• 输入白名单:仅允许反序列化已知类
-
• 使用JSON:避免Java原生反序列化,改用Jackson/Gson
11. 文件包含与路径遍历:目录下的幽灵
成因
未校验文件路径参数,导致包含或读取敏感文件。
高危代码示例
// 文件包含漏洞include(request.getParameter("page")); // 攻击者传入../../etc/passwd
审计技巧
-
• 搜索文件包含函数:全局搜索RequestDispatcher.include()
、include() -
• 路径规范化检查:确认是否使用getCanonicalPath()
修复方案
- • 白名单控制:仅允许包含固定文件
白名单校验示例
List<String> allowedPages = Arrays.asList("home.jsp", "profile.jsp");if (!allowedPages.contains(pageParam)) { throw new SecurityException("非法页面");}
12. 防御之道:安全开发全流程
代码层
-
• 使用ESAPI进行输入输出编码
-
• 集成静态代码分析工具(SonarQube、Checkstyle)
测试层
-
• 使用SAST工具(Checkmarx)扫描代码漏洞
-
• 结合DAST工具(Burp Suite)进行渗透测试
运维层
-
• 配置WAF规则拦截常见攻击(如SQL注入、XSS)
-
• 定期更新中间件和依赖库
结语
:安全是开发的生命线。通过系统化的代码审计、严格的权限校验、及时的依赖更新,开发者能够将风险降至最低。记住:漏洞不会消失,但可以通过严谨的态度将其扼杀在摇篮中!