Java SSRF漏洞审计:从原理到实战的攻防指南

📅 2026/6/24 4:27:15
Java SSRF漏洞审计:从原理到实战的攻防指南
1. 项目概述为什么Java SSRF审计是安全工程师的必修课最近在复盘几个内部系统的安全评估报告发现一个老生常谈但又屡禁不止的问题——SSRFServer-Side Request Forgery服务端请求伪造。尤其是在Java生态里从传统的Servlet到主流的Spring框架再到各种第三方HTTP客户端库稍不留神就会埋下隐患。这不仅仅是CTF比赛里的考点更是真实渗透测试和代码审计中的高频漏洞。很多开发同学对SSRF的理解还停留在“能访问内网”的层面但它的危害远不止于此。一个未受控的SSRF点轻则成为攻击者探测内网、扫描端口的跳板重则可能直接访问元数据服务、攻击内部脆弱应用甚至与反序列化等漏洞结合形成杀伤链。因此深入理解Java中SSRF的成因、挖掘技巧和修复方案对于安全工程师和注重安全的开发者来说是一项必须掌握的核心技能。这篇内容我就结合自己这些年审计Java代码和做渗透测试的经验拆解一下Java SSRF审计的完整思路、常见风险代码模式以及那些容易被忽略的绕过技巧和修复要点。2. SSRF漏洞原理与Java中的典型风险场景2.1 从原理理解SSRF不仅仅是“发起请求”SSRF的本质是“应用代替攻击者发起请求”。当一个Web应用提供了从服务端发起网络请求的功能比如根据用户输入的URL获取图片、数据同步、网页预览等且对这个URL参数没有进行严格的校验和限制时攻击者就可以构造恶意的URL让应用服务器去访问其本无权访问的内网系统、本地环回地址127.0.0.1或者其他受保护的资源。在Java中这个风险被放大了因为Java提供了极其丰富和多样化的HTTP/网络请求客户端。不同于PHP的file_get_contents或curlJava开发者可选的工具库非常多每种库的默认行为、支持的协议、解析逻辑都可能存在差异这就给攻击者留下了多种可能的利用路径。核心风险场景包括但不限于远程资源加载用户上传头像时填写网络URL服务端下载。数据获取与聚合从用户提供的RSS源、API地址获取数据。网页预览/转码提供网页截图、内容抓取功能。内部服务调用根据传入的参数动态拼接URL调用内部其他微服务的接口。文件处理解析Office文档、XML文件中的外部实体或链接XXE有时会触发SSRF。2.2 Java中发起网络请求的常见方式与风险点理解风险首先要清楚Java里怎么发起请求。下面这几种是审计时需要重点关注的2.2.1java.net.URL和java.net.HttpURLConnection这是JDK原生的、最基础的方式。风险代码通常长这样String url request.getParameter(url); URL u new URL(url); HttpURLConnection conn (HttpURLConnection) u.openConnection(); // ... 读取conn.getInputStream()这里的URL类支持多种协议httphttpsfilejarftp等。如果未对传入的url字符串做任何处理攻击者可以传入file:///etc/passwd来读取服务器文件或者传入http://169.254.169.254/latest/meta-data/AWS元数据服务地址来获取云服务器的敏感信息。注意HttpURLConnection默认会跟随重定向3xx响应。这意味着即使你的代码只发起一次请求如果攻击者控制了一个返回302重定向到内网地址的服务器请求依然会被转向到内网。这是一个非常关键的利用点。2.2.2 Apache HttpClient这是企业级应用中最常用的HTTP客户端库之一功能强大配置灵活但配置不当风险很高。String target request.getParameter(target); CloseableHttpClient client HttpClients.createDefault(); HttpGet get new HttpGet(target); CloseableHttpResponse response client.execute(get);风险点默认不检查重定向与HttpURLConnection相反HttpClient的默认实现如HttpClientBuilder.create().build()默认不自动处理重定向。但这不代表安全因为攻击者可以直接构造内网地址。协议处理它依赖于JDK的java.net.URL进行URL解析因此同样支持file、jar等协议。配置覆盖开发者可以通过自定义RequestConfig、HttpClientBuilder来设置连接超时、代理等其中如果配置了socketFactory或laxRedirectStrategy可能会引入新的风险。2.2.3 OkHttpSquare公司开发的现代HTTP客户端在Android和后台服务中都很流行。OkHttpClient client new OkHttpClient(); String url request.getParameter(url); Request req new Request.Builder().url(url).build(); Response response client.newCall(req).execute();OkHttp的OkHttpClient在默认情况下对于它无法理解的URL scheme如file://会在构建请求时直接抛出IllegalArgumentException这提供了一定的默认防护。但是这取决于OkHttp自身的URL解析逻辑并非绝对安全。更重要的是如果传入的是一个合法的http://192.168.1.1:8080/admin它依然会照常访问。2.2.4 Spring框架的RestTemplateSpring生态的标配通常用于微服务间的调用。Autowired private RestTemplate restTemplate; public String fetchData(String userUrl) { // 危险直接使用用户输入 return restTemplate.getForObject(userUrl, String.class); }RestTemplate底层默认使用JDK的HttpURLConnection或者可以通过配置使用Apache HttpClient。因此它继承了底层客户端的所有风险特性。审计时需要追踪RestTemplate是如何被初始化的RestTemplateBuilder或直接new以及是否配置了自定义的ClientHttpRequestFactory。2.2.5 其他易忽略的入口java.net.Socket直接使用Socket连接如果IP和端口来自用户输入同样存在风险可导致服务器向任意内网IP端口发起TCP连接用于端口扫描。java.net.URLConnection的派生类如HttpsURLConnection。ImageIO.read(URL)用于读取网络图片底层会发起HTTP请求。XML解析器如解析外部实体XXE时可能会发起HTTP/FTP请求。3. 代码审计实战定位与挖掘SSRF漏洞知道了风险点我们来看看在真实的、可能非常庞大的Java代码库里如何高效地找到这些漏洞。3.1 静态审计从源码和字节码中寻找蛛丝马迹静态审计是基础目标是无需运行代码就发现潜在风险。3.1.1 关键词搜索与模式匹配这是最直接的方法。在IDE或代码搜索工具中全局搜索以下关键词类名HttpURLConnection,URL,HttpClient,OkHttpClient,RestTemplate,Request,HttpGet,HttpPost,URLConnection,Socket。方法名.openConnection(),.openStream(),.execute(),.getForObject(),.getForEntity(),.postForObject(),.newCall()。参数名url,uri,link,source,target,endpoint,api。通常这些参数会从HttpServletRequest.getParameter()、RequestParam、PathVariable等处获取。找到这些代码位置后进行数据流追踪这个URL参数从哪里来是否经过了校验最终传给了哪个危险方法3.1.2 关注参数传递与封装很多时候URL不是直接拼接的而是经过了几层封装。// 案例1经过工具类封装 String internalApi “http://internal.service/api/data?id” userInputId; String result HttpUtil.get(internalApi); // 需要查看HttpUtil.get的实现 // 案例2隐藏在配置对象中 Config config new Config(); config.setServiceUrl(request.getParameter(“callbackUrl”)); // 风险点在这里 someService.callExternal(config); // 需要跟踪callExternal方法审计时要有“链式”思维沿着参数传递路径一直追到最终发起请求的地方。3.1.3 检查URL构建逻辑很多SSRF发生在动态拼接时尤其是将用户输入拼接到固定域名或路径前。String host “internal.company.com”; String path request.getParameter(“path”); // 用户可控 String fullUrl “http://” host “/api/” path;这里攻击者可以通过传入path为evil.com/利用语法将host变为evil.com最终请求http://evil.com/api/。这就是所谓的“URL解析差异导致的绕过”。3.2 动态审计与FUZZ验证漏洞并探索利用边界静态分析找到可疑点后需要通过动态测试来验证漏洞是否存在并尝试绕过可能的防护。3.2.1 搭建测试环境与代理拦截最好的方式是在本地或测试环境运行起应用。使用Burp Suite、ZAP或mitmproxy作为代理拦截所有出站流量。这样当你在前端触发一个“下载URL”功能时可以在代理中清晰地看到服务器实际发起了什么请求请求头是什么是否跟随了重定向。3.2.2 精心构造Payload进行FUZZ针对找到的可控参数系统地尝试各种Payload基本内网探测http://127.0.0.1:8080/adminhttp://localhost:3306尝试连接MySQLhttp://192.168.1.1常见路由器地址http://169.254.169.254/latest/meta-data/AWS元数据http://[::1]:8080/IPv6环回地址协议绕过不同协议file:///etc/passwdgopher://在某些旧库中可能支持dict://。畸形协议将http写成Http、HtTp等看解析器是否大小写不敏感。利用URL解析特性利用http://expected-hostevil.com。如果代码是new URL(“http://” userInput)而用户输入evil.com最终会请求evil.com。利用#http://expected-host#evil.com。#后面是片段部分解析器可能处理不当。利用?http://expected-host?redirectevil.com。如果后端逻辑是取?后的参数作为最终目标就可能被绕过。利用DNS重绑定这是一个高级技巧。让一个域名在第一次解析时返回一个允许的外网IP通过白名单校验在很短的TTL后第二次解析时返回内网IP如127.0.0.1。由于服务器在建立TCP连接时使用的是第二次解析的IP从而绕过基于域名或第一次解析IP的黑白名单校验。这需要攻击者控制一个域名并设置极短的TTL。重定向利用如果怀疑有重定向跟随可以搭建一个简单的HTTP服务器对于特定请求返回302状态码Location头指向内网地址。观察服务器的请求日志看是否收到了来自目标应用的对内网地址的请求。绕过字符串过滤如果代码里用contains(“127.0.0.1”)来过滤尝试http://127.1许多系统会解析为127.0.0.1http://2130706433127.0.0.1的十进制表示http://0x7f000001十六进制表示http://127.0.0.1.nip.ionip.io是一个泛域名解析服务127.0.0.1.nip.io解析到127.0.0.1如果过滤了localhost尝试LOCALHOST、LocalHost、::1、0:0:0:0:0:0:0:1。3.2.3 利用工具进行自动化探测可以编写简单的脚本将上述Payload列表批量发送到目标参数通过响应时间、响应内容或错误信息来判断是否存在SSRF以及可能访问到的内部服务。例如访问一个不存在的端口通常会超时或连接拒绝而访问一个存在的Web服务可能会返回特定的HTTP状态码或Banner。4. 深入利用SSRF漏洞的危害升级找到SSRF漏洞只是第一步理解它能造成多大危害才能更好地评估风险等级。4.1 信息收集与内网探测这是SSRF最直接的作用。通过让服务器循环访问192.168.1.1-254的80、443、8080等常见端口可以绘制出内网拓扑发现内部的管理后台、未授权API、测试环境等。结合响应内容如特定的Title、Cookie、报错信息可以识别出应用类型如Jenkins, Confluence, Redis等。4.2 访问云元数据服务在云环境AWS, GCP, Azure, Aliyun等中这是一个极高危的场景。云实例内部可以通过一个固定的内网地址如AWS的169.254.169.254访问元数据服务获取临时密钥、角色信息等。攻击者通过SSRF获取这些凭证后可以进一步控制云资源造成“一键沦陷”。# 尝试访问AWS IMDSv1 curl http://169.254.169.254/latest/meta-data/ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/{role-name}实操心得现在主流云厂商都推出了IMDSv2Instance Metadata Service Version 2它要求先发起一个PUT请求获取令牌再用令牌访问元数据。这增加了一些利用难度但并非不可绕过。审计时要注意应用发起的请求是否可能包含PUT方法。4.3 攻击内部脆弱应用如果内网中存在已知漏洞且未对外网开放的应用SSRF就是打通攻击链的“桥梁”。例如攻击内网Redis未授权访问写入Webshell。攻击内网未授权访问的H2 Database Console执行任意SQL。攻击内网Jenkins、GitLab的未授权RCE漏洞。向内部Kafka、RabbitMQ等消息队列发送恶意数据。4.4 组合拳利用SSRF很少单独造成毁灭性影响但它是一个优秀的“跳板”。SSRF Redis未授权访问通过gopher协议如果支持或HTTP协议如果Redis配置了HTTP接口直接向Redis发送命令写入定时任务或Webshell。SSRF FastJSON反序列化如果内网有一个使用FastJSON且开启autoType的服务SSRF可以构造一个特殊的HTTP请求触发该服务的反序列化漏洞。SSRF XXE有些应用在处理XML时如果支持外部实体SSRF可以将其作为触发XXE的入口点。5. 修复与防御从根源上杜绝SSRF风险找到漏洞后更重要的是如何修复。防御SSRF需要多层次、纵深式的方案。5.1 输入校验建立可靠的白名单机制黑名单永远不是好主意IP、域名、协议。最有效的方法是白名单。域名白名单如果业务功能明确只允许从几个固定的可信域名获取资源那么就在后端维护一个允许的域名列表。private static final SetString ALLOWED_DOMAINS Set.of(“api.trusted.com”, “cdn.safe.org”); public boolean isUrlAllowed(String urlString) { try { URL url new URL(urlString); String host url.getHost(); return ALLOWED_DOMAINS.contains(host); } catch (MalformedURLException e) { return false; } }注意这里要使用url.getHost()它返回的是规范化后的主机名小写去掉端口。同时要警惕通过、#等进行的绕过确保在解析前或解析后传入的URL主机部分完全符合预期。IP白名单如果必须通过IP访问则解析出IP地址进行校验。InetAddress address InetAddress.getByName(new URL(urlString).getHost()); if (!address.isSiteLocalAddress() !address.isLoopbackAddress()) { // 检查IP是否在允许的CIDR范围内 // 例如允许 203.0.113.0/24 }注意isSiteLocalAddress()判断是否为私有IP如10.x.x.x, 172.16.x.x-172.31.x.x, 192.168.x.xisLoopbackAddress()判断是否为环回地址127.x.x.x, ::1。但要注意DNS重绑定攻击它可能在校验时解析为外网IP实际连接时解析为内网IP。5.2 使用安全的网络客户端并正确配置协议限制对于只用于HTTP/HTTPS请求的功能在代码层面限制只允许http和https协议。if (!urlString.startsWith(“http://”) !urlString.startsWith(“https://”)) { throw new IllegalArgumentException(“Only HTTP/HTTPS protocols are allowed”); }更严谨的做法是使用java.net.URI它比URL的解析更严格且可以通过URI.getScheme()来检查协议。禁用重定向和协议重定向HttpURLConnectionconn.setInstanceFollowRedirects(false);Apache HttpClientRequestConfig config RequestConfig.custom() .setRedirectsEnabled(false) // 禁用HTTP重定向 .build(); CloseableHttpClient client HttpClients.custom() .setDefaultRequestConfig(config) .build();OkHttp默认不跟随重定向如需设置在构建OkHttpClient时使用.followRedirects(false)。RestTemplate需要配置底层的ClientHttpRequestFactory来禁用重定向。设置连接超时和读取超时防止攻击者利用SSRF进行DoS攻击或端口扫描时长时间挂起连接。// 以HttpURLConnection为例 conn.setConnectTimeout(5000); // 5秒连接超时 conn.setReadTimeout(10000); // 10秒读取超时5.3 网络层隔离与权限最小化出站网络策略在服务器或容器层面使用防火墙如iptables, AWS Security Group严格限制应用服务器发起的出站连接。只允许访问业务必须的外部服务和特定的内部服务如数据库、缓存禁止访问元数据服务地址和整个内网段。使用跳板机或代理对于必须访问外部资源的应用可以统一配置一个安全的出站代理。所有外部请求都经过代理在代理层实施统一的安全策略如URL过滤、速率限制。容器化环境在Kubernetes中可以使用NetworkPolicy来限制Pod之间的网络流量以及Pod对外部的访问。云元数据服务防护如果云平台支持禁用或升级到IMDSv2并为实例分配最小权限的IAM角色。5.4 业务逻辑层面的缓解避免直接使用用户输入的完整URL如果业务是“网页预览”可以考虑让用户输入“文章ID”由后端根据ID从固定的、受信任的源获取URL。使用中间代理服务建立一个受你完全控制的代理微服务。前端将用户提供的URL发给这个代理服务代理服务负责进行严格的安全校验白名单、协议检查等然后再去获取资源最后将安全处理后的内容返回给主应用。这样将风险集中在一个可控的服务中。6. 审计案例深度剖析从源码到利用让我们看一个模拟的真实案例它融合了多个常见的错误模式。漏洞代码片段Spring Boot应用RestController public class PreviewController { Autowired private RestTemplate restTemplate; // 默认使用SimpleClientHttpRequestFactory (基于HttpURLConnection) GetMapping(“/preview”) public String previewPage(RequestParam String url) { // 1. 简单的黑名单过滤形同虚设 if (url.contains(“127.0.0.1”) || url.contains(“localhost”) || url.contains(“192.168.”) || url.contains(“10.”) || url.contains(“169.254”)) { return “URL not allowed”; } // 2. 直接使用用户输入的URL发起请求 try { ResponseEntityString response restTemplate.getForEntity(url, String.class); return “Preview: ” response.getBody().substring(0, Math.min(100, response.getBody().length())); } catch (Exception e) { return “Error fetching URL: ” e.getMessage(); } } }审计与攻击过程识别风险点preview接口直接接收url参数并传给RestTemplate.getForEntity。RestTemplate默认跟随重定向。绕过黑名单Payload 1:http://127.1:8080/admin。127.1等价于127.0.0.1绕过了对127.0.0.1的字符串匹配。Payload 2:http://0x7f000001/。十六进制IP表示。Payload 3:http://2130706433/。十进制IP表示。Payload 4:http://localhost.nip.io/。解析到127.0.0.1。利用重定向攻击者控制http://attacker.com/redirect该地址返回302状态码Location: http://169.254.169.254/latest/meta-data/。提交urlhttp://attacker.com/redirect。应用首先请求攻击者服务器收到重定向响应后自动向AWS元数据服务发起第二次请求成功获取到元数据信息。这里的关键是黑名单只检查了用户最初输入的URL没有检查重定向后的URL。协议绕过尝试由于RestTemplate底层是HttpURLConnection尝试file:///etc/passwd。但HttpURLConnection通常只支持HTTP/HTTPS此Payload可能直接抛出异常但值得一试。修复方案使用白名单如果业务只允许预览少数几个合作网站改用白名单。禁用重定向自定义RestTemplate禁用重定向。Bean public RestTemplate secureRestTemplate() { SimpleClientHttpRequestFactory factory new SimpleClientHttpRequestFactory(); // 禁用重定向 factory.setFollowRedirects(false); return new RestTemplate(factory); }解析并校验最终访问的Host即使不禁用重定向也应在收到响应后检查最终请求的URL可通过拦截器或自定义ClientHttpRequestFactory实现确保其Host在白名单内。业务逻辑重构考虑将“预览”功能改为后端从固定源获取内容而非直接代理用户提供的任意URL。7. 高级绕过技巧与疑难问题排查即使实施了上述防御攻击者仍可能有一些“奇技淫巧”。作为审计者需要知道这些高级技巧。7.1 DNS重绑定攻击的深入理解与防御这是绕过IP/域名白名单的终极杀器之一。攻击原理攻击者注册一个域名evil.com设置TTL为0。当应用服务器校验evil.com时DNS服务器返回一个允许的外网IP如1.2.3.4校验通过。随后应用服务器发起真正的HTTP请求在建立TCP连接前需要再次解析evil.com此时攻击者的DNS服务器返回一个内网IP如192.168.1.1。由于校验和请求发生在两次DNS解析之间攻击成功。防御措施在请求层面校验IP在发起网络请求的代码处再次解析域名获取IP并与白名单IP比对。确保校验和请求使用的IP来自同一次解析结果。但这在高并发下可能仍有极小的时间窗口。使用固定DNS解析在应用启动时或首次校验时将域名解析为IP并缓存后续请求都使用这个缓存的IP。但这不适合需要动态解析的场景。禁用外部域名对于只访问内部服务的功能直接禁止使用域名只允许IP地址并对IP进行严格的白名单校验。网络层隔离最有效的方式。严格限制应用服务器不能访问非必要的内网段包括169.254.169.254等元数据地址。7.2 针对URL解析差异的绕过不同库、甚至同一库的不同版本对URL的解析可能存在差异。案例java.net.URL和java.net.URI的解析就有所不同。某些旧版本的HTTP客户端库可能不支持URL标准中的所有功能导致对包含、#、\等字符的URL处理异常。审计建议在修复时使用标准、严格的方式进行URL解析和校验。推荐使用java.net.URI进行解析因为它更符合RFC标准且行为更可预测。解析后使用URI.getHost()获取主机名URI.getScheme()获取协议。7.3 盲SSRF的探测与利用有时SSRF没有回显盲SSRF服务器请求了但响应内容不会返回给攻击者。这时需要利用带外Out-of-Band, OOB技术。探测方法时间延迟让服务器访问一个你控制的、会故意延迟响应的端点如sleep(10)通过观察应用响应时间是否变长来判断漏洞是否存在。DNS查询让服务器访问一个类似http://unique-id.attacker-dns-server.com的地址。即使HTTP请求失败服务器也会先解析这个域名。你只需要在你的DNS服务器日志中查看是否收到了对unique-id.attacker-dns-server.com的查询请求即可确认漏洞。这是最有效、最常用的盲SSRF探测方式。HTTP回调让服务器访问你控制的HTTP服务器并在你的服务器日志中查看访问记录。即使请求失败如403、404TCP连接建立和HTTP请求发送的记录通常也会被记录下来。7.4 排查技巧与工具推荐日志分析在测试时开启应用服务器和HTTP客户端的DEBUG级别日志查看详细的请求发出记录。网络抓包在服务器端使用tcpdump或Wireshark抓包这是最权威的证据可以看清服务器到底向哪个IP的哪个端口发送了什么数据。使用交互式SSRF测试服务器搭建或使用在线的服务如Burp Collaborator、Interactsh它们会自动生成一个临时域名并记录所有到达该域名的DNS、HTTP、HTTPS请求是检测盲SSRF的神器。代码审计工具辅助可以使用Semgrep、CodeQL编写自定义规则来扫描代码库中的SSRF模式。例如寻找“用户输入流入URL构造函数或HttpClient.execute方法”的数据流。Java SSRF的审计是一场攻防的博弈。作为防守方必须深刻理解攻击者的思维和利用链从输入校验、客户端配置、网络隔离等多个层面构建纵深防御。而作为审计者则需要具备耐心和想象力不放过任何一处用户输入流入网络请求的地方并时刻思考“如果我是攻击者我会如何绕过当前的限制”。记住没有一劳永逸的修复只有持续的安全意识和深度的防御实践。在实际项目中我通常会建议将SSRF的防护代码抽象成公司内部的通用安全组件对所有出站请求进行强制性的统一校验和审计这样才能在架构层面降低风险。