PHP Curl Security Hardening

Published On22 Jan 2021

PHP Curl Security Hardening

Curl is powerful and feature-rich command line tool and a library for data transferring with URLs, with support for several protocols including HTTP and HTTPS, among dozens of others. It supports modern features such as QUIC and TLS 1.3, and it comes with several customization options.

PHP curl extension integrates libcurl, the underlying core of Curl with PHP. curl extension supports a majority of features offered by libcurl, and that level of customizability requires careful tweaking and security hardening.

Daniel Stenberg and hundreds of other bright contributors spend many hours developing new features and fixing security issues, but not all servers might run the latest version of Curl, which means it is necessarily to proactively guard against potential security vulnerabilities.


Curl Protocols

Curl supports more than 25 protocols, and PHP in turn supports many of the protocols Curl supports. This not only includes the widely used HTTP and HTTPS protocols, but also FTP, file, SCP, LDAP, and many others.

Curl will happily accept any protocol it supports, and there lies the first potential security vulnerability.

When using Curl with a user-provided URL, it is absolutely necessary to validate the provided URL, because not all URLS are HTTP/HTTPS URLS.

An application that accepts a user-provided URL, and then return or make it accessible otherwise to the user opens a Server-Side Request Forgery vulnerability.

$url = $_POST['feed_url'];
$ch = curl_init($url);
curl_exec($ch);

The snippet above might not be harmful if the provided URL is a safe https:// URL. However, because there is no validation for it, an attacker providing a crafted URL can perform an SSRF attack.

$url = 'file:///etc/passwd'
$ch = curl_init($url);
curl_exec($ch);

From PHP 8.0 and later, curl_init function returns a CurlHandle object instead of a resource object. This is one of the steps in PHP's Resource to Object migration.

Because the Curl request is made at the web server, it might bypass firewalls and use internal networks that must not be accessed by outsiders.

For example, an FTP server that is not connected to the Internet, but internally networked to the web server might accept connections if the user provides a URL such as ftps://192.168.1.15/secrets.txt.

Hardening: Limiting allowed protocols

Curl supports CURLOPT_PROTOCOLS option that accepts a bitmask of protocols Curl should only accept.

curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);

By default, CURLOPT_PROTOCOLS is configured to accept any URL, including file://. It's important to limit protocols Curl accepts as a last-line defense. All CURLPROTO_* constants are documented in curl_setopt documentation.

Since Curl 7.85, the CURLOPT_PROTOCOLS option is deprecated. PHP does not emit any deprecation notices, but on PHP builds with Curl 7.85 or later, consider using the replacement CURLOPT_PROTOCOLS_STR option (available since PHP 8.3):

- curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
+ curl_setopt($ch, CURLOPT_PROTOCOLS_STR, "HTTPS,HTTP");

Redirects to Malicious URLs

Curl has a feature that it automatically follows HTTP redirects. This opens an attack vector that a user-provided URL appears to be a harmless URL, but the remote server issues an HTTP redirect response that Curl automatically follows.

For example, the user might provide a valid https:// URL, but the remote server responds with an HTTP redirect that leads to file://, ftp://, or other URL scheme that the application might not expect.

Since libcurl 7.19.4, it no longer allows ftp:// and scp:// URLs for redirects, but it still allows other protocols, which might open a security vulnerability.

Curl does not follow redirects by default, and it requires CURLOPT_FOLLOWLOCATION option to be enabled. However, it is fairly common to see user-land PHP code that enables CURLOPT_FOLLOWLOCATION option.

Hardening: Not enabling FOLLOWLOCATION or limiting protocols

By default, Curl does not follow redirects. Unless it is absolutely necessary, do not enable the CURLOPT_FOLLOWLOCATION option.

As mentioned in the section above, setting CURLOPT_PROTOCOLS is a good measure against malicious protocols; Curl will refuse to redirect to a location if it is not allowed with CURLOPT_PROTOCOLS option.

Additionally, Curl supports CURLOPT_REDIR_PROTOCOLS option to further restrict the allowed redirect URL protocols. It accepts a CURLPROTO_* value bitmask. If this option is not set, it will inherit from CURLOPT_PROTOCOLS.

curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);

In the example above, Curl will accept http:// and https:// URL as the initial URL. However, it will refuse to follow if the remote server responds with a redirect to a http:// URL.

Since Curl 7.85, the CURLOPT_REDIR_PROTOCOLS option is also deprecated. On PHP 8.3 or later, if built with libcurl 7.85 or later, the new CURLOPT_REDIR_PROTOCOLS_STR is a replacement:

- curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
+ curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS_STR, "HTTPS");

Do not, under any circumstances, use CURLPROTO_ALL, CURLPROTO_FILE, or any undesired protocols for CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS options as it would allow an attacker to access arbitrary files, internal networks, or undesired URL schemes.

Infinite Redirects

Another attack vector with automatic Curl redirects is that the target server can respond with a long redirect sequence, or worse, a redirect loop.

PHP internally provides protection against this by setting the default value for CURLOPT_MAXREDIRS option to 20.

