跳转到帖子

SSRF(Server-Side Request Forgery,服务端请求伪造)在代码审计中并不少见。它通常不是一个单独的“危险函数”问题,而是外部输入经过 URL 解析、网络请求、重定向和响应处理等多个环节后,形成了服务端可控的请求能力。真正有效的分析,不能只盯着某个 HTTP 客户端调用,而应当沿着数据流判断:谁能控制请求地址、请求会发往哪里、是否允许重定向、响应是否会回显,以及修复是否覆盖了所有变体。一、问题场景与风险边界常见业务包括图片抓取、Webhook 回调、远程文件导入、站点连通性检测和第三方接口代理。例如,应用提供如下接口:POST /api/import
{
\"source\": \" /> }服务端为了读取远程内容,可能直接使用用户提交的地址发起请求。如果缺少目标限制,攻击者就可能诱导服务端访问本不应由外部访问的网络资源,例如内部管理接口、云环境元数据地址或仅绑定在回环地址上的调试服务。这里需要区分两个问题:服务端是否能向攻击者无法直接访问的网络位置发起请求;请求结果是否会返回给攻击者,或者是否会改变服务端状态。即使响应内容不直接回显,盲 SSRF 仍可能通过状态码、响应时间、错误信息或业务副作用暴露信息,因此不能简单地以“接口没有返回正文”作为安全结论。二、代码审计时如何定位数据流审计可以从出网点反向追踪。常见出网点包括 Java 的 HttpClient、RestTemplate、WebClient,Python 的 requests、urllib,以及 Node.js 的 axios、fetch。定位到调用点后,重点检查 URL 是否来自请求参数、数据库字段、消息队列或可被普通用户间接控制的配置。以 Java 代码为例,下面的实现存在典型问题:public byte[] download(String source) throws IOException {
URL url = new URL(source);
HttpURLConnection connection =
(HttpURLConnection) url.openConnection();
connection.setConnectTimeout(3000);
connection.setReadTimeout(5000);
return connection.getInputStream().readAllBytes();
}这段代码的问题不在于使用了某个特定类,而在于缺少完整的目标校验:没有限制协议,理论上可能接受非 HTTP 协议或产生解析差异;没有校验解析后的 IP 是否属于受限地址段;没有处理 DNS 解析结果变化;没有限制重定向,初始地址合法并不代表最终地址合法;没有限制响应大小,可能被利用造成资源消耗;异常信息和响应内容可能将内部信息带回前端。三、容易遗漏的解析与校验细节1. 不要只做字符串前缀判断类似 source.startsWith(\" 的判断不可靠。它无法正确处理用户名字段、端口、大小写、尾随点、编码字符以及相似域名等情况。安全校验应基于标准 URL 解析结果,分别检查协议、主机、端口和解析地址。2. 域名校验不能替代 IP 校验即使业务只允许访问某个域名,也要考虑域名解析到内网地址的情况。更稳妥的做法是先解析主机名,再对所有解析结果进行地址分类,拒绝回环地址、链路本地地址、私有地址、未指定地址以及企业明确禁止的网段。下面是一个用于审计思路说明的简化示例,实际项目还应补充 IPv4、IPv6、DNS 变化和异常处理:URI target = URI.create(source);

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

String host = target.getHost();
if (host == null || host.length() > 253) {
throw new IllegalArgumentException(\"invalid host\");
}

for (InetAddress address : InetAddress.getAllByName(host)) {
if (address.isAnyLocalAddress()
|| address.isLoopbackAddress()
|| address.isLinkLocalAddress()
|| address.isSiteLocalAddress()) {
throw new IllegalArgumentException(\"restricted address\");
}
}这个示例不能直接当作通用安全组件使用。生产实现应采用经过审查的网络地址判断库,并结合企业网络边界策略。尤其在 IPv6 环境中,不能只覆盖常见的 IPv4 私有网段。3. 重定向是独立的校验点请求初始地址通过校验后,服务端仍可能被 30x 响应引导到另一个地址。因此应关闭自动重定向,或者对每一次跳转重新执行完整校验,并设置最大跳转次数。HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NEVER)
.connectTimeout(Duration.ofSeconds(3))
.build();如果业务必须支持重定向,建议显式读取 Location,限制跳转次数,并对新地址重新执行协议、域名、解析地址和端口校验,而不是简单开启自动跟随。四、修复方案应当分层设计单一校验通常很容易被遗漏,比较稳妥的方式是将控制措施分成业务层、解析层、网络层和资源层。层次建议措施目的业务层优先使用资源 ID 或固定白名单,不直接接收任意 URL减少用户可控范围解析层限制 HTTPS,校验主机、端口、路径和解析地址避免解析绕过请求层关闭或严格控制重定向,设置连接和读取超时降低跳转与阻塞风险网络层使用独立出口代理或 egress ACL,默认拒绝访问内部网段即使代码失误也保留边界资源层限制响应大小、并发数、下载时间和响应类型防止资源消耗其中网络层控制非常重要。代码校验难以覆盖所有解析差异,而出站防火墙、容器网络策略或专用代理可以提供第二道防线。应用不应直接拥有访问管理网段、数据库网段和云平台控制面的能力。五、如何设计安全验证用例验证修复时,不建议只测试一个正常地址。应在隔离测试环境中构造一组输入,观察应用是否拒绝、是否产生出站请求,以及日志是否记录了足够的信息。合法的 HTTPS 公网地址:确认正常业务不受影响;HTTP、非标准协议和缺少主机的地址:确认协议校验有效;回环地址、私有地址、链路本地地址和 IPv6 本地地址:确认地址分类有效;解析到受限地址的测试域名:确认域名解析后的地址也会被检查;返回重定向的测试服务:确认每次跳转都重新校验;超大响应、慢响应和多次跳转:确认资源限制有效;包含特殊字符、端口和用户名字段的 URL:确认解析逻辑没有依赖字符串拼接。测试时应在本地或专门的靶场环境中完成,使用可控的测试服务记录请求即可,不要将验证目标指向生产内网或第三方系统。六、日志与监控中的关键字段SSRF 修复不能只看接口是否返回错误,还应检查服务端日志和出口流量。建议记录请求来源、业务用户、原始 URL 的脱敏摘要、解析后的主机和端口、最终决策、拒绝原因、响应大小、耗时以及重定向次数。日志中不建议直接保存可能包含凭据的完整 URL,例如 URL 用户名、查询参数和签名字段。可以记录规范化后的主机、端口和哈希摘要,并为拒绝事件设置告警阈值。若同一用户短时间内大量提交不同内网地址、不断触发 DNS 解析失败或重复尝试重定向,应纳入异常行为分析。一个可复现的安全修复,应该同时回答三个问题:危险请求是否被拦截、正常请求是否仍然可用、拦截行为是否能够被日志和监控准确识别。七、常见错误与改进建议只拦截几个固定字符串:应改为基于标准解析结果和地址分类判断。只校验第一次请求:应关闭自动重定向,或对每次跳转重新验证。只依赖应用层过滤:应配合出站网络策略,限制服务账号的访问范围。只关注响应内容:应同时关注状态码、耗时、错误信息和业务副作用。修复后只做单个回归用例:应覆盖协议、地址族、DNS、重定向和资源限制等测试维度。总结SSRF 的本质是服务端获得了超出业务需要的网络访问能力。代码审计时,应从用户输入一路追踪到实际出网点,重点检查 URL 解析、DNS 解析、地址分类、重定向和资源控制,而不是停留在函数名层面。比较可靠的治理方式是“业务白名单限制 + 标准化解析校验 + 重定向控制 + 出站网络隔离 + 完整监控”多层配合。对于长期维护的系统,还应将这些规则封装成统一组件,并通过单元测试、集成测试和网络策略测试持续验证,避免不同业务各自实现一套不完整的 URL 过滤逻辑。

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

0篇意见

推荐意见

没有意见。

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