jeffersongoncalves/laravel-ssrf-guard
GitHub: jeffersongoncalves/laravel-ssrf-guard
一个 Laravel 安全包,通过公共 IP 校验、DNS 重绑定锁定和逐跳重定向复查来防止应用在获取不可信 URL 时遭受 SSRF 攻击。
Stars: 1 | Forks: 0

# Laravel SSRF Guard
[](https://packagist.org/packages/jeffersongoncalves/laravel-ssrf-guard)
[](https://github.com/jeffersongoncalves/laravel-ssrf-guard/actions?query=workflow%3Arun-tests+branch%3Amaster)
[](https://github.com/jeffersongoncalves/laravel-ssrf-guard/actions?query=workflow%3A"Fix+PHP+code+styling"+branch%3Amaster)
[](https://packagist.org/packages/jeffersongoncalves/laravel-ssrf-guard)
每当你的应用获取来自用户的 URL(例如头像 URL、webhook 目标、链接预览、导入的 `og:image`)时,它可能会被诱骗去访问**内部**服务:`http://localhost`、`http://10.0.0.1`,或云元数据端点 `http://169.254.169.254`。这就是**服务器端请求伪造(SSRF)**。
Laravel SSRF Guard 能够安全地获取不受信任的 URL。它的作用包括:
- 验证 URL 的 host 是否**仅**解析为公共 IP 地址,默认拒绝私有、保留、环回和链路本地地址范围(包括 IPv4 `A` 和 IPv6 `AAAA` 记录);
- 通过 curl 的 `CURLOPT_RESOLVE` 将连接**锁定**到已验证的 IP,从而消除 DNS 重绑定(TOCTOU)的时间窗口,防止域名在验证和连接之间切换到内部 IP;
- 执行安全的 `GET` 请求,该请求会跟随重定向,但会**重新验证每一跳**,因此公共 host 无法通过 `302` 将你重定向到内部地址。
## 安装说明
你可以通过 composer 安装此包:
```
composer require jeffersongoncalves/laravel-ssrf-guard
```
你可以使用以下命令发布配置文件:
```
php artisan vendor:publish --tag="ssrf-guard-config"
```
以下是发布后的配置文件:
```
return [
'timeout' => (int) env('SSRF_GUARD_TIMEOUT', 8),
'max_redirects' => (int) env('SSRF_GUARD_MAX_REDIRECTS', 3),
'allowed_schemes' => ['http', 'https'],
'allow_private' => (bool) env('SSRF_GUARD_ALLOW_PRIVATE', false),
];
```
## 用法
从容器中解析 guard(它已注册为 singleton),或直接实例化它。
### 检查 URL 是否可以安全获取
```
use JeffersonGoncalves\SsrfGuard\SsrfGuard;
$guard = app(SsrfGuard::class);
$guard->isPublicUrl('https://example.com'); // true
$guard->isPublicUrl('http://127.0.0.1'); // false (loopback)
$guard->isPublicUrl('http://10.0.0.1'); // false (private)
$guard->isPublicUrl('http://169.254.169.254'); // false (link-local / metadata)
$guard->isPublicUrl('ftp://example.com'); // false (scheme not allowed)
```
在存储或获取用户输入之前,使用它来验证:
```
if (! app(SsrfGuard::class)->isPublicUrl($request->input('webhook_url'))) {
abort(422, 'The URL must point to a public host.');
}
```
### 安全地获取 URL
`safeGet()` 会执行锁定 IP 且重新验证重定向的请求,并返回标准的 `Illuminate\Http\Client\Response`。如果 URL(或任何重定向跳转)不是公共地址,它会抛出 `JeffersonGoncalves\SsrfGuard\Exceptions\BlockedHostException`:
```
use JeffersonGoncalves\SsrfGuard\SsrfGuard;
use JeffersonGoncalves\SsrfGuard\Exceptions\BlockedHostException;
try {
$response = app(SsrfGuard::class)->safeGet($untrustedUrl);
$body = $response->body();
} catch (BlockedHostException $e) {
report($e);
// The URL pointed at a non-public host — refuse to proxy it.
}
```
你可以传递额外的 HTTP Client 选项;它们会与安全默认值合并:
```
$response = app(SsrfGuard::class)->safeGet($url, [
'headers' => ['Accept' => 'image/*'],
]);
```
### 获取 curl resolve 锁定记录
如果你构建自己的请求,并且只想要已验证的 `CURLOPT_RESOLVE` 条目,请调用 `resolveEntries()` —— 对于任何非公共/不支持的 URL,它会返回 `null`:
```
$resolve = app(SsrfGuard::class)->resolveEntries('https://example.com');
// ['example.com:443:93.184.216.34'] (or null if not public)
```
## 配置
| 键 | 默认值 | 描述 |
|-----|---------|-------------|
| `timeout` | `8` | `safeGet()` 请求的最大执行秒数。 |
| `max_redirects` | `3` | 要跟随的重定向跳转次数 —— 每一跳都会被重新验证。 |
| `allowed_schemes` | `['http', 'https']` | 被视为有效的 scheme;其他所有格式都会被拒绝。 |
| `allow_private` | `false` | **危险** —— 当设为 `true` 时,跳过私有/保留/环回/链路本地的检查。仅供本地开发使用;切勿在生产环境中启用。 |
## 测试
```
composer test
```
## 更新日志
请查看 [CHANGELOG](CHANGELOG.md) 了解最近的更改信息。
## 安全漏洞
请查看 [我们的安全策略](../../security/policy) 了解如何报告安全漏洞。
## 贡献者
- [Jèfferson Gonçalves](https://github.com/jeffersongoncalves)
- [所有贡献者](../../contributors)
## 开源协议
MIT 许可证 (MIT)。请查看 [许可文件](LICENSE.md) 获取更多信息。标签:CISA项目, ffuf, Laravel, OpenVAS, PHP, SSRF防护, TLS, Web安全, 网络安全, 蓝队分析, 防御工具, 隐私保护