PHP2Shell has sent website operators around the world scrambling to patch. Government portals and hospital websites across Europe have been taken offline as a precaution, and the Wikimedia Foundation has disabled some supporting systems until they can be safely updated. Reuters has a rundown of the affected services.
The concern centres on two critical vulnerabilities in PHP, the software behind many websites and content management systems. Disclosed on 21 October 2026, CVE-2026-109618 and CVE-2026-109619 can allow an attacker to run code on a vulnerable server without logging in. PHP has released fixes, leaving operators with the urgent job of finding affected systems and getting them updated.
At Peakhour, we were also seeing suspicious traffic. Early on 22 October, one of our honeypots captured a request that combined upload-progress tracking with an unusual collection of empty file uploads. Its structure matched a PHP2Shell-style probe. We did not observe code execution.
If you run PHP, the priority is to install a fixed release and restart the PHP workers that serve your website. A web application firewall (WAF) can block known request patterns while you roll out the update. Peakhour's WAF blocks requests containing PHP's default upload-progress field, and we are updating protections as new patterns emerge.
What we saw at the honeypot
At 03:15:36 AEDT on 22 October 2026, a client at 94.154.43.7 sent a GET request to one of our honeypots. About 1.4 seconds later, it sent a multipart POST to / on port 8080.
Multipart forms are the format browsers commonly use to send files alongside ordinary form fields. This request contained 151 file entries with empty filenames. Their field names were nested 60 bracket levels deep. It also included PHP_SESSION_UPLOAD_PROGRESS and PHPSESSID.
Those details are what made the request stand out. Even an upload with no filename can make PHP allocate memory, and deeply nested field names add more work. The upload-progress field brings session handling into the process while PHP is still reading the body.
Our WAF logs showed upload-progress probes that morning too. At 08:49 AEDT, requests from 169.58.51.5 triggered blocks for the upload-progress field and forms with too many arguments.
We checked the honeypot logs through 10:50 AEDT on 22 October and found no further requests combining upload-progress tracking with deeply nested, empty-filename file entries. The connection details and source addresses are at the end of this post for anyone checking their own logs.
Why an empty upload can still cause trouble
An empty filename might suggest that PHP has little to process. But PHP still has to represent the file entry internally. Three parts of this request matter.
First, upload-progress tracking starts early. When the feature is enabled, a field named PHP_SESSION_UPLOAD_PROGRESS tells PHP to track the upload and begin session processing as the request body arrives. That reaches PHP internals before the application's normal request handling begins.
Second, PHP's max_file_uploads limit does not count entries with empty filenames. Those entries still create file metadata, so that setting alone does not bound the memory used by a request like this.
Third, PHP turns bracketed field names into arrays. Nesting those names 60 levels deep makes PHP build more complex data structures. Repeating that across many file entries adds memory pressure, even when the entries contain no useful files.
Together, these features give a crafted request a way to put pressure on PHP's memory handling. The vulnerabilities concern what PHP does when that processing goes wrong.
What the two vulnerabilities do
CVE-2026-109618 concerns out-of-memory handling. If PHP runs out of memory while preparing a request, it can leave internal data only partly initialised. When PHP cleans up the failed request, it may treat that incomplete data as valid. An attacker can exploit the resulting memory corruption to run code.
CVE-2026-109619 concerns state left behind between requests. A PHP worker is a process that handles web requests, often one after another. This bug can leave a worker holding a reference to memory that a previous request has already released. A later request handled by that same worker may use the old reference before PHP resets it. This is a use-after-free vulnerability, which can also lead to code execution.
The advisories give the attacks high complexity ratings. Reliability depends on the PHP build, extensions, memory layout and how requests reach workers. For the second vulnerability, an attacker also needs a later request to reach the same worker and act on the leftover state.
Those conditions make a reliable exploit harder to build. The PHP team has nevertheless demonstrated unauthenticated code execution against Apache mod_php, before application code runs, including with multiple workers. PHP's exploit assessment explains the reliability factors.
Our assessment is that automated retries can reduce the practical burden of reaching the required conditions. Repeated multipart probes, memory failures and unexpected worker exits deserve investigation while teams patch. The request we captured is evidence of probing; it does not establish a successful compromise.
Update PHP and restart the workers
PHP released fixes on 21 October in versions 8.2.35, 8.3.36, 8.4.27 and 8.5.12. Install a fixed release for your branch, or a vendor package that includes the fixes, then restart every PHP worker serving web requests.
PHP's release notice identifies Apache mod_php as the highest-risk setup and urges all installations serving web requests to update. Check the PHP runtime your website actually uses when verifying the rollout.
A WAF helps during the gap between disclosure and patching. It can inspect incoming requests and block known attack patterns before they reach PHP. Rules can also be updated centrally as probes change, and the logs can show which requests need attention.
That is the role of Peakhour's upload-progress protection during this rollout. Patching fixes the underlying PHP vulnerabilities; WAF rules reduce exposure to the request patterns they recognise.
Temporary PHP settings while you patch
The following measures reduce exposure to particular attack paths. Updating PHP remains the complete fix. Apply configuration changes to the PHP runtime serving the website, restart every worker and check the effective settings.
Disable session upload progress
For CVE-2026-109619, disable session upload progress in configuration that applies before PHP parses the request. An FPM pool can enforce this with:
php_admin_flag[session.upload_progress.enabled] = off
The equivalent php.ini setting is session.upload_progress.enabled = Off.
Timing matters here. A script-level ini_set() call and FPM .user.ini take effect too late for this mitigation. Disabling upload progress blocks the confirmed session paths, but the advisory describes other ways to reach the vulnerability.
Also review invalid or deprecated settings applied before request initialisation, and fix attacker-reachable fatal errors and execution timeouts. On PHP 8.5, fatal_error_backtraces = Off blocks the confirmed fatal-backtrace path. That setting is specific to PHP 8.5.
Handle memory limits with care
For CVE-2026-109618, PHP's advisory recommends temporarily enforcing memory_limit = -1 administratively. This removes the predictable failure caused by PHP's own memory limit. It also removes PHP's per-request memory bound, so plan external worker or container memory controls before making the change.
An FPM pool can enforce the setting with:
php_admin_value[memory_limit] = -1
Check that pool, directory and application settings do not restore a finite limit. Prefer external controls that can terminate an affected worker without taking down the whole pool; a shared cgroup limit can affect every worker in it. Underlying allocation failures can still trigger the bug.
For unpatched workers, avoid lowering memory_limit or substituting RLIMIT_AS or RLIMIT_DATA. These can cause the allocation failures that reach the vulnerable code.
Limit the work an upload can create
Keep post_max_size conservative. PHP recommends 8M for applications without uploads and 32M where uploads are needed. Check those values against your application's normal requests before applying them, and keep upload_max_filesize below post_max_size.
At the web server or proxy, cap decoded request-body size. Make sure the limit also covers chunked requests and requests without a usable Content-Length. Bound input variables, multipart parts, part headers and nesting depth as well. Remember that max_file_uploads does not count empty-filename entries, even though PHP still allocates metadata for them.
The official PHP guidance explains these temporary measures and their limits.
What to look for in your logs
Start by comparing unusual multipart requests with the uploads your application normally receives. Look for combinations of:
- Many file entries with empty filenames.
- Deeply nested field names.
- Unusually large numbers of multipart parts or part headers.
- Upload-progress fields, especially alongside the patterns above.
Review nearby requests too, including a GET just before an upload, as we saw at the honeypot. Check PHP and web server logs for memory failures and unexpected worker exits around the same time.
If you block the upload-progress field, check its configured name. PHP_SESSION_UPLOAD_PROGRESS is the default, but administrators can change it with session.upload_progress.name. PHP documents that setting here. A rule matching only the default name will miss a renamed field.
Also check what happens when a request is too large or malformed for your WAF to inspect fully. Monitor parsing errors and tune limits around legitimate application traffic. Our guide to WAF tuning covers this process.
The captured request gives us a useful pattern to investigate. Future probes could change the multipart layout or use other PHP processing paths, so review new patterns as they appear. Keep the PHP update as the priority, then use request limits, WAF rules and log review to support it.
Connection fingerprints for log searches
Connection fingerprints summarise characteristics of a client's requests or connections. They can help you find similar traffic across different source addresses. Treat a shared fingerprint as a clue about a group of clients, rather than proof that requests came from one client. Our guide explains how to interpret fingerprint groups.
We calculated the following JA4H HTTP fingerprints from saved headers using FoxIO's reference implementation. The TLS values are those recorded in each WAF probe group. The honeypot request used plaintext HTTP, so it has no TLS fingerprint.
WAF probe group one
HTTP JA4H po11nn060000_84bafeeaa541_000000000000_000000000000
Observed TLS JA4 t13d131100_f57a46bbacb6_ab7e3b40a677
WAF probe group two
HTTP JA4H po11nn060000_4ea4093e6290_000000000000_000000000000
Observed TLS JA4 t13d131200_f57a46bbacb6_9249cab70c77
WAF probe group three
HTTP JA4H po11nn060000_5c15dc981d69_000000000000_000000000000
Observed TLS JA4 t13d911100_0d5420ba6086_ec21bdc2dd35
WAF probe group four
HTTP JA4H po11nn050000_197af124696f_000000000000_000000000000
Observed TLS JA4 t13d171200_ab0a1bf427ad_ecd0401ec68b
Honeypot request
HTTP JA4H po11nn060000_d09ed10a732b_000000000000_000000000000
TLS JA4 not applicable; plaintext HTTP
The honeypot request declared this browser user-agent:
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36
Its TCP signature was 1460|7|1|1|64240|0x02. Two WAF probes, from 160.187.210.214 and 169.58.51.5, shared those TCP settings but had different HTTP fingerprints.
Several other WAF records shared the same declared body size, headers and browser string. One used Mozilla/5.0 (php2shell-check/1.0). These details provide more ways to search logs. Our guide to recognising vulnerability scanners shows how to combine these clues across source addresses.
Observed source addresses
These addresses appeared in upload-progress probes between 00:00 AEDT on 21 October and 09:00 AEDT on 22 October 2026, or in the honeypot request at 03:15 AEDT on 22 October. Use them to search your logs and review the associated requests before deciding whether to block an address.
```text 2.27.152.153 13.143.120.218 23.27.75.120 23.27.203.212 23.236.222.100 24.144.109.162 31.56.137.245 31.56.138.52 31.58.9.242 31.58.20.52 31.58.148.208 31.59.27.195 34.87.183.75 38.154.233.215 43.229.11.209 45.38.92.232 45.38.114.219 45.39.7.39 45.39.7.227 45.39.27.153 45.39.123.39 45.41.171.199 45.41.176.106 45.41.177.11 45.43.67.111 45.43.82.93 45.61.98.240 45.61.100.180 45.61.123.134 45.67.0.207 45.67.3.53 45.147.187.214 45.151.163.80 45.159.53.193 45.249.59.5 45.249.59.123 46.202.248.133 46.203.154.134 46.203.202.8 46.203.212.201 62.105.202.173 62.105.203.138 64.64.118.134 64.137.8.237 64.137.73.58 64.137.100.131 64.137.101.226 64.137.108.41 67.227.1.14 67.227.37.59 68.167.47.41 68.183.138.186 82.21.221.119 82.21.248.189 82.22.215.207 82.22.234.160 82.22.249.84 82.23.227.154 82.24.212.115 82.25.235.118 82.27.247.49 82.29.233.5 82.29.245.108 89.45.125.116 89.249.192.235 91.211.87.239 92.112.91.92 92.112.143.195 92.112.170.208 92.113.119.3 92.113.236.71 92.113.246.14 94.154.43.7 94.177.5.146 102.212.90.53 103.47.53.192 103.99.34.177 103.101.90.134 103.105.76.121 104.168.118.192 104.252.49.100 104.253.50.139 104.253.81.55 104.253.91.46 107.172.163.190 107.174.194.247 108.165.205.76 136.0.108.192 136.0.184.80 140.99.189.141 140.99.193.124 140.99.203.75 142.111.44.12 142.111.216.8 142.147.129.75 142.147.132.10 145.223.41.159 145.223.45.93 145.223.50.100 148.135.254.33 148.135.254.73 148.135.254.118 151.245.204.165 151.245.204.214 151.245.205.41 151.245.206.59 152.232.81.206 154.9.177.66 154.29.25.85 155.254.39.35 160.187.210.214 166.88.34.50 166.88.64.163 168.199.209.224 168.199.244.14 169.58.51.5 172.120.112.207 172.120.119.224 173.214.177.173 185.101.252.60 192.95.91.217 192.177.87.44 192.186.186.222 198.12.112.116 198.20.191.165 198.46.148.181 198.46.202.184 198.46.241.29 198.89.123.223 198.105.100.102 208.70.8.237 212.42.207.214 213.169.211.115 216.10.27.39 217.117.165.104