The PHP documentation for CURLOPT_FOLLOWLOCATION currently warns that PHP will follow unlimited number of redirects. That information inaccurate, and PHP in fact has a default CURLOPT_MAXREDIRS value set to 20.

If automatic redirects are enabled, and if the max-redirects limitation is taken off, that will effectively halt the application indefinitely because of the synchronous nature of the standard Curl requests.

Hardening: Set a strict max-redirect limit

PHP internally sets a default value of 20 to prevent to prevent infinite direction loops. However, this limit is excessive for most web applications.

CURLOPT_MAXREDIRS accepts an integer larger than or equal to -1.

-1 allows an infinite number of redirects; 0 disables redirects entirely. Setting it to a sane default such as 3 or 5 is ideal.

curl_setopt($ch, CURLOPT_MAXREDIRS, 5);

Time-outs

By default, Curl does not enforce a time-out for a request. PHP's default_socket_timeout INI setting does not affect Curl's time-out either.

Unless a PHP max_execution_time is exhausted, or the networking stack of the operating system decides terminate the request, it is possible for a malicious or malfunctioning remote server to keep a Curl request waiting indefinitely by simply not sending any data after it receives the request.

Hardening: Set a strict time-out

CURLOPT_TIMEOUT option accepts an integer value to the amount of seconds Curl can wait per request.

curl_setopt($ch, CURLOPT_TIMEOUT, 10);

Note that this time-out applies to the total time even if Curl is configured to follow redirects. It is not the time-out value for each redirect if the server responds with redirect responses.

TLS Certificate Validation

Curl does an excellent job by setting sane defaults for TLS (HTTP) certification validation. However, there are several bad examples and quick fixes in most PHP applications that they force Curl to not validate the certificate (by checking the domain names the certificate is issued for) and its peers (certificate authority).

Hardening: Do not disable certification validation

Curl is configured by default to validate the host and peer. The best practice is to not tamper the default values.

However, to enforce the options are correctly set, it is possible to explicitly set CURLOPT_SSL_VERIFYPEER and CURLOPT_SSL_VERIFYHOST.

curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); 
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
  • CURLOPT_SSL_VERIFYHOST makes Curl verify the domain names the certificate was issued for is present (value 1), and includes the connecting domain name (value 2). A common mistake with this option is that value is set to 1. It must be 2, to effectively validate the certificate. Newer versions of Curl in fact alias the option 1 to 2 to to overcome this misconception.
  • CURLOPT_SSL_VERIFYPEER enables verifying the certificate issuer. Setting this option to true means Curl will cryptographically verify that the certificate was issued by a Certificate Authority (CA) that Curl trusts. If set to false, Curl will accept self-signed certificates, that provides little to no protection.

How to fix PHP Curl HTTPS Certificate Authority issues on Windows On Windows, setting CURLOPT_SSL_OPTIONS = CURLSSLOPT_NATIVE_CA helps alleviate potential issues when the Certificate Authority bundle file is not set.

Insecure SSL and TLS versions

Curl supports SSL v2, v3, and TLS versions 1.0 through 1.3. Only TLS 1.2 and TLS 1.3 are considered secure today.

In fact, most browsers no longer accept connections to SSL, and TLS 1.0 and TLS 1.1. Even the browsers that continue to support those deprecated TLS 1.0 and TLS 1.1 versions do so with severe warnings.

Curl will pick the most secure TLS version when it establishes a secure connection, however, Curl still supports the insecure TLS 1.0 and 1.1 versions for compatibility reasons.

It is possible to drop support for insecure TLS versions, and only support TLS 1.2 and up (with TLS 1.3 being the latest) to ensure Curl does not use potentially insecure cipher-suits and protocols.

Hardening: Disable insecure SSL and TLS versions

CURLOPT_SSLVERSION Curl option controls the bounds of SSL and TLS versions Curl should support.

curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); 

CURLOPT_SSLVERSION accepts one of the *`CURLSSLVERSION** [values](https://www.php.net/manual/function.curl-setopt.php). It is highly recommended to *not* set one of theCURL_SSLVERSIONMAX*` values as they would effectively prevent Curl from using modern protocols.

CURL_SSLVERSION_TLSv1_2 is the ideal value, because it disables all insecure protocols (namely, SSL v2, v3, and TLS 1.0 and 1.1), and enables all current TLS versions (TLS 1.2 and later).

Note that the TLS version support depends on the underlying OpenSSL version Curl was built with. A server operating system upgrade is highly recommended if the system does not support TLS 1.2.


Following are some of the extra-mile security hardening steps. However, they require the remote server to be correctly configured for them to work, and PHP built with newer Curl versions. Use them with caution!

Certificate Revocation Checks

Curl supports mechanisms such as OCSP that can check with the certificate issuer if the certificate is still valid.

An enhancement to Curl requests would be to query OCSP status of the server certificate to ensure that the certificate is not revoked.

Note that Curl does not enable OCSP checking by default. If the server fails to provide a valid OCSP response, Curl will hard fail, and it might break many legitimate web sites too.

Extra Hardening: Require Stapled OCSP responses

