kwent/faraday-ssrf-filter

GitHub: kwent/faraday-ssrf-filter

一款 Faraday HTTP 客户端中间件,通过在请求发出前校验解析出的 IP 地址来防止 SSRF 攻击。

Stars: 1 | Forks: 0

# Faraday SSRF Filter [![Gem Version](https://badge.fury.io/rb/faraday-ssrf-filter.svg)](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开发, 中间件, 安全防护, 知识库, 网络请求