Tutorial

PHP: fix CVE-2026-91767 and CVE-2026-91769 (OpenSSL)

Security & Monitoring13 min read14 steps

On 24 September 2026, the PHP project published two security advisories on the OpenSSL extension: CVE-2026-91769, an overly permissive TLS host name verification, and CVE-2026-91767, an out-of-bounds read during that same check. They are rated moderate and fixed by PHP 8.2.34, 8.3.35, 8.4.26 and 8.5.11. They are not comparable to PHP2Shell, but they concern any PHP code that opens an outgoing TLS connection. Here is whether your sites are affected, which version to install on Debian, Ubuntu, RHEL, AlmaLinux, cPanel and Docker, and what to do on a branch with no fix.

Contents· What these two advisories fix1/13
  1. 01What these two advisories fix
  2. 02The essentials in seven points
  3. 03What these flaws allow, and what they do not
  4. 04Rated moderate: is it urgent?
  5. 05Measure your exposure in four steps
  6. 06Which version to install for your branch
  7. 07Apply the fix for your environment
  8. 08If your branch is end of life (8.1 and earlier)
  9. 09Never set verify_peer_name to false
  10. 10A third OpenSSL fix in the same quarter
  11. 11Troubleshooting after the update
  12. 12Errors you may meet and their remedies
  13. 13Build the update into your routine

What these two advisories fix

Both advisories target ext/openssl, the extension that handles TLS encryption for PHP streams. They do not concern the visitors of your site: they concern the connections your PHP code opens to the outside, for example a file_get_contents('https://…'), a fopen on an https URL or a stream_socket_client on tls://.

In those cases, PHP checks that the certificate presented by the remote server matches the requested host name. This is the verify_peer_name option, enabled by default. Both flaws sit in that check. The CVE-2026-91769 advisory (GHSA-vvx9-73fr-5jjx) describes a verification that is too permissive. The CVE-2026-91767 advisory (GHSA-xr7j-rvgx-xq5p) describes an out-of-bounds read in the comparison of wildcard names. Both were published on the same day, 24 September 2026, and are fixed by the same versions.

The essentials in seven points

  • CVE-2026-91769 — moderate severity, CVSS 3.1 score of 4.3. Peer name verification looks at the subjectAltName entries first, then falls back to the Common Name even when the certificate presents a DNS SAN that does not match.
  • CVE-2026-91767 — moderate severity, CVSS 3.1 score of 6.5 (CWE-122). In php_openssl_matches_wildcard_name(), a length underflow passed to memchr() causes an out-of-bounds read when the certificate's wildcard name has more literal characters than the host being verified.
  • Fixed versions — PHP 8.2.34, 8.3.35, 8.4.26 and 8.5.11, for both flaws. Versions 8.4.26 and 8.5.11 appear in the PHP changelog dated 24 September 2026.
  • Who is affected — clients that rely on the default value of verify_peer_name, through file_get_contents(), fopen() and stream_socket_client() on https:// and tls://.
  • Workaround — the advisory gives none. Disabling verification is not one, it is one more flaw.
  • Branches 8.1 and earlier — end of life, no upstream fix. Distribution fixes or third-party backports may exist: check package by package.
  • Exploitation — in the sources we consulted, we found no observed exploitation of these two flaws.

What these flaws allow, and what they do not

CVE-2026-91769 allows host spoofing. A certificate that your client trusts for one host can be used to impersonate another one, if its Common Name contains the targeted name. RFC 6125 says to ignore the CN when a DNS SAN is present, and PHP did not. The advisory considers the case mostly relevant for private PKI, meaning an internal authority that issues certificates for many services.

CVE-2026-91767 allows a PHP client to be crashed. A malicious server, or one with a trusted authority, can trigger the out-of-bounds read during verification: this is a denial of service. The advisory states that the outcome only affects the accept or reject decision and returns no heap content. One honest caveat: the CVSS vector rates confidentiality High and availability None, which does not match the text of the impact. We flag it without taking sides.

What they do not allow. The advisory describes no code execution. Neither flaw is triggered by a visitor request reaching your site. Both vectors are of the adjacent network type (AV:A): the attacker must be close to the communication, not anywhere on the Internet.

Rated moderate: is it urgent?

An honest answer: nothing in the sources we consulted indicates that these flaws are being exploited. We claim nothing beyond that. They are not comparable to PHP2Shell (CVE-2026-109618 and CVE-2026-109619), which targets the interpreter itself and is triggered by an anonymous HTTP request. That flaw is covered separately in our PHP2Shell article; the latest version of your branch, which that article asks you to install, also contains these fixes.

Urgency is therefore graded. It is real if your code calls external URLs with PHP streams (payment APIs, outgoing webhooks, feed imports, licence checks) or internal services whose certificates come from a private PKI. It is much lower for a showcase site that makes no outgoing calls. In both cases the fix stays within the same branch: fit it into your next maintenance window rather than postponing it without a date.

