SSRF突破对file协议的限制
字数 986 2025-08-19 12:42:18
SSRF绕过file协议限制的技术分析与防御方案
0x01 漏洞概述
本文介绍了一种在Java环境中绕过SSRF(Server-Side Request Forgery)防护中file协议黑名单限制的技术。该漏洞允许攻击者通过特殊构造的URL绕过"file://"前缀检查,从而读取服务器本地文件系统。
0x02 漏洞环境
测试环境配置
- JDK版本:11.0.11
- 示例代码:
@RequestMapping("/ssrf")
public String ssrf(String url) {
String urlContent = "";
try {
// 检查URL是否以"file://"开头
if (url.startsWith("file://")) {
return "URL can't start with 'file://'";
}
// 读取URL内容
URL u = new URL(url);
BufferedReader in = new BufferedReader(new InputStreamReader(u.openStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
urlContent += inputLine + "\n";
}
in.close();
} catch (Exception e) {
e.printStackTrace();
}
return urlContent;
}
0x03 漏洞复现
攻击向量
- 直接使用
file://协议会被黑名单拦截 - 绕过POC:
url:file:///D://flag
攻击效果
成功绕过黑名单检查并读取服务器本地文件
0x04 技术原理分析
关键绕过点
漏洞存在于Java的URL类构造函数处理逻辑中:
new URL()构造函数会解析传入的URL字符串- 在
java.net.URL#URL(java.net.URL, java.lang.String, java.net.URLStreamHandler)方法中:- 593行代码检查URL字符串是否以"url:"开头
- 匹配成功后,start变量增加4(从"url:"后开始解析)
- 603行截取从start位置到字符串结尾或下一个'/'字符
- 最终将
url:file://xxx解析为file协议
解析流程
输入: url:file:///D://flag
解析过程:
1. 识别"url:"前缀
2. 跳过前4个字符
3. 剩余部分"file:///D://flag"被识别为file协议URL
0x05 防御方案
不安全的防护方式
- 简单的黑名单过滤(如示例中的
startsWith("file://")检查) - 仅检查特定前缀,不考虑URL解析的复杂性
推荐防护措施
-
协议白名单:
- 仅允许HTTP/HTTPS协议
- 示例代码:
if (!url.startsWith("http://") && !url.startsWith("https://")) { return "Only HTTP/HTTPS URLs are allowed"; }
-
使用安全的HTTP客户端库:
HttpURLConnectionHttpClientOkHttpClient.newCall.execute
-
URL解析前规范化处理:
- 先对输入URL进行规范化处理
- 然后检查协议类型
-
正则表达式匹配:
- 使用严格的正则表达式验证URL格式
- 示例:
Pattern pattern = Pattern.compile("^https?://.+"); if (!pattern.matcher(url).matches()) { return "Invalid URL format"; }
-
网络层限制:
- 配置网络防火墙规则,限制服务器只能访问特定的外部网络
0x06 总结
- 黑名单过滤方式在SSRF防护中效果有限,容易被绕过
- Java的URL解析逻辑存在特殊性,需要特别注意
- 白名单方式比黑名单更安全可靠
- 应结合多种防护措施进行纵深防御
- 在业务允许的情况下,尽可能限制只使用HTTP/HTTPS协议
附录:安全代码示例
@RequestMapping("/ssrf-safe")
public String safeSsrF(String url) {
// 协议白名单检查
if (url == null || !url.matches("^https?://.+")) {
return "Only HTTP/HTTPS URLs are allowed";
}
try {
// 使用URL类前可添加额外检查
URL u = new URL(url);
if (!u.getProtocol().matches("https?")) {
return "Invalid protocol";
}
// 使用HttpURLConnection
HttpURLConnection conn = (HttpURLConnection) u.openConnection();
conn.setRequestMethod("GET");
// 读取响应
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
response.append(inputLine).append("\n");
}
in.close();
return response.toString();
} catch (Exception e) {
return "Error processing URL: " + e.getMessage();
}
}