Skip to content

Verify TLS certificates and hostnames by default - #587

Merged
Vitorinox merged 2 commits into
masterfrom
fix/ssl-verify-575
Oct 31, 2026
Merged

Vitorinox merged 2 commits into
masterfrom
fix/ssl-verify-575

Conversation

@Vitorinox

Copy link
Copy Markdown
Collaborator

Fixes #575

Problem

CurlDispatcher fell back to CURLOPT_SSL_VERIFYPEER => false and CURLOPT_SSL_VERIFYHOST => 0 when ssl_verify_peer / ssl_verify_host were not set, so every HTTPS request accepted any certificate by default.

Fix

  • Defaults are now ssl_verify_peer true and ssl_verify_host 2.
  • Explicit opt-out is unchanged: 0, false, '0' and '' still disable each check, with the same normalisation as before (no PHP notice for 1/true on VERIFYHOST).
  • A null value now means "use the default" (verification on) instead of disabling. This is the only behaviour change for code that sets the options.
  • IP pinning from the SSRF fix (CURLOPT_RESOLVE) only maps host:port to the checked IP; the URL keeps the original hostname, so curl still sends SNI and checks the certificate against it. Confirmed live below, including an IDN host.

Before / after (live, CurlClient with no settings, PHP 8.5, macOS)

URL Before After
https://badssl.com/ 200 200
https://self-signed.badssl.com/ 200 curl error 60, self-signed certificate (18)
https://expired.badssl.com/ 200 curl error 60, certificate has expired (10)
https://wrong.host.badssl.com/ 200 curl error 60, no alternative certificate subject name matches target hostname
https://xn--mnchen-3ya.de/ (punycode) 200 200 (3 of 3 runs; one earlier run timed out on connect, not TLS)

With ['ssl_verify_peer' => false, 'ssl_verify_host' => 0] all of the above return 200, as before.

Tests

  • New testSslVerificationIsOnByDefault in tests/CurlClientTest.php: against the existing local openssl s_server with a self-signed certificate (no external network), no settings, null settings and ssl_verify_host => 0 alone are rejected with error 60; explicit disable returns 200.
  • null moved out of the "disabled" list in testSslVerifyPeerFalsyValuesAreTheOnlyOnesDisabled.
  • No fixture changed. Local: PHPUnit OK (242 tests) on PHP 7.4, 8.1 to 8.5; PHPStan clean.

Not confirmed / out of scope

  • https://münchen.de/ (UTF-8 host in the URL) returns HTTP 400 both before and after and with verification disabled, so TLS succeeds; the 400 looks like a separate Host header issue, not touched here.
  • Hostname verification over CURLOPT_RESOLVE against a trusted certificate is covered by the live checks only; the suite has no trusted local CA.
  • No live check on Linux/Windows CA bundles; curl uses the system's default.

CHANGELOG: 4.5.4 (Security), with how to disable verification. README settings updated.

@Vitorinox
Vitorinox merged commit 24b4102 into master Oct 31, 2026
10 checks passed
@Vitorinox
Vitorinox deleted the fix/ssl-verify-575 branch October 31, 2026 02:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Insecure default SSL configuration in CurlDispatcher (CURLOPT_SSL_VERIFYPEER defaults to false)

1 participant