Measure your exposure in four steps

  1. Read the PHP version actually served

    On the server, run php -r 'echo PHP_VERSION, PHP_EOL;'. Do not rely on OPENSSL_VERSION_TEXT: it is not the system OpenSSL library that is fixed, it is the extension code, so the PHP version is what counts. Beware, the CLI may differ from what your sites serve: on a server that hosts several branches, also read the version of each PHP-FPM pool (see the verification step below). On cPanel, the version is read per domain, see our guide to the PHP selector.

  2. Look for calls that go through PHP streams

    List the outgoing calls that carry a literal URL in the code: grep -rnE "(file_get_contents|fopen)\(\s*['\"]https?://|stream_socket_client\(\s*['\"]tls://" /var/www --include='*.php'. On a cPanel hosting account, replace /var/www with /home. Complete with calls whose URL comes from a variable: grep -rnE "(file_get_contents|fopen|stream_socket_client)\(\s*\$" /var/www --include='*.php'. Think about WordPress plugins and the vendor folder: outgoing calls often hide there.

  3. Spot verifications that are already disabled

    Run grep -rn "verify_peer_name" /var/www --include='*.php'. A value of false means no host name is verified at all: that code is exposed to far more than these two advisories, whatever the PHP version. Fix it before anything else.

  4. Rank your sites

    A site is a priority if it combines a branch below the floor in the table that follows and outgoing calls through streams. Calls made through the cURL extension are not cited by the advisory, which lists PHP streams. WordPress and most HTTP libraries generally use cURL when it is available, but fall back to streams otherwise: do not rule them out without checking.

Which version to install for your branch

Scroll the table

PHP branchMinimum fixed versionStatus
8.58.5.11Fixed. Install the latest 8.5 available.
8.48.4.26Fixed. Install the latest 8.4 available.
8.38.3.35Fixed. Install the latest 8.3 available.
8.28.2.34Fixed. Plan to leave this branch, see the checklist linked at the end of the article.
8.1 and earlier (5.6 to 8.1)No upstream versionEnd of life. Distribution fix or third-party backport to verify, otherwise migrate.

Apply the fix for your environment

  1. Debian and Ubuntu

    Refresh the index with sudo apt update, then upgrade the packages of your branch with sudo apt install --only-upgrade 'php8.3*' (replace 8.3 with your branch). To see what is pending: apt list --upgradable 2>/dev/null | grep -i php. Distributions often fix through a backport without changing the upstream number: read the package log with apt changelog php8.3-cli | grep -i CVE-2026-91769. With a third-party repository such as Sury, the same commands apply.

  2. RHEL, AlmaLinux and Rocky Linux

    Run sudo dnf update 'php*'. If you use a module stream, the branch follows the enabled module: dnf module list --enabled php shows it. Then check the log with rpm -q --changelog php-cli | grep -i CVE-2026-91769. With a third-party repository, package names change: adapt the pattern to what rpm -qa 'php*' returns.

  3. cPanel: EasyApache 4 and the PHP selector

    EasyApache 4 branches are packages named ea-php83, ea-php84, and so on. If you have root, list them with rpm -qa 'ea-php*' | sort then update them with sudo dnf update 'ea-php*'. Read the log with rpm -qa --changelog 'ea-php83*' | grep -i CVE-2026-91769 and the real version with /opt/cpanel/ea-php83/root/usr/bin/php -v. On shared hosting, the host does the update: your part is to check, in the cPanel PHP selector (MultiPHP Manager, or Select PHP Version on CloudLinux), that each domain runs on a fixed branch.

  4. Docker

    An image is not fixed in place: rebuild it, pulling the updated base, with docker build --pull --no-cache -t your-image ., or docker compose build --pull --no-cache with Compose, then restart with docker compose up -d. Check the result inside the container with docker run --rm your-image php -v. A floating tag such as php:8.4-fpm only moves if the upstream image was rebuilt: the printed number is what counts, not the tag name.

  5. Restart the PHP services

    PHP-FPM and Apache keep the old code in memory until they are reloaded. On Debian or Ubuntu: sudo systemctl restart php8.3-fpm (adapt the branch), and sudo systemctl restart apache2 if you use mod_php. On RHEL or AlmaLinux: sudo systemctl restart httpd. On cPanel with PHP-FPM, systemctl list-units 'ea-php*' gives the exact name of the service to restart.

  6. Check the version actually served

    The CLI is not the web service. Query the FPM binary: php-fpm8.3 -v on Debian and Ubuntu, php-fpm -v on RHEL and derivatives, or the domain's version in cPanel. The number must reach the floor of your branch, or its log must cite CVE-2026-91769. Finish with a test outgoing call: php -r 'echo strlen(file_get_contents("https://your-domain.com/")), PHP_EOL;'.

