java 代码审计 - SSRF
SSRF(Server-Side Request Forgery,服务器端请求伪造)是一种由攻击者构造形成由服务器端发起请求的安全漏洞。攻击者能够利用受影响的应用程序,发送伪造的 HTTP 请求,使其伪装成服务器内部发起的请求,从而能够直接或间接地访问或控制应用程序无意中暴漏的受保护资源。
Web 应用程序往往会提供一些能够从远程获取图片或是文件的接口,在这些接口上用户使用指定的 URL 便能完成远程获取图片、下载文件等操作。攻击者可以通过使用file协议来读取服务器本地/etc/passwd和/proc/self/cmdline等敏感文件,同时攻击者也可以利用被攻击的服务器绕过防火墙直接对处于内网的机器发起进一步的攻击。
原理
SSRF 漏洞的本质是服务端提供了从其他应用服务器获取数据的功能,但没有对目标地址做过滤与限制。攻击者通过向应用程序发送特定的请求,传入一个用户可控的参数作为目标 URL 或者 IP 地址,进行实现对受害系统内部任意资源的访问。
支持的协议
在 Java(JDK21)中,SSRF 支持sun.net.www.protocol下所有的协议:file、ftp、http、https、jar、jmod、jrt及mailto协议。这些是 JDK 内置的、通过 URL 类直接支持的标准协议,但在生产代码中应始终使用标准的java.netAPI,避免直接依赖sun.*包。

由于上述协议的限制,以及传入的 URL 协议必须和重定向后的 URL 协议一致的原因,使得 Java 中的 SSRF 并不能像 PHP 中一样使用gopher协议来拓展攻击面。
- file - 本地文件系统协议
- URL 示例:file:///etc/passwd
- 作用:用于访问本地文件系统中的文件。
- ftp - 文件传输协议
- URL 示例:ftp://user:[email protected]/pub/file.txt
- 作用:通过 FTP 协议下载或上传文件。
- http - 超文本传输协议
- URL 示例:http://example.com/index.html
- 作用:获取 Web 资源。
- https - 安全超文本传输协议
- URL 示例:https://example.com/api/user
- 作用:获取 Web 资源。
- jar - JAR 文件协议
- URL 示例:jar:file:///app/lib/utils.jar!/config.properties
- 作用:访问 JAR 包内部的资源文件。
- jmod - JMOD 文件协议(JDK 9+)
- URL 示例:jmod:/path/to/module.jmod!/resources/file.txt
- 作用:用于访问 .jmod 文件中的内容。
- jrt - 运行时镜像协议(JDK 9+)
- URL 示例:jrt:/java.base/java/lang/Object.class
- 作用:访问 运行时模块镜像(Run-Time Image) 中的类和资源。
- mailto - 邮件协议
- URL 示例:mailto:[email protected]?subject=Hello&body=Hi!
- 作用:启动默认邮件客户端,预填收件人、主题、正文等。
常见发起网络请求,并产生 SSRF 漏洞写法
urlConnection
urlConnection类是 Java 中用于发起网络请求的基础类,它是一个抽象类,通过java.net.URL类的openConnection()方法来获取其实例。表示指向 URL 指定资源的活动链接,它有两个直接子类,分别是HttpURLConnection和JarURLConnection。在默认情况下,urlConnection的参数没有有效控制时会引起 SSRF 漏洞。
package com.ssrf.vuln.ssrfdemo.Servlet; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; @WebServlet(name = "Ssrf1Servlet", urlPatterns = "/ssrf1") public class Ssrf1Servlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html; charset=utf-8"); String urlString = req.getParameter("url"); if (urlString == null || urlString.isEmpty()) { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); resp.getWriter().write("URL 参数不能为空"); return; } try { // 1. 创建 URL 对象 URL url = new URL(urlString); // 2. 打开连接,获取 URLConnection 对象 URLConnection urlConnection = url.openConnection(); // 3. 读取响应 BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8")); StringBuilder content = new StringBuilder(); String line; while ((line = in.readLine()) != null) { content.append(line); } in.close(); resp.getWriter().write(content.toString()); } catch (IOException e) { resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); resp.getWriter().write("无法访问指定的URL"); } } } 

