跳转到帖子

背景SSRF(Server-Side Request Forgery)在真实业务里并不少见,尤其是存在“远程图片抓取、Webhook 回调、URL 预览、文档转换、文件导入、第三方接口代理”等功能时,很容易把用户可控 URL 交给服务端发起请求。这类问题的麻烦点在于:表面上只是服务端访问一个 URL,但如果边界控制不足,可能导致内网探测、云元数据访问、访问本机管理端口、打到内部未鉴权接口等风险。本文不讨论攻击利用链,只整理一次代码审计中比较通用的识别、验证和修复方法,适合做安全评审或自查时参考。常见场景审计 SSRF 时,我通常会先按功能入口梳理,而不是一上来全局搜关键字。以下场景值得重点关注:用户头像、文章封面、商品图片的“远程 URL 上传”。富文本编辑器中的 URL 预览、链接解析、OpenGraph 抓取。Webhook 测试功能,允许用户配置回调地址。PDF/Word/HTML 转换服务,支持从 URL 拉取资源。接口代理、数据同步、第三方 API 转发。运维类后台中的健康检查、连通性测试、URL ping。这些功能共同特点是:用户提供一个地址,后端用 HTTP 客户端去访问。代码审计切入点从代码层面看,SSRF 的关键链路通常是:用户输入 URL → 参数校验 → DNS 解析 → 发起请求 → 跟随跳转 → 读取响应 → 保存或返回结果。审计时可以先搜索常见 HTTP 客户端调用点。以 Java 项目为例,常见关键字包括:RestTemplate
WebClient
HttpClient
OkHttpClient
URLConnection
Jsoup.connect
Request.Get
CloseableHttpClientNode.js 项目里可以重点看:axios
request
got
node-fetch
http.request
https.request
undiciPHP 项目里则常见于:curl_exec
file_get_contents
fsockopen
GuzzleHttp\\Client
stream_context_create搜索到调用点后,不要只看这一行是否传入了 URL,更要看 URL 是否可控、是否存在重定向、是否做了 IP 段限制、是否允许非 HTTP 协议、是否存在二次请求。一个典型问题示例下面是审计中很常见的一类写法,业务含义是“根据 URL 下载远程图片”。代码本身不复杂,但安全边界较弱:public byte[] downloadImage(String url) throws IOException {
URL target = new URL(url);
HttpURLConnection conn = (HttpURLConnection) target.openConnection();
conn.setConnectTimeout(3000);
conn.setReadTimeout(5000);
conn.setInstanceFollowRedirects(true);

try (InputStream in = conn.getInputStream()) {
return in.readAllBytes();
}
}这段代码的问题主要有几个:没有限制协议,理论上可能出现非预期协议处理。没有限制目标地址范围,内网地址、本机地址都可能被请求。允许自动跳转,首次 URL 可能是正常域名,但跳转目标可能发生变化。没有限制响应大小,可能导致内存压力。没有校验 Content-Type,非图片内容也会被读取。验证思路:以安全边界为主在授权测试或内部自查中,验证 SSRF 不建议直接访问敏感内部资源。更稳妥的方式是准备可控的测试端点,只验证“服务端是否会按用户输入发起请求”。例如在测试环境部署一个简单的 HTTP 服务,记录请求来源、路径、Header 和时间,用于判断服务端是否发起了请求:python3 -m http.server 8000如果需要观察更详细的请求,可以使用一个简单脚本:from http.server import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
def do_GET(self):
print(\"path:\", self.path)
print(\"headers:\\n\", self.headers)
self.send_response(200)
self.send_header(\"Content-Type\", \"text/plain\")
self.end_headers()
self.wfile.write(b\"ok\")

HTTPServer((\"0.0.0.0\", 8000), Handler).serve_forever()验证重点不是“能不能打到某个敏感地址”,而是确认以下行为:服务端是否会访问用户提交的 URL。请求是否发生在后端服务器,而不是浏览器端。是否跟随 301/302 跳转。是否会解析 DNS 并访问解析后的 IP。是否存在协议限制和地址限制。异常响应是否被原样返回给用户,造成信息泄露。重定向与 DNS 解析是高频坑点不少修复只在请求前做了一次字符串判断,例如禁止 URL 中包含 127.0.0.1、localhost、169.254 等关键字。这类方式很容易漏掉变体,也无法覆盖跳转和 DNS 解析变化。更合理的处理方式是:解析 URL → 校验协议 → 解析域名到 IP → 校验 IP 是否允许 → 发起请求 → 如果跳转,重新对 Location 目标执行同样校验。伪代码逻辑可以参考:1. 只允许 http 和 https
2. 解析 hostname
3. DNS 解析得到所有 IP
4. 任意一个 IP 落入禁止范围,则拒绝
5. 发起请求时禁用自动跳转
6. 如响应为 30x,取 Location 后重新走 1-5
7. 限制响应大小、超时时间、Content-Type禁止访问的地址范围至少应包含:127.0.0.0/8,本机回环地址。10.0.0.0/8、172.16.0.0/12、192.168.0.0/16,常见私网地址。169.254.0.0/16,链路本地地址,云环境中尤其需要注意。0.0.0.0/8、224.0.0.0/4 等特殊用途地址。IPv6 的 ::1、fc00::/7、fe80::/10 等范围。修复示例:请求前后的双重限制下面给一个偏工程化的加固思路,不依赖单纯字符串过滤。具体实现可以结合项目语言和网络库调整。private static final Set<String> ALLOWED_SCHEMES = Set.of(\"http\", \"https\");

public boolean isAllowedUrl(String input) throws Exception {
URI uri = new URI(input).normalize();

String scheme = uri.getScheme();
if (scheme == null || !ALLOWED_SCHEMES.contains(scheme.toLowerCase())) {
return false;
}

String host = uri.getHost();
if (host == null || host.isBlank()) {
return false;
}

InetAddress[] addresses = InetAddress.getAllByName(host);
for (InetAddress address : addresses) {
if (isPrivateOrSpecialAddress(address)) {
return false;
}
}

return true;
}地址判断不要只覆盖 IPv4,也要考虑 IPv6:private boolean isPrivateOrSpecialAddress(InetAddress address) {
return address.isAnyLocalAddress()
|| address.isLoopbackAddress()
|| address.isLinkLocalAddress()
|| address.isSiteLocalAddress()
|| address.isMulticastAddress();
}这只是基础判断。生产环境里建议继续补充明确的 CIDR 判断,尤其是云厂商元数据地址、内部服务网段、容器网络网段等。不同 JDK 方法对某些地址分类并不完全等价,不能完全依赖一个内置方法解决所有场景。更推荐的策略:白名单优先如果业务允许,白名单比黑名单稳定得多。例如只允许访问固定的对象存储域名、公司 CDN 域名、合作方 API 域名。这样可以把 SSRF 的风险面缩小很多。一个相对稳妥的白名单策略:只允许固定域名或固定后缀域名。后缀匹配要避免 example.com.evil.test 这类误判。域名解析后仍要校验 IP,不要认为白名单域名永远安全。禁止用户自定义端口,或只允许 80、443。禁用自动跳转,跳转目标也必须重新校验。后缀匹配可以按边界判断,例如允许 example.com 和 *.example.com:boolean matchDomain(String host) {
host = host.toLowerCase(Locale.ROOT);
return host.equals(\"example.com\") || host.endsWith(\".example.com\");
}请求行为的安全限制即使 URL 校验已经做了,也建议在 HTTP 客户端层面加限制,减少异常情况带来的影响:连接超时和读取超时必须设置,避免线程被长期占用。限制最大响应体大小,例如图片抓取只允许几 MB。限制 Content-Type,例如只允许 image/jpeg、image/png、image/webp。限制请求方法,通常只需要 GET 或 HEAD。不要转发用户可控的敏感 Header。不要把后端请求错误细节完整回显给前端。例如下载远程图片时,应先读取响应头,再决定是否继续读取响应体:Content-Type: image/png
Content-Length: 245760如果 Content-Length 缺失,也不能无限读取,应使用带上限的流读取方式。日志与监控SSRF 类问题在事故复盘时,日志经常是唯一线索。建议记录必要但不过度敏感的信息:发起请求的业务功能和用户标识。原始 URL、最终请求域名、解析到的 IP。响应状态码、耗时、响应大小。是否发生跳转及跳转目标。被策略拒绝的原因。同时可以对异常行为做告警,例如短时间内大量不同端口、不同内网段、异常协议、频繁超时等。告警规则不需要一开始很复杂,先覆盖明显异常就有价值。代码审计清单检查项建议URL 是否用户可控确认参数来源,注意间接传递和配置项覆盖协议限制只允许 http/https,拒绝其他协议域名校验优先白名单,避免简单 contains 判断IP 校验DNS 解析后校验所有返回 IP,覆盖 IPv4/IPv6重定向禁用自动跳转,跳转目标重新校验端口限制仅允许业务必要端口响应限制限制大小、类型、超时错误处理避免把内部错误、请求细节完整回显日志审计记录目标域名、解析 IP、状态码、拒绝原因几个容易忽略的细节只校验 URL 字符串不够,必须基于解析结果做判断。只校验首次请求不够,跳转后的目标也要校验。只考虑 IPv4 不够,IPv6 在不少环境中默认可用。只看业务代码不够,网关、代理、服务网格也可能改变请求路径。只做应用层限制不够,网络层出站访问控制同样重要。实际加固中,应用层校验和网络层隔离最好同时做。应用层负责拒绝明显不合理的输入,网络层负责兜底限制服务能访问的范围。总结SSRF 的本质不是“某个 HTTP 客户端有问题”,而是服务端替用户访问了不该访问的目标。审计时要围绕完整链路看:输入从哪里来、如何解析、访问到哪里、是否跳转、响应如何处理。修复上,不建议依赖关键字黑名单。更稳妥的方案是白名单域名、协议和端口限制、DNS 后 IP 校验、重定向重新校验、响应大小和类型限制,再配合网络层出站访问控制。这样即使某一层出现遗漏,也不至于直接暴露内部服务。

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

0篇意见

推荐意见

没有意见。

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