Two critical PHP flaws, nicknamed php2shell, were discussed publicly before a fix existed. The PHP project has since released the fix (see Patch status). Everything below about how the flaws work is what the PHP project and public sources say, read on 2026-10-21. We have not reproduced it and we do not publish how.
This piece is about the other half: if someone is trying it on your web servers, which logs would show it, and which detections to run.
Last updated: 2026-10-24 UTC.
This page will be updated continuously as we find and improve php2shell monitoring methods and capabilities. The change list at the end shows each update with its date.
What the sources claim, and what is confirmed
The PHP project's own front page, read at 18:28 UTC on 2026-10-21, carries a security advisory dated 21 October 2026: PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 fix two critical vulnerabilities that can allow unauthenticated remote code execution. It names CVE-2026-109618 (out-of-memory handling can interrupt PHP while internal request data is only partly initialized) and CVE-2026-109619 (request-owned internal state can survive after its memory is freed, and a later request on the same worker may use it). It says the risk is highest for mod_php, with a demonstrated exploit where an unauthenticated request can trigger the attack before any application code runs. It also offers source-only backports for PHP 7.4, 8.0 and 8.1. Earlier in the day, before the release, the same page had announced the release for 14:00 UTC and rated the flaws CVSS v4.0 9.2.
The claims below were made by third parties before the release and are not confirmed by the advisory. Where they differ from it, the PHP advisory is the one to follow.
- A public write-up claims a memory-safety bug in session upload-progress handling, triggered by a crafted multipart/form-data POST at request start-up, before your application code runs, with remote code execution as the result. The advisory text for CVE-2026-109618 does not mention upload progress: it is titled "RCE via OOM corruption" and describes out-of-memory handling. Session upload progress appears in the advisory only in the text for CVE-2026-109619.
- A Syhunt protection repository describes the same suspected path, says a hidden text field is enough, with no file upload form needed, and warns that the affected range, the root cause and the remote code execution claim are all unconfirmed.
- A security news channel says exploitation has started, that a researcher reproduced one flaw, and that details leaked before disclosure. We could not read the underlying posts it relies on.
What we could not establish from any source: URLs, parameter names, user agents, file names or the shape of a first-stage payload. So nothing below keys on one. It keys on what a successful attack on any PHP host has to do afterwards: spawn a shell, write a file, or phone home.
Patch status
Fixed upstream: the PHP project released PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 on 21 October 2026, as read on php.net at 18:28 UTC. Source-only backports exist for PHP 7.4, 8.0 and 8.1. Updating is the fix. The detections below still matter for servers you have not updated yet, and for checking whether anyone tried before you did. Read the versions from php.net, not from anyone's blog: one earlier write-up listed fixed versions that were ordinary security releases from September, so they are not this fix.
The logs to collect
- Web server access logs (Apache, Nginx, IIS) and any WAF in front of them. Request method and URL are enough for most of this. The Content-Type request header is not in most default log formats, so a detection that needs it needs a custom format first.
- PHP-FPM and web server error logs, for worker crashes.
- Process creation on Linux (auditd execve or Sysmon for Linux) with the parent process, and on Windows (Sysmon event 1 or Security 4688 with command line).
- File creation inside web roots, on both platforms (auditd file watch, Sysmon event 11).
The five detections
We read our own rule base against these. Windows is covered better than Linux. Four of the five below are gaps we found; the inline PHP reverse shell is already covered by an existing SigmaHQ rule. We have written the four gaps as new rules for our own lab SIEM; they are shown in full in the Sigma rules section below. None of the four is running in a customer environment, and the SIEM fire test is only partly done: three of the four, the web-root write rule (see Detection 4), the rule for a web server spawning a shell (see Detection 2) and the PHP-FPM crash burst idea (see Detection 5, on real PHP-FPM crash lines through a lab-only parser), have fired in a lab SIEM, and the multipart POST burst could not be checked there yet. The thresholds are starting values, not measured ones. The three Linux auditd Sigma rules in the auditd section are separate from these four, and those did fire on real lab records on 2026-10-22.
- A burst of multipart POSTs to .php endpoints. One source sending dozens of multipart/form-data POSTs a minute to pages that are not upload forms. It needs the Content-Type header in the log, and an allowlist of your real upload URLs. Without the header, a POST-rate rule on .php is a noisy stand-in.
- A web server or php-fpm spawning a shell. On Linux, a parent such as php-fpm, nginx or apache2 starting sh, bash, curl, wget or nc. Expect some noise from CMS image tools and backup plugins, and filter those by command line once you have a baseline. SIEM check, 2026-10-24: the lab sent hand-made Sysmon for Linux process-creation lines (event 1, user www-data, parent /usr/sbin/apache2) through the Linux Sysmon connector into a lab SIEM. The lab SIEM rule for a web server spawning a shell fired on the
shline, once in each of two rounds, and did not fire on anlsline sent alone as a control. This is the lab's own SIEM rule, not the downloadable Sigma rule. The lines were made by a lab script, not by a web server process. A correlation row carries no image, so which line fired rests on thels-alone control round, and the row end times do not match the send times (the parser's known timestamp offset). An earlier replay the same day had found that this SIEM rule could not fire from this parser (0 of 4 lines); in this run it fired, and we do not explain the difference. - An inline PHP reverse shell.
php -rwith a socket call and a shell name on the command line. A rule that looks for a bash process opening the connection does not see this, because the connection comes from the php process. - A .php file written in a web root by the web user. www-data, nginx, apache, nobody or the FPM pool user creating .php, .phtml or .phar under the web root, outside the package manager and your deploy user. Exclude framework cache directories.
- A burst of php-fpm worker crashes. Several "exited on signal 11" lines in a few minutes. No source says this bug crashes workers, so treat it as the weakest of the five and rank it last.
On Windows we already watch for a web server process starting a command shell: IIS worker processes starting cmd or PowerShell, and a wider rule for web server and PHP parents starting suspicious children. We read those rules and the parent list does include php-cgi, php, httpd and nginx. That covers the Windows version of the second detection above. We did not fire a test event at them for this piece.
Linux auditd coverage: released in CyRay auditd Distribution 1.0.2
Added 2026-10-21 UTC, updated 2026-10-24 UTC. Separately from the Sigma rules below, CyRay auditd Distribution 1.0.2 carries auditd rules for the web server user, 1.0.3 (2026-10-23) added new cyray- tag names that produced no events, and 1.0.4 (2026-10-23) fixed that by putting both names on one line. The three groups below map to the shell, connection-out and new-file detections above.
What to add. Put these lines in a file under /etc/audit/rules.d/, for example /etc/audit/rules.d/50-php2shell.rules, then load them with sudo augenrules --load. They are copied verbatim from the released rule set. User id 33 is www-data on Debian and Ubuntu, and 48 is apache on RHEL. A user id that does not exist on your host matches nothing and loads clean. Each line ends with -k <name>: that is the tag the rule writes into every event it produces, and the Sigma rules below match on it. These are the lines of release 1.0.4, which carry two tags each: the new cyray- tag and the old atl- tag, so both name families find the event. Release 1.0.3 had put the two tags on separate lines, and auditd records only the first matching line's tag, so the new tags never appeared; 1.0.4 fixes that. In 1.0.4 the old atl- tag stays as the second tag, marked deprecated, and its removal is postponed to a later release. Read the one limit before you use them: when a record carries two tags, auditd stores them as a single hex-encoded field joined by a separator byte. ausearch -k finds the record under either name and ausearch -i decodes it, but a rule that matches the plain tag text in the raw record finds nothing. We read this in the lab on the 1.0.4 rule lines. On 2026-10-24 the lab ran the three Sigma rules on 254 captured records that carry the 1.0.4 two-tag field, with its own evaluator, which decodes the hex field into both tag names before matching (as ausearch -i does): they matched 9, 3 and 5 records (see the lab run after the noise paragraph below). The lab did not run a SIEM or pySigma, so the rules are shown to match once your pipeline decodes the two-tag field; a pipeline that matches the raw undecoded text is still not shown to match. If your pipeline matches plain tag text, use the single-tag lines of release 1.0.2 shown after this block; those are the lines the Sigma rules were fired on.
-a always,exit -F arch=b64 -S execve,execveat -F euid=33 -k cyray-webshell -k atl-webshell -a always,exit -F arch=b32 -S execve,execveat -F euid=33 -k cyray-webshell -k atl-webshell -a always,exit -F arch=b64 -S execve,execveat -F euid=48 -k cyray-webshell -k atl-webshell -a always,exit -F arch=b32 -S execve,execveat -F euid=48 -k cyray-webshell -k atl-webshell -a always,exit -F arch=b64 -S connect -F a2=0x10 -F euid=33 -k cyray-webshell-connect -k atl-webshell-connect -a always,exit -F arch=b64 -S connect -F a2=0x1C -F euid=33 -k cyray-webshell-connect -k atl-webshell-connect -a always,exit -F arch=b64 -S connect -F a2=0x10 -F euid=48 -k cyray-webshell-connect -k atl-webshell-connect -a always,exit -F arch=b64 -S connect -F a2=0x1C -F euid=48 -k cyray-webshell-connect -k atl-webshell-connect -w /var/www -p wa -k cyray-webroot-write -k atl-webroot-write -w /var/www/html -p wa -k cyray-webroot-write -k atl-webroot-write
Single-tag lines of release 1.0.2, for pipelines that match plain tag text:
-a always,exit -F arch=b64 -S execve,execveat -F euid=33 -k atl-webshell -a always,exit -F arch=b32 -S execve,execveat -F euid=33 -k atl-webshell -a always,exit -F arch=b64 -S execve,execveat -F euid=48 -k atl-webshell -a always,exit -F arch=b32 -S execve,execveat -F euid=48 -k atl-webshell -a always,exit -F arch=b64 -S connect -F a2=0x10 -F euid=33 -k atl-webshell-connect -a always,exit -F arch=b64 -S connect -F a2=0x1C -F euid=33 -k atl-webshell-connect -a always,exit -F arch=b64 -S connect -F a2=0x10 -F euid=48 -k atl-webshell-connect -a always,exit -F arch=b64 -S connect -F a2=0x1C -F euid=48 -k atl-webshell-connect -w /var/www -p wa -k atl-webroot-write -w /var/www/html -p wa -k atl-webroot-write
The two -w lines abort the whole rule load if the folder does not exist, so leave out any watch whose folder is missing on your host. The released installer does this for you and prints each line it drops.
What each group catches, and its Sigma rule. All three Sigma rules use logsource: product linux, service auditd and match the tag the rule writes. Written by CyRay, and fired on real lab auditd records on 2026-10-22: 4 of 4 program starts, 2 of 2 outbound connections and 4 of 4 web root writes matched, and the audit-rule reload records matched nothing. A rule tests only the record type and the tag, so the web server user scoping lives in the auditd lines above. What was proven: Linux auditd only, web server users with euid 33 and 48 only, synthetic test events, not yet the real exploit.
- Tags `cyray-webshell` and `atl-webshell`: a program started by the web server user. A web server running as www-data or apache does not normally start other programs, so this is the shape of a web shell running a command. Sigma rule: cyray_lnx_auditd_web_user_starts_program.yml.
- Tags `cyray-webshell-connect` and `atl-webshell-connect`: an outbound network connection by the web server user. The four lines cover IPv4 and IPv6. This is how a reverse shell started from a web shell shows up. Sigma rule: cyray_lnx_auditd_web_user_connects_out.yml.
- Tags `cyray-webroot-write` and `atl-webroot-write`: a write or attribute change under /var/www or /var/www/html. A web shell or dropper written through a web flaw lands here. Sigma rule: cyray_lnx_auditd_web_root_write.yml.
In our lab each tag produced events that reached our SIEM through the connector, and every rule loaded with none refused. The release was recorded on 2026-10-21 at 21:17 UTC. Release 1.0.3 was checked on a lab host on 2026-10-23 and its new tags produced no events; the cause is above. Release 1.0.4 was checked on a lab host on 2026-10-24: the published package matched its checksums, its rules loaded with none refused, and one event per tag family was read back for user ids 33 and 48. That first check read the events under the old atl- names only. A second check on 2026-10-24, of the package as published on our download site, read the events under both the new cyray- names and the old atl- names (4 program-start, 4 connection and 2 web-root rule lines loaded for each, user ids 33 and 48, host restored). It read the events from the audit log; it did not run any Sigma or SIEM rule on them. Only Ubuntu 24.04 LTS has been tested. We did not run the php2shell exploit to produce these events, so this shows the logging path works, not how well it catches the attack. The three Sigma rules above were evaluated against real lab auditd records of the same kind, see the paragraph above.
On 2026-10-22 a fourth lab run generated test events as the web server user: two programs started, one outbound connection and two writes under the web root. All five arrived in the SIEM with the right tag. A program started and a connection made by a different user, and a file written outside the web root, produced no tagged events. SIEM correlation rules built on the three tags fired on those five events and on nothing else, including when we reloaded the audit rules. This is Linux auditd, web server users with euid 33 and 48 only, and synthetic test events, not yet the real exploit.
Fifth lab run, on a real PHP server. On 2026-10-22 we ran test pages as the web server user inside a real php:8.4.26-apache container (mod_php, euid 33), five hits per class. The auditd records arrived with all three tags: 10 program starts, 5 outbound connections and 10 web root writes, every one with euid 33. A public proof-of-concept exploit was also run once, in an isolated container. It stopped at its heap-leak step (HEAP_LEAK_FAILED) and gave no code execution, so no event in this run came from a real exploit. The events came from test pages we wrote, not from the vulnerability. The three SIEM correlation rules fired on the test pages too, but fewer times than the raw records: 2 program starts, 1 outbound connection and 2 web root writes, against 10, 5 and 10 raw records. We have not read why, so we do not explain it. The rules also fired 3 times on the exploit run that failed, matching the container start and a curl call, not the exploit. So the rules are shown on a real PHP process chain, and not proven on the real exploit.
Noise on a benign WordPress site. On 2026-10-22 we ran stock WordPress (PHP 8.4.27 with Apache, MariaDB) in a lab VM and sent about 3,600 page, admin, login, comment and upload requests, plus 64 media uploads. The program-start rule fired once, on sh -c sendmail that PHP mail() runs for a comment notification: a false fire on ordinary WordPress traffic, and the one program start in that window. The web root write rule fired once for the upload burst (256 raw records). The outbound connection rule fired 0 times, although there were 8,977 raw connection records (database and DNS); we have not read why. Fires are fewer than raw records because the correlation rules group identical hits inside a two-minute window. Five exploit-style hits 135 seconds apart gave five separate program-start fire groups. This is one application on one host over about five minutes, not a production false-positive rate. Browser cookie login never worked in this run, so admin pages were fetched logged out. Expect to exclude `sendmail` started by PHP if your sites send mail.
The Sigma rules on real lab logs. On 2026-10-24 the lab generated logs in real php:8.4.27 Apache and PHP-FPM containers (auditd release 1.0.4 lines loaded verbatim) and evaluated each downloadable rule against that one capture with its own Python evaluator, not pySigma and not a SIEM. All seven rules matched real lab events. The multipart POST burst rule matched 47 of 50 requests, with 35 in the busiest minute against its threshold of 30; the three it did not match were a GET, a form POST and a multipart POST to an .html page. The crash burst rule matched 6 PHP-FPM lines and 6 Apache lines, with 12 in the busiest five minutes against its threshold of 5, and neither rule matched the other's lines. The three auditd rules matched 9 (program start), 3 (outbound connection) and 5 (web root write) of 254 records each. The records that did not match are the lab harness (root docker and curl runs, dockerd writes). A write of root-note.php by root matched the web root write rule on purpose, because that rule keys on the audit tag only and the user scoping is in the audit rule lines. All 16 records of user id 33 carried the 1.0.4 two-tag hex field. The shell-spawn rule, with the parent process read from audit data only, matched 2 of 144 exec events (97 had an audit-resolved parent, 47 did not): sh -c id from apache2 and sh -c id from php-fpm, both user id 33. Two sh -c curl events had no audit record for their parent process, so they are unproven, not failed. An earlier evaluation matched 4 and the first matched 7; the extra matches used a process map taken at capture time, which is not audit data, and the root curl runs of the harness were wrong matches and were removed. The rule for PHP files written in a web root matched 2 of 5 file events (drop.php by apache2 and fpm-drop.php by php-fpm, both user id 33, tag cyray-webroot-write); it did not match a file under cache/ (the rule's filter excludes it), a .txt file, or a .php written by root. Correction: the first evaluation of this run reported 0 of 237 records for the auditd rules and no file events. That came from a defect in the lab's evaluator, which decoded a record with two audit tags (stored as one hex field) to nothing. It was fixed and the same capture was evaluated again; the numbers above are the corrected ones. SIEM status, from a replay and a later hand-made-line run on 2026-10-24, sent through a lab Linux Sysmon connector: two ideas have now fired in a lab SIEM, the web-root write idea and the web server spawning a shell idea. The web-root write rule which fired on 3 of 5 file lines (drop.php, fpm-drop.php and cache/cached.php) and not on note.txt or the root-written root-note.php. The raw capture was not kept, so the 9 test lines were rebuilt from the recorded fields (5 file writes, 4 shell spawns). The other six did not fire in the SIEM in that replay, for these reasons. Multipart POST burst: could not be sent, because the lab connector has no parser for Apache access logs. PHP-FPM crash burst: could not be sent in that replay, because there was no PHP-FPM log parser on a lab connector; a later run with a lab-only parser did send real PHP-FPM lines, see the section on the two log rules below. Apache mod_php crash half: no SIEM rule exists for it. Web server spawns a shell: in the replay the lab SIEM rule got 0 of 4 lines, because its product and class filter did not match what the parser emits; in a later run the same day, on hand-made Sysmon-format lines (shell started by the web user under Apache), the lab SIEM rule fired on the sh line and not on an ls control line. That is the lab's own SIEM rule, not the downloadable Sigma rule, and the lines were hand-made, not produced by a web server process. A further lab SIEM rule, for a PHP command-line inline reverse shell, also fired on its hand-made line; it is not offered for download here. The three auditd rules (program start, outbound connection, web root write): not replayed in this run; an earlier lab run fired them 2, 1 and 2 times. The SIEM rule has no cache-path exclusion, while the Sigma rule excludes cache paths, so the SIEM rule fired on 3 of 5 and the Sigma rule matched 2 of 5. These are synthetic test events, not the real exploit, and this does not show detection of the real exploit. Limits that remain: the Sigma numbers here are from the lab's own evaluator, not a SIEM; one capture, no fresh run; the evaluator decodes the two-tag hex field before matching, so a pipeline that matches the raw undecoded text is not shown to match; two curl parents are unproven; not production traffic.
The two log rules on a real PHP-FPM and Apache capture. On 2026-10-24 the lab installed PHP-FPM with Apache (the proxy_fcgi module) on one lab VM, and made four PHP-FPM workers crash on purpose. The crash is synthetic: a test page of ours kills its own worker with signal 11. It is not the exploit, and it says nothing about whether the exploit crashes workers. The capture holds 4 real PHP-FPM crash lines, 10 real Apache access lines (4 of them status 503 on the crash page, and 3 POSTs to .php pages, one of them multipart) and 13 Apache error lines (8 of them the crash-related ones below). We ran the two downloadable rules against these real lines on 2026-10-25: the rule files were parsed with pySigma, which reported no errors, and the patterns and field names it read were then applied to the lines by plain matching, not by a SIEM.
- Real PHP-FPM wording.
WARNING: [pool www] child <pid> exited on signal 11 (SIGSEGV - core dumped) after <seconds> seconds from start. The PHP-FPM rule patternexited on signal 11matched 4 of 21 PHP-FPM log lines: all 4 crash lines and none of the start, stop or notice lines. The wording matches what the rule expects, with(SIGSEGV - core dumped)and the uptime added after it. - Real Apache wording on a PHP-FPM crash does not match the Apache half of the crash rule. No Apache child-exit line appears in this capture. Apache logs, per crashed request, two error lines:
AH01067: Failed to read FastCGI headerandAH01075: Error dispatching request towithConnection reset by peer, and the client request gets a 503 in the access log. The Apache half of the rule (exit signal Segmentation fault (11), the mod_php wording) matched 0 of 13 Apache error lines. That half is for mod_php and was not meant for this setup, but it means the crash burst rule is blind to a crashing PHP-FPM worker seen from the Apache side. The PHP-FPM log line is the one that counts there. - Apache FastCGI error lines are not offered as a crash rule.
AH01067matched 4 of 13 Apache error lines andAH01075matched 4 of 13, one pair per crashed request. Both fire on any FastCGI failure (a worker restart, a timeout), so as a crash signal they would be noise. We did not add them to the downloadable rule and we are not recommending a new Apache FastCGI-error rule. For PHP-FPM crashes, rely on the PHP-FPM log line. - The downloadable crash burst rule needs 5 crashes in 5 minutes. This capture has 4 PHP-FPM crash lines, so the downloadable Sigma correlation would not fire on it by its own threshold, and we did not claim it. The lab SIEM result below is a different rule, with its own settings that we did not read for this page.
- The multipart POST rule matched 0 of 10 real Apache access lines. Apache's default combined format has no Content-Type field, and the rule's base selection needs
cs-content-typeto start withmultipart/form-data. All 10 lines parsed as combined format. 3 of them are POSTs to.phppages, and the multipart one among them cannot be told from the other two in the log. This is the limit the page already states, now seen on a real default log: without a custom log format that records the Content-Type, this rule cannot match a real Apache log. Only a custom Apache LogFormat that records Content-Type, a ModSecurity audit log or a WAF or reverse-proxy log carries that field, and we do not see customers collecting any of these today, so for most customers this detection cannot run yet. The threshold of 30 was also not reachable with 3 POSTs. - SIEM result for PHP-FPM crash lines, 2026-10-25. A lab-only draft PHP-FPM error-log parser, run on one lab SIEM connector, parsed 21 of 21 real PHP-FPM log lines with 0 unparsed. The 4 crash lines carried the process id, the pool name, signal 11, SIGSEGV and the seconds from worker start, and every value matched the log text. The lab SIEM then raised its own PHP-FPM child-crash-burst correlation event from those 4 crashes. Caveats: the End Time of the events read 3 hours early, because the log lines carry no time zone; the parser is lab-only and is not a shipped product parser; this is one lab VM; and the crash is synthetic, made by a test page that kills its own worker, not the exploit. The lab SIEM rule fired on 4 crashes while the downloadable Sigma rule needs 5; they are separate rules, and we did not read the lab SIEM rule's threshold for this page.
Limits of this capture: one lab VM, one PHP and Apache version, synthetic crashes made by a test page, a few seconds of traffic, and a SIEM run for PHP-FPM only through a lab-only parser that customers do not have. The line formats above are the real ones, but they show how the logs look, not how the real exploit behaves.
Sigma rules
Sigma is the open, vendor-neutral format for detection rules. Before writing anything we searched the public SigmaHQ rule set for each of the five detections on 2026-10-21. One is already covered by an existing rule. For the other four we found no rule that does the job, and wrote new ones.
- 1. Burst of multipart POSTs to .php endpoints. No SigmaHQ rule found. New rule below.
- 2. A web server or php-fpm spawning a shell. Nearest is proc_creation_lnx_webshell_detection. It does not cover this: it only looks at recon children such as whoami and uname, not sh, bash, curl, wget or nc, and it does not list php-fpm or php-cgi as parents. The Node.js server child process rule for CVE-2025-55182 covers Node.js parents only. New rule below.
- 3. An inline PHP reverse shell. Already covered by proc_creation_lnx_php_reverse_shell, with a Windows sibling, proc_creation_win_php_inline_command_execution. Two limits: it needs
fsockopenin the command line, so it missesstream_socket_clientandsocket_create, and its shell-name list containssh, which makes it loose. We wrote no new rule for this one. - 4. A .php file written in a web root by the web user. No SigmaHQ file-write rule found. The nearest is av_relevant_files, which is a path list for antivirus signature alerts, not a file-write rule. New rule below.
- 5. A burst of php-fpm worker crashes. Nearest is web_nginx_core_dump. It covers the nginx service only, signal 6 only, one event, no php-fpm and no burst. New rule below.
The four new rules
Every one of the four rules below is experimental and written by CyRay. Detection 1 and Detection 5 fired on logs from real lab PHP containers on 2026-10-24 (not production traffic). Detection 2 and Detection 4 also matched real lab events in the same capture (see the lab run above), and so did the three auditd rules, on records whose two-tag field the lab's evaluator decoded first. The Sigma checker found no errors in them, which says only that the syntax is valid. It says nothing about whether a rule detects anything. The thresholds (30 per minute, 5 per five minutes) are starting values, not measured ones. The two burst rules use Sigma correlation, which the Splunk converter handles; the Elasticsearch query-string converter refuses correlation rules, so there the base rule needs its threshold set in your SIEM. Each file is also a download.
Detection 1: burst of multipart POSTs to .php endpoints
Experimental, written by CyRay. Tested on 2026-10-22 on 120 synthetic log rows, offline: a source sending 35 matching POSTs in 40 seconds fired; a source sending 10 in a minute, one sending 35 spread over 35 minutes, and 40 non-matching rows did not. On the synthetic rows our own script, not the Sigma backend, evaluated the one-minute window. On 2026-10-24 the rule also fired on logs from real lab PHP containers: 47 of 50 requests matched, with 35 in the busiest minute against its threshold of 30. The lab used its own Python evaluator, not a SIEM, and this is not production traffic. Needs the request Content-Type in the web server log, which is a custom log format field in most setups, and an allowlist of your real upload URLs. The burst part is a Sigma correlation: 30 or more matching POSTs from one source IP within one minute. Download: cyray_web_php_multipart_post_burst.yml.
title: Burst of Multipart POST Requests to PHP Endpoints
id: e2f4d5d5-3286-4f0c-96b3-a30246b776eb
status: experimental
description: |
Base rule for the correlation below. Detects a POST with a multipart/form-data Content-Type to a .php URL in web server access logs.
Needs the request Content-Type in the log (not in most default log formats; field cs-content-type needs a custom log format).
Allowlist your real upload URLs with a filter before use. Tested offline on synthetic rows on 2026-10-22; on 2026-10-24 it fired on logs from real lab PHP containers (47 of 50 requests matched, 35 in the busiest minute against a threshold of 30), not on production traffic.
references:
- https://cyray.io/attack-to-detection/t1190-php2shell-php-multipart-rce
author: CyRay
date: 2026-10-21
tags:
- attack.initial-access
- attack.t1190
logsource:
category: webserver
detection:
selection:
cs-method: 'POST'
c-uri|contains: '.php'
cs-content-type|startswith: 'multipart/form-data'
condition: selection
falsepositives:
- Legitimate upload forms and CMS admin pages (allowlist their URLs)
- Load tests and monitoring that post forms
level: low
---
title: Burst of Multipart POST Requests to PHP Endpoints - Correlation
id: f7de57d4-8731-49bd-9ad7-1c5617e5a71c
status: experimental
description: |
One source sending 30 or more multipart/form-data POSTs to .php endpoints within one minute. The threshold is a starting value, not measured.
Context: PHP multipart handling flaws (php2shell), discussed publicly before the PHP fix of 2026-10-21. Tested offline on synthetic rows on 2026-10-22; on 2026-10-24 it fired on logs from real lab PHP containers (47 of 50 requests matched, 35 in the busiest minute against a threshold of 30), not on production traffic.
references:
- https://cyray.io/attack-to-detection/t1190-php2shell-php-multipart-rce
author: CyRay
date: 2026-10-21
tags:
- attack.initial-access
- attack.t1190
correlation:
type: event_count
rules:
- e2f4d5d5-3286-4f0c-96b3-a30246b776eb
group-by:
- c-ip
timespan: 1m
condition:
gte: 30
falsepositives:
- Bulk upload tools and scanners owned by the organisation
- Load tests
level: mediumDetection 2: web server or php-fpm spawning a shell
Experimental, written by CyRay. On 2026-10-24 the lab's own evaluator matched 2 of 144 exec events (`sh -c id` from apache2 and from php-fpm, parent read from audit data); two `sh -c curl` parents had no audit record and are unproven. Not a SIEM. Needs Linux process creation with the parent process. Expect noise from CMS image tools and backup plugins. Download: cyray_lnx_webserver_or_phpfpm_spawns_shell.yml.
title: Web Server or PHP-FPM Spawning a Shell or Download/Network Tool on Linux
id: b12b1b66-2735-4cd7-8a56-518e03397c46
status: experimental
description: |
Detects a web server or PHP process (php-fpm, php-cgi, nginx, apache2, httpd) starting a shell, curl, wget or a netcat variant on Linux.
This is what a successful remote code execution against a PHP host usually does next, whatever the first-stage exploit was.
Needs process creation with the parent process (auditd execve with parent, or Sysmon for Linux event 1). Lab run 2026-10-24: matched 2 of 144 exec events with the parent read from audit data (`sh -c id` from apache2 and php-fpm); two `sh -c curl` parents unproven; lab's own evaluator, not a SIEM.
references:
- https://cyray.io/attack-to-detection/t1190-php2shell-php-multipart-rce
author: CyRay
date: 2026-10-21
tags:
- attack.initial-access
- attack.t1190
- attack.execution
- attack.t1059.004
- attack.persistence
- attack.t1505.003
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentImage|contains:
- 'php-fpm'
- 'php-cgi'
- '/nginx'
- '/apache2'
- '/httpd'
selection_child:
Image|endswith:
- '/sh'
- '/bash'
- '/dash'
- '/curl'
- '/wget'
- '/nc'
- '/ncat'
- '/netcat'
condition: all of selection_*
falsepositives:
- CMS image tools and backup plugins that call a shell (filter by command line once a baseline exists)
- Mail sending through sh from php-fpm
level: highDetection 4: .php file written in a web root by the web user
Experimental, written by CyRay. On 2026-10-24 the lab's own evaluator matched 2 of 5 file events (`drop.php` by apache2, `fpm-drop.php` by php-fpm, user id 33); a cache file, a `.txt` file and a root-written `.php` did not match. The downloadable Sigma rule was evaluated only by that evaluator, not by a SIEM. Needs file creation events that carry the user (auditd file watch or Sysmon for Linux event 11). Excludes common framework cache paths. Download: cyray_lnx_web_user_writes_php_in_webroot.yml. Separately, on 2026-10-24 a SIEM correlation rule of the lab's own with the same idea (a Sysmon for Linux file-create event, a .php, .phtml or .phar file under a web root, written by a web server user) fired once on a hand-sent test line: a .php file in /var/www/html by user www-data. The same line with a path under /home did not fire. A later replay sent the capture's recorded events, rebuilt as Linux Sysmon lines from their stored fields (the raw capture files were not kept), into the same lab SIEM: the lab rule fired on 3 of the 5 rebuilt file lines (drop.php, fpm-drop.php and cache/cached.php) and not on note.txt or the root-written root-note.php; a second run showed those two do not fire. Which row is which line was found by elimination, not by timestamp. The lab SIEM rule has no cache-path exclusion, while the downloadable Sigma rule excludes cache paths, so the Sigma rule matched 2 of 5 and the SIEM rule 3 of 5. This lab SIEM rule is not the downloadable Sigma rule. The first line was sent by a lab script, and the replayed lines were rebuilt from recorded events, so none was made by a PHP process in this SIEM check. The SIEM row carries no file path, so which line fired it is shown by one row for two lines plus the rule's filter excluding the /home path. The row's end time was 3 hours earlier than the send time, a known timestamp offset of this parser; the seconds match.
title: PHP File Written in a Web Root by the Web Server User
id: 10b6e900-27cc-49b0-acba-d746e9265cf6
status: experimental
description: |
Detects a .php, .phtml or .phar file created under a common web root by the web server or PHP-FPM user (www-data, nginx, apache, nobody).
A web shell or a second-stage dropper written through a web flaw looks like this. Needs file creation events with the user (auditd file watch, Sysmon for Linux event 11).
Exclude your deploy user, package manager and framework cache directories. Lab run 2026-10-24: matched 2 of 5 file events (.php by user id 33 from apache2 and php-fpm); a cache file, a .txt file and a root-written .php did not match; lab's own evaluator, not a SIEM.
references:
- https://cyray.io/attack-to-detection/t1190-php2shell-php-multipart-rce
author: CyRay
date: 2026-10-21
tags:
- attack.initial-access
- attack.t1190
- attack.persistence
- attack.t1505.003
logsource:
category: file_event
product: linux
detection:
selection:
User:
- 'www-data'
- 'nginx'
- 'apache'
- 'nobody'
TargetFilename|contains:
- '/var/www/'
- '/usr/share/nginx/'
- '/srv/www/'
- '/srv/http/'
TargetFilename|endswith:
- '.php'
- '.phtml'
- '.phar'
filter_cache:
TargetFilename|contains:
- '/cache/'
- '/storage/framework/'
- '/var/cache/'
condition: selection and not 1 of filter_*
falsepositives:
- CMS plugin or theme installs and updates run as the web user
- Frameworks that compile templates to .php outside the excluded cache paths
level: mediumDetection 5: burst of PHP worker crashes
Experimental, written by CyRay. The weakest of the five. Version 2 matches both PHP-FPM ("exited on signal 11") and Apache mod_php ("exit signal Segmentation fault (11)"), because the first version missed the Apache wording; the test below found that gap. Tested on 2026-10-22, offline, on synthetic log lines: 5 PHP-FPM lines in 299 seconds fired, 4 lines did not, 5 Apache mod_php lines fired, a mix of 3 PHP-FPM and 2 Apache lines fired together, and 5 lines spread over 301 seconds did not. Five lines spanning exactly 300 seconds fire or not depending on whether the engine counts the window as inclusive, which the rule text does not decide. On the synthetic lines our own script evaluated the five-minute window. On 2026-10-24 the rule also fired on logs from real lab PHP containers: 6 PHP-FPM lines and 6 Apache lines matched, with 12 in the busiest five minutes against its threshold of 5. The lab used its own Python evaluator, not a SIEM, and this is not production traffic. Download: cyray_lnx_php_worker_crash_burst.yml.
title: PHP-FPM Worker Exited on Signal 11
id: 22b0c487-9b4c-499e-8c83-6c39d104b1a5
status: experimental
description: |
Base rule for the correlation below. Detects a PHP-FPM log line saying a child exited on signal 11 (segmentation fault).
Tested offline on synthetic log lines on 2026-10-22; on 2026-10-24 it fired on logs from real lab PHP containers (6 PHP-FPM and 6 Apache lines matched, 12 in the busiest five minutes against a threshold of 5), not on production traffic.
references:
- https://cyray.io/attack-to-detection/t1190-php2shell-php-multipart-rce
author: CyRay
date: 2026-10-21
tags:
- attack.initial-access
- attack.t1190
- attack.impact
- attack.t1499.004
logsource:
product: linux
service: php-fpm
detection:
keywords:
- 'exited on signal 11'
condition: keywords
falsepositives:
- A broken PHP extension or a memory problem
level: low
---
title: Apache mod_php Child Exited on Signal 11
id: 5e8f1c3a-7d2b-4c61-9a0e-2f4b6d8c1e73
status: experimental
description: |
Base rule for the correlation below. Detects an Apache error-log line saying a child process exited
on SIGSEGV (AH00052), which is how a crashed mod_php worker is reported. The PHP-FPM rule above
does not match this line (lab T-887046 synthetic test H, 2026-10-22).
references:
- https://cyray.io/attack-to-detection/t1190-php2shell-php-multipart-rce
author: CyRay
date: 2026-10-22
tags:
- attack.initial-access
- attack.t1190
- attack.impact
- attack.t1499.004
logsource:
product: apache
service: error
detection:
keywords:
- 'exit signal Segmentation fault (11)'
condition: keywords
falsepositives:
- A broken Apache module or PHP extension, or a memory problem
level: low
---
title: PHP Worker Crash Burst (PHP-FPM or Apache mod_php)
id: 3c2b877a-4478-478f-8125-2a85873500a0
status: experimental
description: |
Five or more PHP workers (PHP-FPM or Apache mod_php children) exiting on signal 11 within five minutes. The threshold is a starting value, not measured.
Weakest of the five php2shell detections: no public source says the flaw crashes workers. Tested offline on synthetic log lines on 2026-10-22; on 2026-10-24 it fired on logs from real lab PHP containers (6 PHP-FPM and 6 Apache lines matched, 12 in the busiest five minutes against a threshold of 5), not on production traffic.
references:
- https://cyray.io/attack-to-detection/t1190-php2shell-php-multipart-rce
author: CyRay
date: 2026-10-21
tags:
- attack.initial-access
- attack.t1190
- attack.impact
- attack.t1499.004
correlation:
type: event_count
rules:
- 22b0c487-9b4c-499e-8c83-6c39d104b1a5
- 5e8f1c3a-7d2b-4c61-9a0e-2f4b6d8c1e73
timespan: 5m
condition:
gte: 5
falsepositives:
- A faulty extension after an upgrade
level: lowMitigations the PHP advisories give
Read on 2026-10-22 UTC from the advisory records on the php-src repository (GHSA-w8hw-w9fx-j79w for CVE-2026-109618, published 2026-10-21; GHSA-6wmf-4rv5-p3rp for CVE-2026-109619).
- CVE-2026-109618. "For installations that cannot update, setting and enforcing
memory_limit=-1administratively is the primary configuration mitigation." And: "Updating to a PHP release containing the patch to this vulnerability is the _only_ complete mitigation." - CVE-2026-109619. "For the session lifecycle case, set
session.upload_progress.enabled=0administratively and restart every worker." The advisory adds that this does not mitigate the other lifecycle paths.
So session.upload_progress.enabled mitigates CVE-2026-109619 only, not CVE-2026-109618. Updating to a fixed version is the only complete answer to both.
Mitigations the Syhunt repository gives
- Reject multipart POSTs before PHP runs, at Apache or a WAF. Their example is a three-condition rewrite rule that returns 403 to any POST whose Content-Type starts with multipart/form-data, comma variant included. It is deliberately strict: it breaks every upload form and every multipart form on that host.
- Set
session.upload_progress.enabled = Off. Per the advisories above this covers CVE-2026-109619 only. The repository itself says not to present this as a verified fix for both flaws, since a second path in the multipart parser may exist. - If you need uploads and progress, restrict exposure of unnecessary PHP endpoints and plan for the fixed release. Size and rate limits may reduce risk and guarantee nothing.
- Upgrade to a fixed version (see Patch status).
Applying a blocking rule is a change on your production web servers: test it on a staging host first.
When the exploit is unknown, detect the part every exploit of a web server needs: a shell, a new file or a connection out. Those are visible on every server, patched or not.
Worth doing Monday: check that your Linux web servers send process creation with the parent process to your SIEM. Then check which PHP version each one runs.
Change list
- 2026-10-21 UTC: page published with five detections: four new CyRay Sigma rules, not yet fired in a lab, and one existing SigmaHQ rule.
- 2026-10-21 UTC: PHP released the fix (8.5.12, 8.4.27, 8.3.36, 8.2.35; CVE-2026-109618 and CVE-2026-109619). Patch status, title and intro updated from php.net.
- 2026-10-21 UTC: added the promise that this page and the monitoring methods are updated continuously, this change list and the last-updated line.
- 2026-10-21 UTC: three Linux auditd rule keys (web server user starts a program, makes an outbound connection, writes under the web root) passed lab proof: events fired and reached the SIEM.
- 2026-10-22 UTC: the three keys are released in CyRay auditd Distribution 1.0.2 (release recorded 2026-10-21 21:17 UTC). Page section updated.
- 2026-10-23 UTC: CyRay auditd Distribution 1.0.3 adds
cyray-webshell,cyray-webshell-connectandcyray-webroot-writenext to the oldatl-lines (removed in 1.0.4). A lab check of the published package found the new tags load but produce no events, while the old tags do. The cause is that auditd records only the first matching rule's key; a fix with two keys on one line is not released, and it stores the keys in one hex-encoded field that rules must decode first. The page keeps showing the old, working lines and says so. The three auditd Sigma rules list both tag names; the list change has not been re-fired. - 2026-10-24 UTC: CyRay auditd Distribution 1.0.4 is released: each web shell rule is one line with both the
cyray-and the oldatl-tag, which fixes the 1.0.3 failure. The page now shows the 1.0.4 lines and keeps the 1.0.2 single-tag lines for pipelines that match plain tag text. A two-tag record is stored as one hex-encoded field, so plain-text rules, including our three Sigma rules, are not shown to fire on it. Lab check of the published package loaded clean and read events on the old names. - 2026-10-24 UTC: a lab check of the published 1.0.4 package read events under both the
cyray-and theatl-names (user ids 33 and 48). Still not run against a Sigma or SIEM rule. - 2026-10-22 UTC: the auditd section now shows the exact rule lines to add, how to load them, what each catches, and three downloadable Sigma rules (experimental, not yet fired in a lab).
- 2026-10-22 UTC: fourth lab run of the auditd tags: five test events as the web server user reached the SIEM with the right tag, events that should not match produced none, and correlation rules on the tags fired only on the five. The three Sigma rules are still experimental.
- 2026-10-22 UTC: the three auditd Sigma rules fired on real lab auditd records (4 of 4, 2 of 2, 4 of 4), with no match on the audit-rule reload records. Their status changed from experimental to test.
- 2026-10-22 UTC: corrected the mitigation text after reading the PHP advisories:
session.upload_progress.enabledmitigates CVE-2026-109619 only, and the advisory's own mitigation for CVE-2026-109618 is added. The page had leaned on a third-party reading that tied upload progress to CVE-2026-109618. - 2026-10-22 UTC: the multipart POST burst rule was tested on synthetic log rows (fires on a burst, not below the threshold or spread out, no match on 40 non-matching rows). Not yet fired on real web traffic, so it stays experimental.
- 2026-10-22 UTC: fifth lab run on a real
php:8.4.26-apachecontainer: test pages as the web server user produced all three auditd tags (10, 5 and 10 records, euid 33). One public exploit run failed at its heap-leak step, so no event came from a real exploit yet. The SIEM correlation rules fired on the test pages (2, 1 and 2 fires against 10, 5 and 10 raw records, reason not read). - 2026-10-22 UTC: corrected the fifth-run counts: 10 program starts, not 15 (lab found the tag search had also returned the 5 connection records). Added the benign-WordPress noise run: one false fire on PHP mail() sendmail, one on the upload burst, none on the connection rule.
- 2026-10-22 UTC: the PHP-FPM crash burst rule was tested on synthetic log lines (fires on 5 in 299 s, not on 4). The test found that it does not match Apache mod_php crash lines, a coverage gap stated on the page. Not yet fired on real logs, so it stays experimental.
- 2026-10-22 UTC: the crash burst rule is now version 2 and matches Apache mod_php crash lines as well as PHP-FPM (download renamed cyray_lnx_php_worker_crash_burst.yml). Synthetic test: mod_php lines fire, a PHP-FPM plus Apache mix fires, 301 seconds does not; exactly 300 seconds depends on window counting. Not yet fired on real logs.
- 2026-10-24 UTC: lab ran the seven downloadable Sigma rules on logs from real PHP containers: the multipart POST burst rule fired (47 of 50 requests matched), the crash burst rule fired (6 PHP-FPM and 6 Apache lines matched), the three auditd rules matched 0 of 237 records, the web-root-write rule had no records, and the shell-spawn match count is not a count of web server children. The lab used its own Python evaluator, not a SIEM.
- 2026-10-24 UTC: corrected the lab run of the seven Sigma rules: the first evaluation reported 0 of 237 for the auditd rules because of an evaluator defect with two-key records; after the fix the three auditd rules matched 9, 3 and 5 of 254 records, the shell-spawn rule matched 2 web server children with the parent read from audit data, and the web-root write rule matched 2 of 5 file events. Lab's own evaluator on one capture, not a SIEM.
- 2026-10-24 UTC: first SIEM check of one php2shell detection idea: a lab SIEM rule for a web-user .php write under a web root fired on a hand-sent test event and not on a path outside the web root. One of five lab rules; the line was sent by a script, not made by PHP, and it is not the downloadable Sigma rule.
- 2026-10-24 UTC: lab replayed test events (rebuilt from recorded fields, raw capture not kept) through a lab Linux Sysmon connector into a lab SIEM: 1 of 7 Sigma rules fired (web-root write, 3 of 5 file lines, not the two negatives). Not fired: multipart POST burst and PHP-FPM crash burst (no log parser on the lab connector), Apache crash half (no SIEM rule), shell spawn (0 of 4, filter mismatch with the parser), the three auditd rules (not replayed here; an earlier run fired them 2, 1 and 2 times). Synthetic test events, not the real exploit.
- 2026-10-24 UTC: a lab SIEM rule for a web server spawning a shell fired on a hand-made Sysmon-format line (shell started by the web user under Apache) and not on an ls control line; a second lab SIEM rule for a PHP command-line inline reverse shell fired on its line. Hand-made lines, not made by a web server process; not the downloadable Sigma rules.
- 2026-10-25 UTC: ran the two log rules (PHP-FPM crash and multipart POST burst) on a real PHP-FPM and Apache capture from one lab VM, with synthetic crashes made by a test page, not the exploit. The PHP-FPM crash pattern matched 4 of 4 crash lines. The Apache crash half (mod_php wording) matched 0 of 13 Apache error lines, because a PHP-FPM crash logs FastCGI errors there instead. The multipart rule matched 0 of 10 Apache access lines, because the default combined format has no Content-Type. (Superseded by the next entry: a SIEM run for PHP-FPM now exists, through a lab-only parser.)
- 2026-10-25 UTC: lab SIEM proof for PHP-FPM crash lines: a lab-only draft parser parsed 21 of 21 real PHP-FPM lines, the 4 crash lines carried the right process id, pool, signal 11, SIGSEGV and seconds from start, and a lab SIEM raised its PHP-FPM child-crash-burst correlation event from the 4 crashes. Caveats: event times read 3 hours early, the parser is lab-only and not shipped, one lab VM, synthetic crashes, not the exploit. The lab SIEM rule fired on 4 while the downloadable Sigma rule needs 5. Corrected the earlier statement that no PHP-FPM SIEM run exists. Apache limits: AH01067 and AH01075 fire on any FastCGI failure, so no new Apache FastCGI-error rule is offered; the multipart POST rule needs Content-Type in the log, which only a custom Apache LogFormat, a ModSecurity audit log or a WAF or reverse-proxy log carries.