HttpURLConnection
HttpURLConnection是java.net包中的一个类,它继承自URLConnection类,专门用于处理 HTTP 协议的连接。
它可以发送 GET 请求与 POST 请求。同样的,在没有过滤的默认情况下其会产生 SSRF 漏洞。
package com.ssrf.vuln.ssrfdemo.Servlet; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; @WebServlet(name = "Ssrf2Servlet", urlPatterns = "/ssrf2") public class Ssrf2Servlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html; charset=utf-8"); String urlString = req.getParameter("url"); if (urlString == null || urlString.isEmpty()) { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); resp.getWriter().write("URL 参数不能为空"); return; } try { // 创建 URL 对象 URL url = new URL(urlString); // 打开连接,并转换为 HttpURLConnection HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); // 设置请求方法 urlConnection.setRequestMethod("GET"); // 设置连接超时时间 urlConnection.setConnectTimeout(5000); // 设置读取超时时间 urlConnection.setReadTimeout(5000); // 检查响应码 int responseCode = urlConnection.getResponseCode(); if (responseCode != HttpURLConnection.HTTP_OK) { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); resp.getWriter().write("无法访问指定的URL,响应码:" + responseCode); return; } // 读取数据 BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); StringBuilder content = new StringBuilder(); String inputLine; while ((inputLine = in.readLine()) != null) { content.append(inputLine); } in.close(); // 返回实际内容 resp.getWriter().write(content.toString()); } catch (Exception e) { // 捕获异常 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); resp.getWriter().write("发生错误: " + e.getMessage()); } } } Apache HttpClient - Request
Request是是一个基于 Apache HttpClient 的高级封装,提供了更简洁、流畅的 API 来发起 HTTP 请求。在没有过滤的默认情况下会产生 SSRF 漏洞。
<dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5-fluent</artifactId> <version>5.5.1</version> </dependency> package com.ssrf.vuln.ssrfdemo.Servlet; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.hc.client5.http.fluent.Content; import org.apache.hc.client5.http.fluent.Request; import java.io.IOException; @WebServlet(name = "Ssrf3Servlet", urlPatterns = "/ssrf3") public class Ssrf3Servlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { String urlParam = req.getParameter("url"); // 创建请求 Request request = Request.get(urlParam); // 执行请求 Content content = request.execute().returnContent(); String responseBody = content.asString(); // 输出响应 resp.getWriter().write(responseBody); } } 
HttpClient
HttpClient 是 Apache Jakarta Common 下的一个流行的开源 HTTP 客户端库,它不仅支持 HTTP 协议,还支持 HTTPS、代理、Cookie 管理、身份验证等功能。在默认情况下,其也会产生 SSRF 漏洞。
package com.ssrf.vuln.ssrfdemo.Servlet; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.classic.methods.HttpGet; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @WebServlet(name = "Ssrf4Servlet", urlPatterns = "/ssrf4") public class Ssrf4Servlet extends HttpServlet { private static final CloseableHttpClient httpClient = HttpClients.createDefault(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { String urlParam = req.getParameter("url"); // 创建请求 HttpGet hg = new HttpGet(urlParam); // 执行请求 CloseableHttpResponse hResp = httpClient.execute(hg); // 读取响应 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(hResp.getEntity().getContent())); String line; StringBuilder stringBuilder = new StringBuilder(); while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line); } // 输出响应 resp.setContentType("text/html;charset=utf-8"); resp.getWriter().write(stringBuilder.toString()); } } URL/openStream/ImageIO
java.net.URL包是 Java 标准库中用于处理 URL 的类和接口的包。URL 是用于标识和定位资源的字符串,通常包括协议(如HTTP、FTP 等)和资源的位置信息(如域名或 IP 地址、端口、路径等)。java.net.URL包提供了创建、解析、查询和操作 URL 的类和方法。
通过 URL 对象的openStream()方法,能够得到指定资源的输入流。这时如果 URL 对象可控,则会产生 SSRF 漏洞。
ImageIO是 Java 标准库javax.imageio包中的一个核心类,它提供了一组静态方法,用于执行图像的读取、写入、获取格式信息等基本 I/O 操作。
package com.ssrf.vuln.ssrfdemo.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.imageio.ImageIO; import java.awt.*; import java.io.BufferedReader; import java.io.IOException; import java.net.URL; @RestController public class UrlController { @GetMapping("/ssrf5") public String UrlOpenStream(@RequestParam String url) throws IOException { URL u = new URL(url); BufferedReader in = new BufferedReader(new java.io.InputStreamReader(u.openStream())); String inputLine; StringBuilder r = new StringBuilder(); while ((inputLine = in.readLine()) != null) { r.append(inputLine); } in.close(); return r.toString(); } @GetMapping("/ssrf6") public String UrlImageIO(@RequestParam String url) throws IOException { URL u = new URL(url); Image image = ImageIO.read(u); return image.toString(); } } 
源码审计关键词
SSRF 漏洞 URL 中常出现url、f、file、page 等参数。程序会发起 HTTP 请求获取远程资源、分享、收藏等操作,因此代码审计时要特别留意能够发起 HTTP 请求的类及函数。:
- HttpClient.execute
- HttpClient.executeMethod
- HttpURLConnection.connect
- HttpURLConnection.getInputStream
- URL.openStream
- URLConnection.getInputStream
- Request.Get.execute
- Request.Post.execute
- ImageIO.read
- OkHttpClient.newCall.execute
- HttpServletRequest
- BasicHttpRequest
- HttpURLConnection.getInputStream
Solr SSRF 漏洞审计
漏洞背景简述
- 漏洞编号:CVE-2021-27905
- 影响版本:7.0.0 ~ 7.7.3, 8.0.0 ~ 8.8.1
- solr 下载:https://archive.apache.org/dist/lucene/solr/