kwent/faraday-ssrf-filter
GitHub: kwent/faraday-ssrf-filter
一款 Faraday HTTP 客户端中间件,通过在请求发出前校验解析出的 IP 地址来防止 SSRF 攻击。
Stars: 1 | Forks: 0
# Faraday SSRF Filter
[](https://rubygems.org/gems/faraday-ssrf-filter)
一个 [Faraday](https://github.com/lostisland/faraday) 中间件,它通过在允许请求继续之前,根据已知的私有和保留 IP 范围验证解析的 IP 地址,来防止服务端请求伪造 (SSRF) 攻击。
受 [ssrf_filter](https://github.com/arkadiyt/ssrf_filter)(直接使用 `Net::HTTP`)启发,这个 gem 将相同级别的 SSRF 保护带入了任何基于 Faraday 的 HTTP 客户端。
## 功能
- 拦截指向**所有私有/保留 IPv4 和 IPv6 范围**的请求(RFC 1918、RFC 6598、loopback、link-local、multicast 等)
- 检测 **IPv4-mapped/compatible/translated IPv6 地址**(例如 `::ffff:127.0.0.1`)
- 检测 **NAT64 well-known prefix** 地址(例如 `64:ff9b::10.0.0.1`)
- **带有 IP pinning 的 DNS 解析**,以防止 DNS rebinding 攻击
- 默认拦截**直接使用 IP 地址**
- 可配置的 **allowlist/denylist** 以进行细粒度控制
- 支持**自定义 DNS 解析器**
- **Scheme 验证**(默认情况下仅支持 `http`/`https`)
- 适用于**所有 Faraday adapter**
## 安装说明
将其添加到您的 Gemfile 中:
```
gem 'faraday-ssrf-filter'
```
或者直接安装:
```
gem install faraday-ssrf-filter
```
## 使用方法
### 基本用法
```
require 'faraday/ssrf_filter'
conn = Faraday.new(url: 'https://api.example.com') do |f|
f.request :ssrf_filter
f.adapter Faraday.default_adapter
end
# 安全请求正常工作
response = conn.get('/data')
# 对私有 IP 的请求被阻止
# 例如,如果 evil.com 解析到 127.0.0.1
# => 抛出 Faraday::SsrfFilter::PrivateIPError
```
### 配置选项
```
conn = Faraday.new(url: 'https://api.example.com') do |f|
f.request :ssrf_filter,
# Allow specific private ranges (e.g., internal services)
allowlist: ['10.0.0.0/8'],
# Block specific public ranges
denylist: ['93.184.216.0/24'],
# Allow direct IP addresses in URLs (default: false)
allow_ip_addresses: true,
# Restrict allowed URI schemes (default: ['http', 'https'])
allowed_schemes: %w[http https],
# Custom DNS resolver (default: Resolv.getaddresses)
resolver: ->(hostname) { Resolv.getaddresses(hostname) }
f.adapter Faraday.default_adapter
end
```
### 错误处理
所有错误均继承自 `Faraday::SsrfFilter::SSRFError`(该错误继承自 `Faraday::Error`):
```
begin
conn.get('/data')
rescue Faraday::SsrfFilter::PrivateIPError => e
# Hostname resolved to a private/reserved IP
rescue Faraday::SsrfFilter::DirectIPError => e
# URL contains a direct IP address (blocked by default)
rescue Faraday::SsrfFilter::InvalidSchemeError => e
# URI scheme not in allowed list
rescue Faraday::SsrfFilter::DNSResolutionError => e
# Could not resolve hostname
rescue Faraday::SsrfFilter::UnsafeRedirectError => e
# Response redirects to a private/reserved IP or disallowed scheme
rescue Faraday::SsrfFilter::SSRFError => e
# Catch-all for any SSRF error
end
```
## 被拦截的 IP 范围
### IPv4
| 范围 | 用途 |
|---|---|
| `0.0.0.0/8` | Current network |
| `10.0.0.0/8` | Private (RFC 1918) |
| `100.64.0.0/10` | Carrier-grade NAT (RFC 6598) |
| `127.0.0.0/8` | Loopback |
| `169.254.0.0/16` | Link-local (包括云元数据 endpoint) |
| `172.16.0.0/12` | Private (RFC 1918) |
| `192.0.0.0/24` | IETF 协议分配 |
| `192.0.2.0/24` | TEST-NET-1 |
| `192.168.0.0/16` | Private (RFC 1918) |
| `198.18.0.0/15` | Benchmarking |
| `198.51.100.0/24` | TEST-NET-2 |
| `203.0.113.0/24` | TEST-NET-3 |
| `224.0.0.0/4` | Multicast |
| `240.0.0.0/4` | Reserved |
| `255.255.255.255/32` | Broadcast |
### IPv6
| 范围 | 用途 |
|---|---|
| `::/128` | Unspecified |
| `::1/128` | Loopback |
| `100::/64` | Discard prefix |
| `2001::/32` | Teredo tunneling |
| `2001:2::/48` | Benchmarking |
| `2001:10::/28` | ORCHID |
| `2001:20::/28` | ORCHIDv2 |
| `2001:db8::/32` | Documentation |
| `2002::/16` | 6to4 tunneling |
| `3fff::/20` | Documentation |
| `5f00::/16` | Segment Routing (SRv6) |
| `fc00::/7` | Unique local |
| `fe80::/10` | Link-local |
| `ff00::/8` | Multicast |
| `64:ff9b:1::/48` | NAT64 local prefix |
此外,所有 IPv4 黑名单范围在其 IPv4-compatible (`::x.x.x.x`)、IPv4-mapped (`::ffff:x.x.x.x`)、IPv4-translated (`::ffff:0:x.x.x.x`) 和 NAT64 (`64:ff9b::x.x.x.x`) 的 IPv6 表示形式中也会被拦截。
## 工作原理
1. **Scheme 验证** — 默认仅允许 `http` 和 `https`
2. **直接 IP 拦截** — 默认拦截使用 IP 地址而不是 hostname 的 URL
3. **DNS 解析** — 使用 `Resolv.getaddresses` 将 hostname 解析为 IP 地址
4. **IP 验证** — 根据全面的 denylist 检查每个解析出的 IP
5. **Hostname 替换 (HTTP)** — 对于 HTTP 请求,URL 的 hostname 会替换为已验证的 IP,并将 `Host` header 设置为原始 hostname,从而防止 DNS rebinding
6. **TLS 保留 (HTTPS)** — 对于 HTTPS 请求,在 URL 中保留 hostname 以维持正确的 TLS SNI 和证书验证。解析出的 IP 将存储在 `X-Faraday-SSRF-Resolved-IP` header 中
7. **重定向验证** — 检查重定向响应(带有 `Location` header 的 3xx 状态码)。重定向目标会被解析并根据相同的 denylist 进行验证,如果指向私有/保留 IP,则会引发 `UnsafeRedirectError`
## 中间件顺序
当使用跟随重定向的中间件(例如 `faraday-follow_redirects`)时,请将其放置在 SSRF 过滤器**之前**,以便对每次重定向进行验证:
```
conn = Faraday.new(url: 'https://api.example.com') do |f|
f.response :follow_redirects # outer — follows redirects
f.request :ssrf_filter # inner — validates each request including redirects
f.adapter Faraday.default_adapter
end
```
无论中间件顺序如何,SSRF 过滤器还会作为纵深防御措施验证响应中的重定向 `Location` header。
## 致谢
这个 gem 深受 [Arkadiy Tetelman](https://github.com/arkadiyt) 的 [ssrf_filter](https://github.com/arkadiyt/ssrf_filter) 启发。全面的 IP 黑名单、IPv4-mapped IPv6 检测以及 NAT64 处理均基于他出色的作品。感谢您为 Ruby 生态系统构建了如此全面且经过充分测试的 SSRF 保护库。
## 许可证
MIT 许可证。有关详情,请参阅 [LICENSE](LICENSE)。
标签:IP过滤, Ruby, SSRF防御, Syscall, Web开发, 中间件, 安全防护, 知识库, 网络请求