Artica EdgeGuard refuses any request carrying the form field PHP_SESSION_UPLOAD_PROGRESS, the attack surface named by the published PHP2Shell workaround.
There is nothing to create, scope or compile: the rule is active on every protected site from the moment the agent updates.
CVE-2026-109618 and CVE-2026-109619, reported together as "PHP2Shell", are two remote code execution flaws in PHP, each scored 9.2 on CVSS v4.
Anyone able to send HTTP requests to a PHP application can reportedly run code on the server without logging in.
This refuses a request on its way to PHP. It does not repair PHP. Update the PHP servers behind the reverse proxy as soon as fixed versions are published.
The technical details of both flaws were unconfirmed when this was written. What is published is a workaround: turn off PHP's upload-progress feature (session.upload_progress.enabled), which is what points at the surface.
PHP begins tracking an upload when a request carries a form field named PHP_SESSION_UPLOAD_PROGRESS. Ordinary web applications essentially never send it, so its presence is the signal — and refusing it is the same decision as turning the feature off, taken one hop earlier and without touching the PHP configuration.
Unlike the rule described for the legacy Web Application Firewall, there is no rule to create, no phase to choose, no scope to set and nothing to compile.
The protection ships with the engine and is on for every site, including sites whose WAF settings have never been touched.
It can be switched off, and that is the only way it is off: WAF → Global Settings → Attack Detection → Core Detection, last entry in the list.

The switch inherits down the hierarchy like every other detection:
| Global Settings | the default for the whole installation — stored as on |
| Groups | overrides the global default for the sites in a group |
| Tenants | overrides the group, for one site |
| Path Rules | overrides the site, on one path |
At the three levels below global, leaving the switch untouched means inherit — it does not mean off. The engine treats an unset value as enabled, so a site whose configuration predates this release keeps the protection rather than quietly losing it.
The rule reads parameter names, not the text of the request, in every envelope a form field can arrive in.
| Multipart upload | curl -F PHP_SESSION_UPLOAD_PROGRESS=test -F file=@test.txt — the shape the published proof of concept uses |
| Urlencoded body | name=x&PHP_SESSION_UPLOAD_PROGRESS=test |
| Query string | ?PHP_SESSION_UPLOAD_PROGRESS=test |
| Any capitalisation | php_session_upload_progress is refused too. Deliberately wider than PHP: PHP indexes $_POST with the configured name and those keys are case-sensitive, so a differently-cased field almost certainly does not trigger the feature. Refusing it anyway costs nothing — no application sends a field by that name in any casing — and covers the gap between how a WAF parses a request and how PHP does. |
| Percent-encoded names | PHP%5FSESSION%5FUPLOAD%5FPROGRESS is the same field |
A request that merely mentions the field is left alone. A value is not a name.
This distinction is the whole design rather than a detail. A rule that matched the text of the request would refuse this page — its own documentation contains the field name, and publishing it sends that text in a form. It would then refuse the ticket, the changelog and the advisory that quote it, and the security engineer pasting the name into a search box.
?q=PHP_SESSION_UPLOAD_PROGRESS |
allowed — a search for the name |
body=the field PHP_SESSION_UPLOAD_PROGRESS is what PHP watches |
allowed — somebody writing about it |
a multipart part named content whose value quotes the field |
allowed — a page being published |
an ordinary upload: csrf_token, file, description |
allowed — nothing changes for sites that do not send the field |
These three cases ship as part of the appliance's self-test, which runs them alongside seven others and reports a pass or a failure for each:
./tools/waf-selftest.sh https://www.example.com
To send them by hand, from any machine, use the scheme and port the site actually listens on.
echo test > test.txt
# With the field: EdgeGuard answers 403
curl -s -o /dev/null -w '%{http_code}\n' \
-F PHP_SESSION_UPLOAD_PROGRESS=test -F file=@test.txt https://www.example.com/
# Without it: the site answers as usual
curl -s -o /dev/null -w '%{http_code}\n' \
-F file=@test.txt https://www.example.com/
# The name as a value: also answered by the site, which is the point
curl -s -o /dev/null -w '%{http_code}\n' \
--data-urlencode 'q=PHP_SESSION_UPLOAD_PROGRESS' https://www.example.com/
Each refusal is one row in Monitoring → Security Events, under the attack type PHP Upload-Progress Field.

That row is not a test. It is a real exploitation attempt, and it is worth reading closely.
A POST to /index.php from an address in Germany, carrying no user agent at all — not a forged one, none: a bare tool rather than anything pretending to be a browser. It scored 59, which is not a probe. Thirteen rules fired on one request.
Opening the row shows what the request was and what decided it.

This rule contributed 5 against a threshold of 5, so it refuses on its own without needing anything else to agree. What the rest of the list shows is why the score reached 59: the field did not merely exist, it carried a payload. The SQL-injection, command-injection and XML-injection detectors each fired inside the PHP_SESSION_UPLOAD_PROGRESS parameter, and CRS 920120 — attempted multipart/form-data bypass — fired on the envelope, alongside the PHP-injection and remote-code-execution rules of the 932 and 933 families.
Under Matched Rules, management plane 2.4.43 and later show the two CVE identifiers linked to their NVD entries, and the evidence beside each rule says where it matched and what text matched it.
The value is never stored. Where the matched text would be, the event reads
(value withheld: sensitive parameter)— which is why the screenshot above shows a request that carried a webshell without showing the webshell. The value is not what this rule reads, and keeping it would put request contents in the event table.
If the rule ever refuses something legitimate, it is switched off the way every other rule is: an exclusion scoped to a path, an address, a method or a site, from the event itself — Mark false positive on the event detail, or WAF → False Positives.
That leaves an audit entry naming who did it and a hit counter showing whether the exclusion is still doing anything.
Switching the detection off entirely is the toggle above. Both are reversible; the exclusion is narrower, and narrower is better.
Stated plainly, because each of them changes what this protects against.
security_body_preview_size, 4 KB by default. session.upload_progress.name can change the field name. The name then belongs to the application and the engine cannot know it.| Engine 3.0.44 | the rule, active with no configuration |
| Engine 3.0.45 + management plane 2.4.40 | the console switch, inherited down the hierarchy |
| Engine 3.0.46 | the CVE identifiers attached to each event |
| Management plane 2.4.43 | those identifiers shown on the event, linked to the NVD |