ivan-magda/swift-ssrf-guard

GitHub: ivan-magda/swift-ssrf-guard

一个纯 Swift 编写的 IP 地址出站安全分类器,用于检测并拦截指向私有网络或保留地址段的请求,防止 SSRF 攻击。

Stars: 0 | Forks: 0

# SSRFGuard [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/49/4951584a2c5d06216265dc18d0ad9f8e7e095b31309e0c44fb008d2022b1443e.svg)](https://github.com/ivan-magda/swift-ssrf-guard/actions/workflows/swift.yml) [![Swift 6.0](https://img.shields.io/badge/Swift-6.0-orange.svg)](https://swift.org) [![Platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS%20%7C%20visionOS%20%7C%20Linux-blue.svg)](https://swift.org) [![SPM Compatible](https://img.shields.io/badge/SPM-compatible-brightgreen.svg)](https://swift.org/package-manager) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) 判断某个 IP 地址是安全的出站目标,还是指向内部私有网络的目标。SSRFGuard 会在 IPv4 和 IPv6 中拒绝私有、环回、链路本地、运营商级 NAT 以及其他保留地址段。它会解包 IPv4 映射地址和 NAT64 地址,从而防止通过等效的 IPv6 表示形式让私有目标逃避检查,并且在遇到格式错误的输入时会直接拒绝。它是纯 Swift 实现,没有第三方运行时依赖,并且在 Apple 平台和 Linux 上运行效果完全一致。 ``` import SSRFGuard let classifier = EgressClassifier() // strict defaults if let address = ResolvedAddress.parse("169.254.169.254") { classifier.isAllowed(address) // false: the link-local cloud-metadata endpoint } ``` ## 目录 - [背景](#background) - [功能](#features) - [环境要求](#requirements) - [安装](#installation) - [用法](#usage) - [工作原理](#how-it-works) - [项目结构](#project-structure) - [贡献](#contributing) - [License](#license) ## 背景 当服务器代表他人获取 URL 时,如果攻击者引导该请求指向内部目标(例如:环回接口、私有子网或像 `169.254.169.254` 这样的云元数据 endpoint),就会发生服务器端请求伪造(SSRF)。任何将用户输入转化为出站连接的功能,都迫使你必须判断解析后的目标究竟是一个公共地址还是一个内部地址。 SSRFGuard 诞生于一个个人助手守护进程,该进程需要为其所有者获取 URL,并且必须拒绝任何试图反向访问主机内部的行为。它非常轻量,并且对其作用范围非常明确:它只负责对地址进行分类。它不会打开 socket、遵循重定向或固定连接,它本身也不能单独作为针对 DNS 重绑定(rebinding)的防御手段。为此,请将其与连接建立阶段的固定(pinning)机制结合使用(参见上面的说明)。 SSRFGuard 专门处理那些极其容易出错的边缘情况:它会将 IPv4 映射地址、NAT64 以及 IPv4 兼容的 IPv6 形式解包为内嵌的 IPv4 并重新检查;能够正确识别那些被 `inet_pton` 拒绝但仍被 `getaddrinfo` 解析的旧版数字 IPv4 表示形式并将其视为字面量;对于格式错误的地址,它会直接拒绝而不会予以放行。 ## 功能 - **默认严格。** `EgressPolicy.strict` 会阻止 IPv4 和 IPv6 中的私有、环回、链路本地、CGNAT、基准测试、文档、多播以及保留地址段。 - **防绕过规范化。** IPv4 映射地址、NAT64 (`64:ff9b::/96`) 以及 IPv4 兼容的 IPv6 地址会被解包为其内嵌的 IPv4 地址并重新检查,从而防止利用 IPv6 表示形式将私有目标偷渡过去。 - **安全失败。** 格式错误的地址(例如不是 16 个字节的 IPv6 值)会被坚决拒绝,绝不放行。 - **可配置策略。** 您可以在严格的地址段集合之上叠加自己的 CIDR,也可以直接提供您自己的列表。 - **强类型判定。** 通过 `Classification` 可得知地址被拒绝的具体原因:是匹配到了哪个地址段,还是输入格式有误。 - **独立线程池解析器。** 内置的解析器在 Dispatch 的 worker 上执行 `getaddrinfo`,因此即使 DNS 查找发生停滞,也永远不会阻塞 Swift 并发池中的固定线程。 - **无第三方运行时依赖。** 仅纯粹依赖 libc、Dispatch 和 Foundation。在 Linux 上也能顺利通过构建和测试。 ## 环境要求 - iOS 16.0+, macOS 13.0+, tvOS 16.0+, watchOS 9.0+, visionOS 1.0+, 或 Linux - Swift 6.0+ / Xcode 16+ ## 安装 ### Xcode 在 Xcode 中,打开 **File -> Add Package Dependencies…**,输入仓库 URL,然后将 `SSRFGuard` 库添加到您的 target 中: ``` https://github.com/ivan-magda/swift-ssrf-guard ``` ### Package.swift 将该 package 添加到您的依赖项中: ``` dependencies: [ .package(url: "https://github.com/ivan-magda/swift-ssrf-guard", from: "1.0.0") ] ``` 然后将 `SSRFGuard` 添加到您的 target 中: ``` .target( name: "YourTarget", dependencies: [ .product(name: "SSRFGuard", package: "swift-ssrf-guard") ] ) ``` ## 用法 ### 对地址进行分类 `EgressClassifier` 默认使用 `EgressPolicy.strict`。`classify(_:)` 会返回一个类型化的判定结果;`isAllowed(_:)` 则是返回是/否的简化形式。 ``` import SSRFGuard let classifier = EgressClassifier() guard let address = ResolvedAddress.parse("10.0.0.1") else { return } switch classifier.classify(address) { case .allowed: connect(to: address) case .blocked(.matchedRange(let range)): log("refused: \(address) is in \(range)") // refused: 10.0.0.1 is in 10.0.0.0/8 case .blocked(.malformed): log("refused: malformed address") } ``` ### 解析主机后再进行分类 将主机解析为具体的地址,对每一个进行分类,并且只连接到您已分类为允许的地址。切勿通过主机名进行连接:那会再次引入上文所述的重绑定漏洞。 ``` let resolver = SystemAddressResolver() let classifier = EgressClassifier() let addresses = try await resolver.resolve(host: "example.com") for address in addresses where classifier.isAllowed(address) { connect(to: address) // pin the socket to this exact address } ``` ### 自定义被阻止的地址段 在默认的严格集合之上叠加额外的地址段,或者从头构建一个策略。 ``` if let corporate = CIDR.parse("203.0.113.0/24") { let classifier = EgressClassifier(policy: .strict.adding([corporate])) } let openPolicy = EgressPolicy(blockedRanges: []) // blocks nothing except malformed input ``` ### 在脱离网络的情况下进行测试 注入一个预编排的(scripted) `AddressResolving`,以确定性地驱动测试运行。 ``` struct ScriptedResolver: AddressResolving { let table: [String: [ResolvedAddress]] func resolve(host: String) async throws -> [ResolvedAddress] { guard let addresses = table[host] else { throw AddressResolutionError.unresolvable(host: host) } return addresses } } ``` ## 工作原理 1. **规范化。** 对于 IPv6 地址,将其 IPv4 映射、NAT64 或 IPv4 兼容形式解包为内嵌的 IPv4。如果发现值不是 16 个字节,则作为格式错误予以拒绝。 2. **策略检查。** 使用规范化的地址与策略中的每个 `CIDR` 进行比对测试。第一个包含该地址的范围即生效,分类器会将该范围作为结果反馈回来。 3. **返回判定结果。** 如果没有匹配到任何范围则返回 `.allowed`,否则返回 `.blocked` 及其具体原因。 匹配过程是严格按照地址族进行的:IPv4 映射的 IPv6 形式本身永远不会去匹配 IPv4 的拦截规则,这就是为什么必须先通过规范化步骤对其进行解包的原因。这种执行顺序可以阻止 `64:ff9b::7f00:1` 在 NAT64 网络中实际连接到 `127.0.0.1`。 ## 项目结构 ``` Sources/SSRFGuard/ ├── Address/ # ResolvedAddress and CIDR value types ├── Classifier/ # EgressClassifier, EgressPolicy, and the Classification verdict └── Resolver/ # the AddressResolving seam and the getaddrinfo default ``` ## License 在 MIT License 下发布。详情请参阅 [LICENSE](LICENSE)。
标签:CISA项目, IPv4/IPv6, IP地址分类, SSRF防护, Swift, 网络安全, 隐私保护