Repository navigation
Conversation
… evasion-trivial)
… evasion-trivial)
|
@microsoft-github-policy-service agree |
There was a problem hiding this comment.
🟡 Changes recommended
The DeviceProcessEvents rule splits the username-only AccountName column on backslash (breaking the Account entity) and maps the FileHash Algorithm identifier to the hash-value column, both of which produce incorrect entity data.
2 open issues
What changed in this PR
This PR adds four standalone Microsoft Sentinel Analytic Rule templates under Detections/ that detect exploitation of the (community-reported) PHP session.upload_progress heap use-after-free pair CVE-2026-109618/109619. Three rules target IIS access logs (W3CIISLog) for POC-specific indicators and a worker-crash behavioral pattern, and one targets DeviceProcessEvents for post-RCE shell spawns under PHP worker processes. The rules fit the repository's convention for community-contributed standalone detections (valid GUIDs, required metadata block, valid connectorIds, MITRE mappings).
Changes:
- Add two "exact/zero-FP" IOC rules (hardcoded POC User-Agent strings and multipart boundaries) on
W3CIISLog. - Add one behavioral
W3CIISLogrule correlating large multipart POSTs +PHPSESSIDcookie + 200/502/503 crash ratio. - Add one behavioral
DeviceProcessEventsrule for shell/recon processes spawned under php-fpm/lsphp/php.
| File | Description |
|---|---|
Detections/W3CIISLog/phpup2026-POCUserAgent.yaml |
Flags POC User-Agent IOCs; column refs and entity mappings valid. |
Detections/W3CIISLog/phpup2026-POCBoundary.yaml |
Flags POC multipart boundary IOCs; column refs and entity mappings valid. |
Detections/W3CIISLog/phpup2026-WorkerCrashPattern.yaml |
Behavioral worker-crash correlation; scStatus-as-string usage consistent with existing W3CIISLog rules. |
Detections/DeviceProcessEvents/phpup2026-PHPShellSpawn.yaml |
Post-RCE shell spawn detection; contains a broken Account entity mapping (AccountName split) and a malformed FileHash Algorithm mapping. |
Review effort: Balanced
…m is computed string
|
Fixed in the latest commit. Account entity — FileHash entity — Version bumped to |
POCBoundary.yaml and POCUserAgent.yaml keyed on one released tool's hardcoded multipart boundary and User-Agent strings — trivial to change, won't survive a rewritten exploit. Replace with a structural proxy using only standard W3C extended log fields (POST + PHPSESSID cookie + oversized body), which complements WorkerCrashPattern.yaml by also catching a one-shot exploit that never crashes a worker.
CI's NonAsciiValidations test requires detection YAML to be pure ASCII. WorkerCrashPattern.yaml and PHPShellSpawn.yaml (U+2014 em-dash) and the new UploadProgressOversizedPost.yaml (same) all had one. Replaced with a plain hyphen in each.
…o this PR, not a modification to master, so the version-check script won't actually require this, but keeping version metadata consistent with PHPShellSpawn.yaml's bump)
…remise Both CVEs were originally treated as the same session.upload_progress UAF. PHP's official 2026-10-21 advisory splits them: CVE-2026-109619 is this bug; CVE-2026-109618 is a separate, unrelated OOM-corruption issue. Corrected tags/descriptions to CVE-2026-109619 only across all three rules. Also flags that WorkerCrashPattern.yaml's 502/503 worker-crash signal is unvalidated: a real PHP 8.3.6 lab given the documented trigger precondition (a multi-second stall between multipart file parts) returned a clean 200 OK, not a crash. See https://github.com/rjmendez/phpup-2026 ANALYSIS.md's mechanism finding.
…remise Both CVEs were originally treated as the same session.upload_progress UAF. PHP's official 2026-10-21 advisory splits them: CVE-2026-109619 is this bug; CVE-2026-109618 is a separate, unrelated OOM-corruption issue. Corrected tags/descriptions to CVE-2026-109619 only across all three rules. Also flags that WorkerCrashPattern.yaml's 502/503 worker-crash signal is unvalidated: a real PHP 8.3.6 lab given the documented trigger precondition (a multi-second stall between multipart file parts) returned a clean 200 OK, not a crash. See https://github.com/rjmendez/phpup-2026 ANALYSIS.md's mechanism finding.
…remise Both CVEs were originally treated as the same session.upload_progress UAF. PHP's official 2026-10-21 advisory splits them: CVE-2026-109619 is this bug; CVE-2026-109618 is a separate, unrelated OOM-corruption issue. Corrected tags/descriptions to CVE-2026-109619 only across all three rules. Also flags that WorkerCrashPattern.yaml's 502/503 worker-crash signal is unvalidated: a real PHP 8.3.6 lab given the documented trigger precondition (a multi-second stall between multipart file parts) returned a clean 200 OK, not a crash. See https://github.com/rjmendez/phpup-2026 ANALYSIS.md's mechanism finding.
|
Pushed a correction based on PHP's official advisory (published 2026-10-21, after this PR was opened) and a real lab reproduction: CVE attribution fix. The advisory splits what this PR originally treated as one bug class into two unrelated CVEs: CVE-2026-109619 ("RCE via stale global state") is this
Full writeup: https://github.com/rjmendez/phpup-2026 (see |
Ofer Inbar (oferInbar)
left a comment
There was a problem hiding this comment.
Please address the detection-quality and validation concerns below before these templates are presented as validated CVE detections. Please start each affected detection description with the literal [Unvalidated] until representative testing supports its CVE-specific claim. For the shell-spawn rule, this qualification concerns CVE attribution, not the general usefulness of PHP child-process monitoring. I acknowledge the existing UNVALIDATED paragraph in WorkerCrashPattern; the request is to make that status explicit at the beginning of the description and consistent across unsupported CVE-specific claims.
PHP's official advisory describes stale state crossing request boundaries, with a producer, an interruption/reset failure, and a later consumer on the same worker. It does not validate the community write-up's ordinary multipart-stall/dangling-counter explanation: GHSA-6wmf-4rv5-p3rp . The community analysis itself now disclaims that mechanism: https://github.com/rjmendez/phpup-2026/blob/bb1dee82e31d35daad4fdb0b60cec8fb83ef38e8/ANALYSIS.md#L20-L33 .
Please also align the PR title and summary with the corrected CVE scope. The current title/body still group CVE-2026-109618 and CVE-2026-109619 together, whereas the rule files now reference 109619 only. These comments are based on static review and published evidence, not execution against a live Sentinel workspace.
Moved the unvalidated caveat to the start of each description instead of mid-paragraph, per review request. Independently verified both advisories (GHSA-6wmf-4rv5-p3rp, GHSA-w8hw-w9fx-j79w) against the primary source: CVE-2026-109619 is a cross-request, cross-worker UAF (a fatal-error-interrupted request leaves a stale global pointer; a later, different request on the same worker consumes it during early-init -- upload-progress handling is one confirmed consumer path among others), not the within-one-request heap-spray-via-multipart- stall mechanism this repo's rules were built around. That changes the scope of what's unvalidated: - WorkerCrashPattern.yaml: the 502/503 crash premise specifically (already disproven in a real lab) plus the broader mechanism mismatch. - UploadProgressOversizedPost.yaml: the heap-spray rationale behind the oversized-body heuristic doesn't match the real mechanism either -- the correlation with actual exploitation is unconfirmed, not just the crash angle. - PHPShellSpawn.yaml: scoped the qualifier to CVE attribution only, per review -- the rule's general value as a PHP child-process-spawn indicator doesn't depend on which mechanism caused the RCE.
|
Both addressed, and independently verified against the primary source (not just taken on your word or the repo's):
PR title/body updated to scope to CVE-2026-109619 only, with both advisories linked directly and the mechanism correction stated up front rather than left implicit. |
…rtifact Same correction pushed to github:rjmendez/phpup-2026 (commit f99b300). The prior revision keyed on a single-request multipart-stall heap-spray mechanism derived from the leaked PoC artifact's own claims; that does not match the real fix (php/php-src 82f8f31b16a, 'Avoid state corruption via bailout') and VulnCheck independently reports none of the public PoCs they reviewed have the correct mechanism. - UploadProgressOversizedPost.yaml: oversized-body heuristic replaced with the real, fix-confirmed duplicate-Content-Disposition-header OOM-amplification signal (requires AzureDiagnostics/WAF body inspection, not W3CIISLog -- standard access logs don't carry body content). Doesn't require session.upload_progress at all, since the amplification vector works on any multipart endpoint. - WorkerCrashPattern.yaml: the unvalidated 502/503 crash-ratio premise is retired, not kept with a caveat. Repurposed into the burst-over-time complement to the above (repeated amplification attempts from one source), on the real signal instead of a disproven one. - PHPShellSpawn.yaml: description updated to the confirmed mechanism (cross-request bailout state corruption, session.upload_progress as one confirmed consumer proven by the PHP-8.0 EOL branch's outright disable of that function) instead of the dead spray-chain claim. Rule logic unchanged -- still generically valid regardless of mechanism.
|
Update since the last round: PHP's official fix commit ( Rewrote all three rules around the actual fix diff rather than keep caveating the old theory:
PR title/body updated to match. Full writeup (and the same rewrite applied to the private research repo) at https://github.com/rjmendez/phpup-2026/blob/master/ANALYSIS.md. |
…nt-Disposition Same under-inclusivity bug caught and fixed in the sibling private-repo Suricata rule: a real lab reproduction (phpup-2026's examples/pcaps/phpup2026-real-oom-amplification.pcap) used an arbitrary short header name instead of literal Content-Disposition, for maximum entries-per-byte. countof(requestBody_s, "Content-Disposition") >= 2 would miss it. Replaced with extract_all + mv-expand + summarize to flag any header name repeated within one request body, independent of which name an attacker picks. Version bumped to 2.1.0.
|
Follow-up fix, same day: caught the same under-inclusivity bug in this PR's duplicate-header rule that I'd already fixed in the sibling Suricata rule over in the reference repo. Replaced it with a generic "any header name repeated within one request body" check ( |
Ofer Inbar (oferInbar)
left a comment
There was a problem hiding this comment.
Thanks for following up on the upstream php/php-src fix commit (82f8f31b16a) and retiring the unvalidated 502/503 worker-crash ratio premise.
However, the two rewritten AzureDiagnostics rules (phpup2026-UploadProgressOversizedPost.yaml and phpup2026-WorkerCrashPattern.yaml) currently fail KQL compilation against live Log Analytics workspaces (SEM0100: Failed to resolve scalar expression named 'requestBody_s') and have logic issues that would cause them to either never match or match every multi-field HTML form submission. Please see the inline comments below.
…position bug Caught during a full validity sweep of the reference repo (github.com/rjmendez/phpup-2026): this rule's burst-detection query used the same countof(requestBody_s, "Content-Disposition") >= 2 literal match already fixed in UploadProgressOversizedPost.yaml, missed in that earlier pass. Fixed the same way -- extract_all + mv-expand + summarize to flag any repeated header name. Version bumped to 2.1.0.
|
Follow-up: doing a full validity sweep of the reference repo turned up the same under-inclusive bug in |
…remaining rule per review
Ofer Inbar's review confirmed, and I independently verified against
Microsoft's own AzureDiagnostics/AGWFirewallLogs schema reference
(learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/agwfirewalllogs),
that 'requestBody_s' is not a real column: Application Gateway/Front Door
WAF logs do not expose raw request bodies at all (the closest field,
DetailedData, only contains a snippet when a managed ruleset rule
already matched something -- not a general-purpose field you can run
your own body-pattern query against). Both rewrites of this query
('fix the literal Content-Disposition match' then 'fix the KQL syntax')
were built on a field that doesn't exist, on a table that doesn't carry
the data this detection needs. Not fixable by renaming a field --
removing phpup2026-UploadProgressOversizedPost.yaml and
phpup2026-WorkerCrashPattern.yaml entirely rather than attempt a further
patch, per the reviewer's explicit recommendation and the obvious
'don't salvage bad data' call once the data source itself doesn't exist.
Fixed phpup2026-PHPShellSpawn.yaml per the remaining review comments:
- InitiatingProcessFileName switched from has_any substring matching to
an anchored regex, and now includes apache2/httpd -- CVE-2026-109618
was demonstrated against Apache mod_php specifically, where PHP runs
in-process inside the Apache worker, not as a separate php-fpm binary.
- FileName switched from has_any (substring) to in~ (exact,
case-insensitive): has_any on tokens like 'sh', 'id', 'nc' matched
unrelated binaries like openssh, cloud-init, systemd-networkd.
- Description now leads with '[Unvalidated]' and states plainly that
CVE attribution is unvalidated (the reference capture used a direct
shell_exec() test endpoint, not either CVE's actual mechanism), with
benign-use tuning and overlap-with-existing-content notes added per
the review.
- Reset version to 1.0.0 (new, unmerged file) and dropped the CVE
numbers from the rule name, since the rule's value doesn't depend on
CVE attribution.
|
Ofer Inbar (@oferInbar) thank you for the thorough review, especially the live-workspace KQL compilation check -- that caught a real mistake that static review of my own work hadn't. Summary of what changed in response to both your reviews (replied inline on each thread too):
This PR is now much smaller in scope than where it started, which is the right outcome given what didn't hold up. |
Resolving comments on the companion Azure-Sentinel PR (Azure/Azure-Sentinel#15377) surfaced a real schema error: 'requestBody_s' is not a real AzureDiagnostics column. @oferInbar's review reported a live SEM0100 compilation failure; independently confirmed against Microsoft's own schema reference (learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/agwfirewalllogs) that AGWFirewallLogs (what Application Gateway/Front Door WAF data feeds into) has no raw-request-body field at all -- the closest, DetailedData, only holds a snippet when a managed ruleset rule already matched something, not a general-purpose field. Not fixable by renaming a field; built three separate rules/queries across this repo (and this PR) on data that was never exposed by the log source. Retired six queries in rules/sentinel/phpup2026.kql (T2-1, T2-3, T3-3, T3-4, H1, H2) and deleted the two analytics-rules YAML files that depended on them (phpup2026-UploadProgressOversizedPost.yaml, phpup2026-WorkerCrashPattern.yaml), mirroring the PR's resolution. Also fixed, same review round: T3-1/T3-2 in phpup2026.kql and the remaining phpup2026-PHPShellSpawn.yaml both had has_any substring matching on short tokens ('sh', 'id', 'nc') that match unrelated binaries (openssh, cloud-init, systemd-networkd) -- switched to exact/anchored matching, and added apache2/httpd coverage since CVE-2026-109618 was demonstrated against Apache mod_php specifically. PHPShellSpawn's description now leads with [Unvalidated] since its CVE attribution was never independently confirmed. The duplicate-header-name OOM-amplification signal these retired KQL queries couldn't carry over AzureDiagnostics is still available via Suricata (rules/suricata.rules SID 9109633), which inspects the real HTTP body directly and is live-fire verified against real captures.
… say is usually unpopulated Caught while cross-checking a real, already-merged detection in this same repo (Detections/DeviceProcessEvents/ SolarWinds_SUNBURST_Process-IOCs.yaml) for the TimeGenerated field convention. Microsoft's own DeviceProcessEvents schema reference states SHA256 'is usually not populated - use the SHA1 column when available'. Switched to SHA1. Version bumped to 1.0.1.
|
One more fix, found while cross-checking the TimeGenerated/DeviceProcessEvents field convention against this repo's own SolarWinds_SUNBURST_Process-IOCs.yaml: this rule's FileHash entity was mapped to SHA256, which Microsoft's own DeviceProcessEvents schema docs say "is usually not populated -- use the SHA1 column when available." Switched to SHA1. Version bumped to 1.0.1. |

Summary
Adds one Microsoft Sentinel Analytics Rule template:
Detections/DeviceProcessEvents/phpup2026-PHPShellSpawn.yaml- a shellor reconnaissance tool spawned directly under a PHP worker process
(php-fpm, lsphp, or Apache mod_php). Generic PHP child-process-monitoring
indicator; CVE attribution is explicitly unvalidated (see the rule's own
description).
History (why this PR shrank from three rules to one)
This PR went through several rounds of review and rewrite:
Original submission: three rules built on a single-request
multipart-stall heap-spray mechanism taken from a leaked PoC
artifact's own claims.
First rewrite: VulnCheck independently reported that public PoCs
reviewed (plausibly including the one this analysis started from) do
not have the correct mechanism, and a real lab reproduction of the
old mechanism's documented precondition produced a clean
200 OK,not a crash. Rewrote all three rules around the actual PHP fix
commit (
php/php-src82f8f31b16a, "Avoid state corruption viabailout") instead of the PoC's claims.
Second rewrite (prompted by Ofer Inbar (@oferInbar)'s review): fixed an
under-inclusive literal
"Content-Disposition"string match in thetwo
AzureDiagnostics-based rules with a generic duplicate-headercheck.
This round, removing two of the three rules entirely: Ofer Inbar (@oferInbar)'s
follow-up review reported that both
AzureDiagnosticsrules failKQL compilation against a live workspace (
SEM0100: Failed to resolve scalar expression named 'requestBody_s'), and separately have astructural logic flaw (matching across MIME part boundaries rather
than within one part, so any legitimate multi-field form submission
would match). I independently verified against Microsoft's own schema
reference
(https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/agwfirewalllogs)
that
AzureDiagnostics/AGWFirewallLogsdoes not expose a rawrequest-body field at all - the closest column,
DetailedData, onlycontains a snippet when a managed ruleset rule has already matched
something, not a general-purpose field a custom query can pattern-match
against. This isn't fixable by renaming a field; the data this
detection needs isn't exposed by the log source at all. Removed
phpup2026-UploadProgressOversizedPost.yamlandphpup2026-WorkerCrashPattern.yamlrather than attempt a furtherpatch.
Also fixed the remaining rule,
phpup2026-PHPShellSpawn.yaml, perthe same review round: added Apache mod_php coverage (the SAPI the
advisory actually names for confirmed RCE, where PHP runs in-process
inside the Apache worker rather than as a separate php-fpm binary),
switched short substring matches (
"sh","id","nc") to exactmatching to stop false hits on unrelated binaries like
opensshorcloud-init, and led the description with[Unvalidated]since CVEattribution for this rule was never independently confirmed (the
reference capture used a direct
shell_exec()test endpoint, noteither CVE's actual mechanism).
Rule
Detections/DeviceProcessEvents/phpup2026-PHPShellSpawn.yamlBackground on the underlying PHP vulnerability pair this started from
(CVE-2026-109618 / CVE-2026-109619,
php/php-srccommit82f8f31b16a):https://github.com/rjmendez/phpup-2026
🤖 Generated with Claude Code