SafeCurl
, (*1)
SafeCurl intends to be a drop-in replacement for the curl_exec function in PHP. SafeCurl validates each part of the URL against a white or black list, to help protect against Server-Side Request Forgery attacks., (*2)
For more infomation about the project see the blog post 'SafeCurl: SSRF Protection, and a "Capture the Bitcoins"'., (*3)
Protections
Each part of the URL is broken down and validated against a white or black list. This includes resolve a domain name to it's IP addresses., (*4)
If you chose to enable "FOLLOWLOCATION", then any redirects are caught, and re-validated., (*5)
Installation
SafeCurl can be included in any PHP project using Composer. Include the following in your composer.json
file under require
., (*6)
"require": {
"j0k3r\safecurl": "~3.0"
}
Then update Composer., (*7)
composer update
Usage
It's as easy as replacing curl_exec
and wrapping it in a try {} catch {}
block., (*8)
use fin1te\SafeCurl\SafeCurl;
use fin1te\SafeCurl\Exception;
try {
$url = 'http://www.google.com';
$curlHandle = curl_init();
//Your usual cURL options
curl_setopt($curlHandle, CURLOPT_USERAGENT, 'Mozilla/5.0 (SafeCurl)');
//Execute using SafeCurl
$safeCurl = new SafeCurl($curlHandle);
$response = $safeCurl->execute($url);
} catch (Exception $e) {
//URL wasn't safe
}
Options
The default options are to not allow access to any private IP addresses, and to only allow HTTP(S) connections., (*9)
If you wish to add your own options (such as to blacklist any requests to domains your control), simply get a new SimpleCurl\Options object, add to the white or black lists, and pass it along with the method calls., (*10)
Domains are express using regex syntax, whilst IPs, scheme and ports are standard strings (IPs can be specified in CIDR notation)., (*11)
use fin1te\SafeCurl\SafeCurl;
use fin1te\SafeCurl\Options;
$options = new Options();
$options->addToList('blacklist', 'domain', '(.*)\.fin1te\.net');
$options->addToList('whitelist', 'scheme', 'ftp');
$curlHandle = curl_init();
//This will now throw an InvalidDomainException
$safeCurl = new SafeCurl($curlHandle, $options);
$response = $safeCurl->execute('http://safecurl.fin1te.net');
//Whilst this will be allowed, and return the response
$safeCurl = new SafeCurl($curlHandle, $options);
$response = $safeCurl->execute('ftp://fin1te.net');
Since we can't get access to any already set cURL options (see Caveats section), to enable CURL_FOLLOWREDIRECTS
you must call the enableFollowRedirects()
method. If you wish to specify a redirect limit, you will need to call setMaxRedirects()
. Passing in 0
will allow infinite redirects., (*12)
$options = new Options();
$options->enableFollowLocation();
//Abort after 10 redirects
$options->setFollowLocationLimit(10);
URL Checking
The URL checking methods are also public, meaning that you can validate a URL before using it elsewhere in your application, although you'd want to try and catch any redirects., (*13)
use fin1te\SafeCurl\Url;
use fin1te\SafeCurl\Exception;
try {
$url = 'http://www.google.com';
$validatedUrl = Url::validateUrl($url);
$fullUrl = $validatedUrl['url'];
} catch (Exception $e) {
// URL wasn't safe
}
Optional Protections
In addition to the standard checks, two more are available., (*14)
The first is to prevent DNS Rebinding attacks. This can be enabled by calling the enablePinDns
method on an Options
object. There is one major issue with this - the SSL certificate can't be validated. This is due to the real hostname being sent in the Host
header, and the URL using the IP address., (*15)
$options = new Options();
$options->enablePinDns();
The second disables the use of credentials in a URL, since PHP's parse_url
returns values which differ from ones cURL uses. This is a temporary fix., (*16)
use fin1te\SafeCurl\SafeCurl;
use fin1te\SafeCurl\Exception;
use fin1te\SafeCurl\Options;
$options = new Options();
$options->disableSendCredentials();
$curlHandle = curl_init();
//This will throw an InvalidURLException
$safeCurl = new SafeCurl($curlHandle, $options);
$response = $safeCurl->execute('http://user:pass@google.com');
Cavets
Since SafeCurl uses gethostbynamel
to resolve domain names, which isn't IPv6 compatible, the class will only work with IPv4 at the moment. See Issue #1., (*17)
As mentioned above, we can't fetch the value of any cURL options set against the provided cURL handle. Because SafeCurl handles redirects itself, it will turn off CURLOPT_FOLLOWLOCATION
and use the value from the Options
object. This is also true of CURLOPT_MAXREDIRECTS
., (*18)