在您的 URL 获取器到达私有网络之前拦截 SSRF 重定向链
服务器端请求伪造(SSRF)是一种众所周知的漏洞,允许攻击者滥用服务器端功能来发起非预期请求。虽然大多数开发人员了解 SSRF 的基础知识——例如阻止本地地址(如 localhost 或 127.0.0.1)——但一种更隐蔽且更危险的变体往往被忽视:重定向链。这些链可以利用 DNS 解析和 HTTP 重定向来访问私有网络,从而绕过初始允许列表。让我们深入探讨其工作原理以及如何缓解这一问题。
SSRF 重定向链漏洞利用
考虑以下场景:
-
用户提交 URL:用户提交 https://example.invalid/report。应用程序的初始允许列表可能会批准此请求,因为 DNS 查询返回的是一个公共 IP 地址。
-
服务器响应重定向:服务器处理请求并响应重定向到 http://169.254.169.254/latest/meta-data/。此 IP 地址属于 Google Compute Engine 元数据服务,通常仅限于内部网络。
-
获取客户端跟随重定向:应用程序的获取客户端自动跟随重定向,而不会重新评估允许列表。对 example.invalid 的原始批准在无声中赋予了访问完全不同主机的权限。
这是许多 URL 预览和导入服务忽视的 SSRF 边界:一个 URL 并非一个网络目的地。DNS 解析可以将公共主机名解析为私有 IP 地址,而 HTTP 重定向可以链式通过多个主机,最终到达受限网络。
这为何重要
1. 绕过允许列表
传统的 SSRF 防御通常依赖于允许列表(受信任主机的白名单)。如果攻击者可以构造一个解析到受信任域但重定向到私有 IP 的 URL,则允许列表会被绕过。
2. 访问敏感数据
私有网络通常包含敏感数据或服务。如果攻击者可以将获取请求重定向到 http://10.0.0.1/admin,他们可能会获得管理访问权限或窃取数据。
3. 通过多重重定向链式传递
攻击者可以创建一系列重定向,每个重定向都逐步接近目标网络。例如:
- 初始 URL 解析到一个受信任的公共主机。
- 第一个重定向到一个半受信任的主机。
- 第二个重定向到目标网络内的私有 IP。
每个重定向看起来都是合法的,使得检测和缓解更加困难。
缓解策略
1. 深度检查重定向
不要盲目跟随重定向。在执行获取之前检查最终目的地。例如,在 Node.js 应用程序中使用 fetch 时,您可以拦截重定向:
const fetch = require('node-fetch');
async function fetchWithRedirects(url) {
const response = await fetch(url, {
redirect: 'manual' // 这允许手动处理重定向
});
if (response.type === 'opaqueredirect') {
// 手动处理重定向
console.log('重定向被拦截:', response.url);
// 不要自动跟随重定向
return null;
}
return response;
}
// 示例用法
fetchWithRedirects('https://example.invalid/report')
.then(response => {
if (!response) {
console.log('由于重定向而取消获取');
} else {
response.text().then(console.log);
}
});
2. 阻止私有 IP 范围
阻止解析到私有 IP 范围(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16 等)的请求。这可以在网络级别或应用程序内完成:
const isPrivateIP = (ip) => {
const privateRanges = [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16'
];
return privateRanges.some(range => {
const [base, prefix] = range.split('/');
const baseIp = parseInt(base.split('.').join(''), 16);
const ipNum = parseInt(ip.split('.').join(''), 16);
return ipNum >= baseIp && ipNum < (baseIp + (1 << (32 - parseInt(prefix))));
});
};
// 示例用法
const ip = '10.0.0.1';
if (isPrivateIP(ip)) {
console.log(`阻止私有 IP: ${ip}`);
} else {
console.log(`允许 IP: ${ip}`);
}
3. 禁用获取的 DNS 解析
某些获取库允许禁用特定请求的 DNS 解析。例如,在 Go 中,您可以使用 net/http 并配合自定义的 Dialer:
package main
import (
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"time"
)
func main() {
client := &http.Client{
Transport: &http.Transport{
Dial: func(network, address string) (net.Conn, error) {
if net.ParseIP(address).IsLoopback() || net.ParseIP(address).IsPrivate() {
return nil, fmt.Errorf("blocked")
}
return net.Dial(network, address)
},
},
}
// 示例请求
resp, err := client.Get("https://example.invalid/report")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// 处理响应
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
4. 使用 Web 应用防火墙(WAF)
WAF 可以帮助检测和阻止恶意重定向链。寻找能够识别链式重定向模式或请求私有 IP 范围的规则。
结论
SSRF 重定向链是一种复杂的规避技术,可以绕过传统的 SSRF 防御。通过理解这些链的工作原理并实施深度检查、阻止私有 IP 范围、禁用获取的 DNS 解析以及利用 WAF,您可以显著降低 SSRF 攻击的风险。始终假设任何外部输入都可能导向意外的网络目的地,并验证请求生命周期的每个步骤。
要点
永远不要信任 URL 的初始目的地。始终检查重定向并阻止对私有网络的访问,以防止 SSRF 重定向链破坏您的应用程序。
Block SSRF Redirect Chains Before Your URL Fetcher Reaches Private Networks
Server-Side Request Forgery (SSRF) is a well-known vulnerability that allows attackers to abuse server-side functionality to make unintended requests. While most developers understand the basics of SSRF—such as blocking local addresses like localhost or 127.0.0.1—a more subtle and dangerous variant often goes overlooked: redirect chains. These chains can bypass initial allowlists by leveraging DNS resolution and HTTP redirects to reach private networks. Let's dive into how this works and how to mitigate it.
The SSRF Redirect Chain Exploit
Consider the following scenario:
-
User Submits a URL: A user submits https://example.invalid/report. The application's initial allowlist might approve this because the DNS lookup returns a public IP address.
-
Server Responds with a Redirect: The server processes the request and responds with a redirect to http://169.254.169.254/latest/meta-data/. This IP is part of the Google Compute Engine metadata service, which is typically restricted to internal networks.
-
Fetch Client Follows Redirect: The application's fetch client automatically follows the redirect without re-evaluating the allowlist. The original approval for example.invalid silently grants authority to contact a completely different host.
This is the SSRF boundary many URL-preview and import services miss: a URL is not one network destination. DNS resolution can resolve a public hostname to a private IP, and HTTP redirects can chain through multiple hosts, eventually reaching a restricted network.
Why This Matters
1. Bypassing Allowlists
Traditional SSRF defenses often rely on allowlists (whitelist of trusted hosts). If an attacker can craft a URL that resolves to a trusted domain but redirects to a private IP, the allowlist is bypassed.
2. Access to Sensitive Data
Private networks often contain sensitive data or services. An attacker who can redirect a fetch request to http://10.0.0.1/admin could potentially gain administrative access or exfiltrate data.
3. Chaining Through Multiple Redirects
An attacker could create a series of redirects, each stepping closer to the target network. For example:
- Initial URL resolves to a trusted public host.
- First redirect to a semi-trusted host.
- Second redirect to a private IP within the target network.
Each redirect appears legitimate, making detection and mitigation harder.
Mitigation Strategies
1. Deep Inspection of Redirects
Do not blindly follow redirects. Inspect the final destination before executing the fetch. For example, in a Node.js application using fetch, you can intercept redirects:
const fetch = require('node-fetch');
async function fetchWithRedirects(url) {
const response = await fetch(url, {
redirect: 'manual' // This allows manual handling of redirects
});
if (response.type === 'opaqueredirect') {
// Handle the redirect manually
console.log('Redirect intercepted:', response.url);
// Do not follow the redirect automatically
return null;
}
return response;
}
// Example usage
fetchWithRedirects('https://example.invalid/report')
.then(response => {
if (!response) {
console.log('Fetch canceled due to redirect');
} else {
response.text().then(console.log);
}
});
2. Block Private IP Ranges
Block requests that resolve to private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, etc.). This can be done at the network level or within the application:
const isPrivateIP = (ip) => {
const privateRanges = [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16'
];
return privateRanges.some(range => {
const [base, prefix] = range.split('/');
const baseIp = parseInt(base.split('.').join(''), 16);
const ipNum = parseInt(ip.split('.').join(''), 16);
return ipNum >= baseIp && ipNum < (baseIp + (1 << (32 - parseInt(prefix))));
});
};
// Example usage
const ip = '10.0.0.1';
if (isPrivateIP(ip)) {
console.log(`Blocked private IP: ${ip}`);
} else {
console.log(`Allowed IP: ${ip}`);
}
3. Disable DNS Resolution for Fetch
Some fetch libraries allow disabling DNS resolution for certain requests. For example, in Go, you can use net/http with a custom Dialer:
package main
import (
"net/http"
"net"
"time"
)
func main() {
client := &http.Client{
Transport: &http.Transport{
Dial: func(network, address string) (net.Conn, error) {
if net.ParseIP(address).IsLoopback() || net.ParseIP(address).IsPrivate() {
return nil, fmt.Errorf("blocked")
}
return net.Dial(network, address)
},
},
}
// Example request
resp, err := client.Get("https://example.invalid/report")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// Process response
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
4. Use a Web Application Firewall (WAF)
A WAF can help detect and block malicious redirect chains. Look for rules that identify patterns of chained redirects or requests to private IP ranges.
Conclusion
SSRF redirect chains are a sophisticated evasion technique that can bypass traditional SSRF defenses. By understanding how these chains work and implementing deep inspection, blocking private IP ranges, disabling DNS resolution for fetch, and leveraging WAFs, you can significantly reduce the risk of SSRF exploits. Always assume that any external input could lead to unexpected network destinations and validate every step of the request lifecycle.
Takeaway
Never trust the initial destination of a URL. Always inspect redirects and block access to private networks to prevent SSRF redirect chains from compromising your application.