j0k3r / httplug-ssrf-plugin
HTTPlug 的服务器端请求伪造(SSRF)防护插件
v3.0.0
2023-04-18 06:11 UTC
Requires
- php: >=7.4
- php-http/client-common: ^2.6
- php-http/discovery: ^1.15
- php-http/message: ^1.13
- psr/http-factory: ^1.0
- psr/http-message: ^1.0 || ^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.16
- guzzlehttp/guzzle: ^7.5
- guzzlehttp/psr7: ^2.0
- php-http/guzzle7-adapter: ^1.0
- phpstan/extension-installer: ^1.2
- phpstan/phpstan: ^1.10
- phpstan/phpstan-phpunit: ^1.3
- symfony/phpunit-bridge: ^6.2.3
README
受SafeCurl启发,该插件旨在验证URL的每一部分与白名单或黑名单,以帮助在HTTPlug使用时防止服务器端请求伪造攻击。
URL的每一部分都会被拆分并验证,包括将域名解析为其IP地址。
安装
可以使用Composer将其包含在任何PHP项目中。
composer require j0k3r/httplug-ssrf-plugin
使用方法
use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\ServerSideRequestForgeryProtectionPlugin; use Http\Client\Common\PluginClient; use Http\Discovery\Psr18ClientDiscovery; $ssrfPlugin = new ServerSideRequestForgeryProtectionPlugin(); $pluginClient = new PluginClient( Psr18ClientDiscovery::find(), [$ssrfPlugin] );
如果URL无效,插件会抛出Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Exception\InvalidURLException
异常。
选项
默认选项是不允许访问任何私有IP地址,并且只允许HTTP(S)连接。
如果您想添加自己的选项(例如,将任何请求到您控制的域的黑名单),只需获取一个新的Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Options
对象,将其添加到白名单或黑名单中,并与方法调用一起传递。
域使用正则表达式语法表示,而IP、方案和端口使用标准字符串(IP地址可以用CIDR表示法指定)。
use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Options; use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\ServerSideRequestForgeryProtectionPlugin; use Http\Discovery\Psr17FactoryDiscovery; use Http\Discovery\Psr18ClientDiscovery; use Http\Client\Common\PluginClient; $options = new Options(); $options->addToList(Options::LIST_BLACKLIST, Options::TYPE_DOMAIN, '(.*)\.example\.com'); $pluginClient = new PluginClient( Psr18ClientDiscovery::find(), [new ServerSideRequestForgeryProtectionPlugin($options)] ); // This will throw an Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Exception\InvalidURLException\InvalidDomainException $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest('GET', 'https://www.example.com'); $response = $pluginClient->sendRequest($request); $options = new Options(); $options->setList(Options::LIST_WHITELIST, [Options::TYPE_SCHEME => ['https']]); $pluginClient = new PluginClient( Psr18ClientDiscovery::find(), [new ServerSideRequestForgeryProtectionPlugin($options)] ); // This will be allowed, and return the response $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest('GET', 'https://www.example.com'); $response = $pluginClient->sendRequest($request); // This will throw an Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Exception\InvalidURLException\InvalidDomainException $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest('GET', 'https://www.example.com'); $response = $pluginClient->sendRequest($request);
可选保护
除了标准检查外,还有两个可选的保护。
第一个是防止DNS重绑定攻击。这可以通过在Options
对象上调用enablePinDns
方法来启用。这个问题的一个主要问题是SSL证书无法验证。这是由于在Host
头中发送了实际的主机名,并且URL使用了IP地址。
$options = new Options(); $options->enablePinDns();
第二个是禁用URL中的凭据使用,因为PHP的parse_url
返回的值与cURL使用的值不同。这是一个临时修复。
use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Options; use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\ServerSideRequestForgeryProtectionPlugin; use Http\Discovery\Psr17FactoryDiscovery; use Http\Discovery\Psr18ClientDiscovery; use Http\Client\Common\PluginClient; $options = new Options(); $options->disableSendCredentials(); //This will throw an Http\Client\Exception\RequestException $pluginClient = new PluginClient( Psr18ClientDiscovery::find(), [new ServerSideRequestForgeryProtectionPlugin($options)] ); $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest('GET', 'https://user:[email protected]'); $response = $pluginClient->sendRequest($request);
注意事项
由于该库使用gethostbynamel
来解析域名,它不兼容IPv6,因此该类目前只能与IPv4一起使用。