When CURLOPT_SSL_VERIFYSTATUS option is set to true, Curl validates that the server staples an OCSP response during the TLS handshake. Web sites such as yours truly has that feature, but not all servers do.

curl_setopt($ch, CURLOPT_SSL_VERIFYSTATUS, true);

Note that if CURLOPT_SSL_VERIFYSTATUS is enabled, and if the server does not staple a valid OCSP staple, Curl will refuse that connection. Not stapling an OCSP response does not mean the TLS connection is insecure.

Encrypted DNS Requests (DNS over HTTPS)

When Curl connects to a remote URL, it resolves that domain name to an IP address by using the system DNS resolver. By nature, DNS requests and responses can be eavesdropped or tampered with if an attacker manages to hijack the DNS server or the route to it.

While the system DNS resolver might be able to validate the authenticity of a DNS response with DNSSEC, it is not possible to prevent censorship or prevent them from being observed.

DNS-over-HTTPS, is a new standard that the HTTP client communicates with a DNS server using an encrypted HTTPS connection. This prevents the DNS requests and requests eavesdropping tampering.

Extra Hardening: DNS over HTTPS

In Curl versions 7.62 and later, Curl supports DNS-over-HTTPS, that uses an HTTPS connection to a DNS server of choice.

PHP 8.1 adds DNS over HTTPS in Curl, and it can use the DoH support if the underlying libcurl supports it.

if (defined('CURLOPT_DOH_URL')) {
    curl_setopt($ch, CURLOPT_DOH_URL, 'https://cloudflare-dns.com/dns-query');
}

The example above configures Curl to use CloudFlare's 1.1.1.1 DNS service if Curl supports DoH.

Taking it a step ahead, specialty DNS servers such as ones that block malware domains might be of interest as well.

HTTP Strict Transport Security (HSTS)

Latest version of Curl adds support for HTTP Strict Transport Security. It is not supported in the PHP curl extension yet, but it might in a future version.

HSTS allows the server to send an HTTP header that asks the client to remember to always use HTTPS in the future, and never use HTTP for a specified time period. Taking it a step ahead, Curl will support HSTS Preload feature, that allows Curl to refer a static file that lists all domains it should always connect over HTTPS.

For example, once again, yours truly PHP.Watch sends HSTS header, and even supports HSTS Preload by including php.watch in almost all browser HSTS Preload lists to always use HTTPS. Curl's HSTS and HSTS Preload support means Curl can refer to a similar list of domains, and make sure it will always use HTTPS when connecting to that domain. This works even if the initial URL or redirect URLs are http://.


Summary

  1. Limit Curl Protocols
    curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
  2. Do not enable automatic redirects unless absolutely necessary
  3. If redirects are enabled enabled, limit allowed protocols (if different from #1 above).
    curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
  4. If redirects are enabled, set a strict limit
    curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
  5. Set a strict time-out
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
  6. Do not disable certification validation, or enforce it
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); 
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
  7. Disable insecure SSL and TLS versions
    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); 

Following are some additional hardening steps, that might not be for everyone.

  1. Require stapled OCSP responses

    curl_setopt($ch, CURLOPT_SSL_VERIFYSTATUS, true);
  2. Use DNS over HTTPS (PHP >= 8.1)

    if (defined('CURLOPT_DOH_URL')) {
    curl_setopt($ch, CURLOPT_DOH_URL, 'https://cloudflare-dns.com/dns-query');
    }
  3. HSTS and HSTS Preload (Upcoming)

Recent Articles on PHP.Watch

All ArticlesFeed 
PHP 8.4 Installation and Upgrade guide for Ubuntu and Debian

PHP 8.4 Installation and Upgrade guide for Ubuntu and Debian

A guide for Debian and Ubuntu on how to install PHP 8.4 on a new server or how to upgrade an existing PHP setup to PHP 8.4.
How to fix `mysql_native_password` not loaded errors on MySQL 8.4

How to fix mysql_native_password not loaded errors on MySQL 8.4

How to fix the SQLSTATE[HY000] [1524] Plugin 'mysql_native_password' is not loaded errors caused in MySQL 8.4 no longer enabling the mysql_native_password plugin by default.
How to fix PHP Curl HTTPS Certificate Authority issues on Windows

How to fix PHP Curl HTTPS Certificate Authority issues on Windows

On Windows, HTTPS requests made with the Curl extension can fail because Curl has no root certificate list to validate the server certificates. This article discusses the secure and effective solutions, and highlights bad advice that can leave PHP applications insecure.
Subscribe to PHP.Watch newsletter for monthly updates

You will receive an email on last Wednesday of every month and on major PHP releases with new articles related to PHP, upcoming changes, new features and what's changing in the language. No marketing emails, no selling of your contacts, no click-tracking, and one-click instant unsubscribe from any email you receive.

Support PHP.Watch — If you find the articles, version information, Codex, and other PHP.Watch contributions useful, consider supporting through GitHub Sponsors. Your sponsorship helps dedicate more time to creating valuable content and improving the PHP community. Together, we can keep the momentum going — thank you for your support!

Thanks to the highest tier sponsor: @TomasVotruba for your generous support to keep PHP.Watch moving 💜