If your branch is end of life (8.1 and earlier)

According to HeroDevs, PHP branches 5.6 to 8.1 are end of life and receive no upstream fix for these flaws. Do not assume anything either way. Your distribution may have backported the fix into its package: it is possible, it is not guaranteed, and it is checked package by package, with the change log (apt changelog, rpm -q --changelog) and the distribution's security tracker. Third-party vendors also offer backports for abandoned versions.

Without a confirmed fix, you have three levers. The first is migrating to a maintained branch, 8.3 or 8.4 after testing your site: the method is in our PHP migration checklist. The second is to limit the outgoing calls of that code to hosts whose certificates you control. The third is to restrict the server's outgoing traffic at the firewall. The last two reduce exposure, they fix nothing.

Never set verify_peer_name to false

Faced with a certificate error, the temptation is to add verify_peer_name => false in a stream_context_create. That is worse than the flaw: no host name is verified anymore, so any trusted certificate passes for any host. The advisory gives no workaround, and this option is not one. When a connection fails after the update, repair the certificate.

A third OpenSSL fix in the same quarter

CVE-2026-14355 is a separate flaw: memory corruption in openssl_encrypt() with the AES-WRAP-PAD algorithm (GH-22187). According to the PHP changelog (https://www.php.net/ChangeLog-8.php), it is fixed in PHP 8.4.23, dated 3 July 2026, and in PHP 8.5.8, dated 2 July 2026. It affects encryption, not TLS verification: it changes nothing in the exposure test above.

If you install 8.4.26 or 8.5.11 or later, you already have this fix. For branches 8.2 and 8.3, we do not cite a version, as we have not verified it: read your package log to see whether CVE-2026-14355 appears in it.

Troubleshooting after the update

The fix tightens a check. Some calls that used to pass may therefore fail now, and that is the expected consequence of the fix, not a regression. The case to test first is an internal service whose certificate comes from a private authority. Here are the real messages you may meet and the matching remedy.

Errors you may meet and their remedies

  • SSL operation failed with code 1 then certificate verify failed — typical full message: stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages: error:0A000086:SSL routines::certificate verify failed. Probable cause: the certificate presents a DNS SAN that does not contain the host being called, but its CN does. The old check accepted it by falling back to the CN, the new one rejects it. Read the certificate with openssl s_client -connect interne.your-domain.com:443 -servername interne.your-domain.com </dev/null 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName. Remedy: reissue the certificate with a correct DNS SAN for every name used, never disable verification.
  • file_get_contents(): Failed to enable crypto — warning that accompanies the previous one. Read the full message that follows: depending on the version, you may also find Peer certificate CN=… did not match expected CN=…. Same cause, same remedy.
  • A certificate with no SAN at all, only a CN, still fails — the fix keeps the CN fallback for certificates without a service identifier. The cause is therefore elsewhere: authority not trusted by the client, host called by an alias or an IP address missing from the certificate, expired certificate. Remedy: add the internal authority to cafile in the stream context, and reissue the certificate with a SAN.
  • child exited on signal 11 (SIGSEGV) in the PHP-FPM log — a worker crashing during an outgoing TLS call is compatible with CVE-2026-91767. Do not conclude from that sign alone: check the version, update and see whether the pattern disappears.
  • php -v still shows the old version — service not restarted, CLI and FPM on different branches, Docker image rebuilt without --pull, or the domain's version unchanged in cPanel. Go back to the verification step.
  • The distribution log does not cite the CVE — its absence proves neither that the fix is there nor that it is missing. Consult the distribution's security tracker, and failing that compare the upstream number with the floor in the table.

Build the update into your routine

  1. Record the floor in your inventory

    Add a "minimum version" column per branch to your fleet inventory, with the values from the table. A version below the floor then becomes visible without any research.

  2. Schedule PHP updates

    A PHP security fix is a minor update within the same branch: plan a regular slot rather than a project for each advisory, with a service restart and a check of the version served.

  3. Test an outgoing call after each update

    Replay php -r 'echo strlen(file_get_contents("https://your-domain.com/")), PHP_EOL;' from each server, and a call to your internal services. A certificate error then shows up immediately, not at a customer's.

  4. Connect this fix to the rest of your monitoring

    For the method of surveying a fleet, read the PHP version inventory for a client fleet. For the most critical flaw of the moment, see PHP2Shell. To change a site's version, follow the cPanel PHP selector guide. For a branch that is ending, go back to the PHP 8.2 migration checklist.

Choose the PHP version of each site

On a ServOrbit.com web hosting plan, you choose the PHP version domain by domain from cPanel, and you move to a branch that is still fixed after testing your site.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab