跳转到帖子

背景SSRF(Server-Side Request Forgery,服务端请求伪造)在实际业务里并不罕见,尤其常见于图片抓取、URL 预览、Webhook、文件导入、远程资源同步等功能。它的风险点不只在于“服务端能替用户发请求”,更关键的是服务端所处的网络位置通常比外部用户更敏感,可能访问到内网服务、云厂商元数据接口或未暴露在公网的管理端口。这篇文章主要从代码审计和防护落地角度整理 SSRF 的识别思路,不讨论攻击内网目标或获取敏感凭据的具体利用过程,重点放在如何发现风险点、如何验证边界、如何加固。典型场景在审计 Web 项目时,下面几类功能需要重点关注:根据用户提供的 URL 抓取图片、头像、封面、视频信息。在线文档转换、PDF 生成、HTML 转图片。Webhook 回调地址配置。URL 健康检查、站点连通性测试。导入远程文件、远程模板、RSS 或 sitemap。后端代理下载文件,再上传到对象存储。这些功能的共同点是:用户输入最终影响了服务端发起网络请求的目标地址。如果缺少严格校验,就可能形成 SSRF 风险。问题代码示例下面是一个常见的远程图片下载逻辑,业务上看起来很正常,但如果直接使用用户传入的 URL,就存在明显风险。public String fetchImage(String imageUrl) throws IOException {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);
conn.setReadTimeout(5000);

try (InputStream in = conn.getInputStream()) {
return storageService.upload(in);
}
}这段代码的问题不在于使用了 HttpURLConnection,而在于没有回答几个关键问题:是否只允许 http / https 协议?是否限制了目标域名范围?是否检查解析后的 IP 是否属于内网地址?是否处理了 30x 跳转后的目标地址?是否限制响应大小和下载时间?是否禁止访问本机、链路本地地址、云元数据地址等敏感网段?代码审计中的识别方法审计时可以从“入口、传播、出口”三个方向梳理。1. 找入口入口通常是用户可控参数,例如:请求参数:url、target、callback、redirect、image、avatar、fileUrl。JSON 字段:webhookUrl、sourceUrl、remotePath。数据库中由用户配置后被异步任务读取的地址。管理后台里看似只有管理员可配,但普通用户可间接影响的数据。不要只看 Controller 层。很多 SSRF 出现在异步任务、消息队列消费、定时同步任务中,入口和发起请求的位置可能相隔较远。2. 看传播重点关注 URL 字符串是否经过拼接、解码、重定向、模板渲染等过程。实际审计里比较容易漏的是二次解码和协议相对地址。// 需要注意这种二次处理
String raw = request.getParameter(\"url\");
String decoded = URLDecoder.decode(raw, StandardCharsets.UTF_8);
download(decoded);如果校验发生在解码前,而请求发生在解码后,就可能出现校验与使用不一致的问题。3. 找出口常见的服务端请求出口包括:Java:HttpURLConnection、OkHttp、Apache HttpClient、RestTemplate、WebClient。Go:http.Get、http.Client.Do、net.Dial、grpc.Dial。Python:requests、urllib、aiohttp。PHP:curl、file_get_contents、fopen。Node.js:axios、request、fetch、http.request。如果项目里封装了统一 HTTP 客户端,建议优先审计封装层,因为业务代码可能大量复用同一套请求逻辑。安全验证的边界在合法授权的测试环境里,可以使用自建的回显服务验证服务端是否会对外发起请求,例如记录请求来源 IP、User-Agent、请求路径等。验证目标应是自己控制的域名或测试服务,不应扫描或访问未授权内网资产。# 示例:在测试机启动一个简单 HTTP 服务观察请求
python3 -m http.server 8080如果服务端访问了测试地址,说明存在服务端请求行为。接下来应验证防护逻辑是否生效,例如:非 http/https 协议是否被拒绝。localhost、127.0.0.1、私有网段地址是否被拒绝。域名解析到内网地址时是否被拒绝。跳转到不允许地址时是否被拒绝。超大响应、慢响应是否会被中断。容易被忽略的风险点1. 只校验字符串,不校验解析结果仅通过字符串 contains 或 startsWith 判断 URL 通常不可靠。URL 的写法很多,解析结果才是服务端实际访问的目标。// 不推荐
if (url.startsWith(\" {
download(url);
}建议使用标准 URL 解析库解析 scheme、host、port,并在 DNS 解析后检查 IP 地址范围。2. 忽略 DNS 解析后的地址域名本身看起来正常,不代表解析结果安全。防护时不能只做域名格式判断,还要解析 IP,并判断是否落入禁止访问的地址段。InetAddress[] addresses = InetAddress.getAllByName(host);
for (InetAddress address : addresses) {
if (isPrivateOrLocalAddress(address)) {
throw new IllegalArgumentException(\"blocked address\");
}
}需要注意 DNS Rebinding 场景:校验时解析一次,真正请求时 HTTP 客户端可能再次解析,结果不一致。更稳妥的做法是将请求绑定到已校验的 IP,同时校验 Host 头和 TLS 场景下的证书处理策略,或者在网络层做出站访问控制。3. 忽略重定向很多 HTTP 客户端默认跟随 30x 跳转。如果只校验初始 URL,不校验跳转后的 Location,就可能绕过业务限制。建议关闭自动重定向,手动处理每一次跳转,并对新地址重复完整校验。RequestConfig config = RequestConfig.custom()
.setRedirectsEnabled(false)
.setConnectTimeout(3000)
.setSocketTimeout(5000)
.build();4. 忽略响应大小限制SSRF 不一定只造成访问敏感地址,也可能被用于消耗资源。下载远程文件时应限制响应大小、读取时间和内容类型,避免被大文件或慢速响应拖垮。long maxBytes = 5 * 1024 * 1024;
long total = 0;
byte[] buffer = new byte[8192];

while ((len = input.read(buffer)) != -1) {
total += len;
if (total > maxBytes) {
throw new IOException(\"response too large\");
}
}推荐的防护策略SSRF 的防护不建议只依赖一层校验。比较稳妥的方式是业务白名单、解析校验、HTTP 客户端限制、网络出站控制共同使用。1. 优先使用白名单如果业务只需要访问固定服务,最推荐使用域名白名单或服务标识映射,而不是让用户直接传完整 URL。{
\"avatar_provider\": \"cdn-a\",
\"path\": \"/user/10001/avatar.png\"
}后端根据 provider 拼接固定域名,用户只控制有限的 path,并继续对 path 做规范化校验。2. 限制协议和端口通常只允许 http 和 https。端口建议只允许 80、443 或业务明确需要的端口,避免服务端请求落到非常规管理端口。URI uri = URI.create(input);
String scheme = uri.getScheme();

if (!\"http\".equalsIgnoreCase(scheme) && !\"https\".equalsIgnoreCase(scheme)) {
throw new IllegalArgumentException(\"unsupported scheme\");
}

int port = uri.getPort();
if (port != -1 && port != 80 && port != 443) {
throw new IllegalArgumentException(\"unsupported port\");
}3. 拒绝敏感地址段需要拒绝的地址通常包括:本地回环地址:127.0.0.0/8、::1。私有地址:10.0.0.0/8、172.16.0.0/12、192.168.0.0/16。链路本地地址:169.254.0.0/16、fe80::/10。本机任意地址:0.0.0.0、::。组织内部明确禁止访问的网段。云环境中还要特别关注元数据服务地址,建议从网络层直接禁止业务容器访问这类地址,而不是只靠应用层判断。4. 统一封装安全 HTTP 客户端不要让各业务模块各写一套校验。建议封装统一的 SafeHttpClient,并要求所有远程抓取类功能只能通过它发起请求。封装层至少包含:URL 标准化解析。scheme、host、port 校验。DNS 解析和 IP 范围判断。重定向逐跳校验。连接超时、读取超时。响应大小限制。Content-Type 白名单。日志记录和告警。5. 加网络层出站控制应用层校验容易因为新代码、新依赖、异常解析逻辑出现遗漏。生产环境建议配合网络策略做出站访问控制,例如容器网络策略、安全组、iptables、服务网格 egress policy 等。一个简单原则是:默认不允许业务服务访问任意地址,只放行业务确实需要访问的外部域名或出口代理。这样即使某处遗漏了代码校验,风险也会被压低。日志与监控建议SSRF 类问题发生时,日志通常能提供比较有价值的线索。建议记录以下字段:触发功能和用户标识。原始 URL 和规范化后的 URL。解析得到的 IP 地址。最终请求地址和跳转链路。拒绝原因。响应状态码、响应大小、耗时。注意日志中不要记录敏感 token、完整签名 URL 或认证头。对于被拦截的内网地址、异常端口、频繁失败请求,可以接入告警。审计清单检查项建议用户是否可控 URL确认入口来源,包括异步任务和配置项协议限制只允许 http/https端口限制默认只允许 80/443 或业务白名单端口域名解析解析所有 A/AAAA 记录并检查地址范围重定向关闭自动跳转,逐跳重新校验响应限制设置超时、大小限制、类型限制统一封装禁止业务直接调用底层 HTTP 客户端网络隔离通过安全组或网络策略限制出站访问总结SSRF 的本质是“用户输入影响服务端请求目标”。审计时不要只看某个危险函数,而要沿着入口、传播、出口完整梳理。防护上也不要依赖单点字符串校验,推荐采用白名单、DNS/IP 校验、重定向控制、资源限制和网络层出站控制的组合方案。实际项目里,最有长期价值的做法是把安全请求能力沉到统一组件里,再配合代码规范和网络策略。这样既能减少业务重复造轮子,也能避免某个新功能因为图省事再次引入同类风险。

代码审计与漏洞定位示意图
代码审计、调用链与关键函数定位示意

0篇意见

推荐意见

没有意见。

游客
抱歉,你的帖子内容包括我们不允许的字词。请编辑你的帖子,删除下面高亮的屏蔽字。
添加意见…