PHP2Shell: how to know if your server was exploited with a single grep (and why your access.log won't find it)

To know if PHP2Shell was exploited on your server, AWS published a single signature: POST requests with PHP_SESSION_UPLOAD_PROGRESS in the body, from October 17 onwards. The problem is that on a nginx or Apache server with default configuration, that grep returns nothing, even if you’ve been attacked.

Here’s what AWS found, where that signature actually lives, and what you can review today on a server that doesn’t store request bodies. The vulnerabilities are CVE-2026-109618 and CVE-2026-109619.

What did AWS see before PHP2Shell disclosure?

Amazon Threat Intelligence detected a PHP2Shell exploitation attempt on October 17, 2026 in MadPot, its honeypot network. That was four days before the PHP project lifted the embargo and published the fix. The request came from an IP associated with a commercial anonymization network. According to AWS, this indicates a real threat actor, though they don’t know how they obtained the exploit details while the flaw was still under embargo.

The rest of the timeline was very quick:

  • Around 02:00 UTC on October 21: AWS detected the first public proofs of concept, twelve hours before the embargo was lifted.
  • Between 02:00 and 08:00 UTC that day: more attempts arrived against MadPot from another anonymized infrastructure. The payloads were different, suggesting multiple actors.
  • At 14:00 UTC on October 21: the embargo was lifted and patches were released. Within hours, new proofs of concept appeared, which were used almost immediately against AWS sensors.

AWS also clarifies that their analysis is ongoing and it’s unclear whether any of those attempts would have succeeded against real systems. The clarification matters: an attempt against a honeypot is not a confirmed intrusion.

The practical instruction, however, is clear. AWS recommends starting the investigation from the first known exploitation, not from the disclosure date, and for PHP2Shell that means October 17.

What’s the PHP2Shell signature in the logs?

According to AWS, the signature is a POST request with the string PHP_SESSION_UPLOAD_PROGRESS inside an upload form. That string is the default value of the PHP directive session.upload_progress.name: the name of the form field that triggers upload progress tracking.

Two pieces of official documentation explain why this affects almost any server:

  • The feature is enabled by default. The PHP manual indicates that session.upload_progress.enabled has the value "1" by default. If no one disabled it, your PHP is listening for that field.
  • It runs before your code. The manual clarifies that upload progress is written to the session before any script executes. According to AWS, the two CVEs are in how PHP processes multipart/form-data requests, before your application receives it. Your framework’s validation never sees that request.

AWS confirms the signature but doesn’t publish the internal mechanism of the exploit, so this article doesn’t describe one.

Why doesn’t grep find PHP2Shell in your access.log?

Because the string is in the request body, and access logs by default don’t store the body.

The predefined combined format of nginx, which is what’s used if you don’t specify another, logs this:

'$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"'

Apache’s combined format logs the same fields: client IP, identity, user, time, request line, status code, response size, referer, and user agent.

Neither stores the body, and neither stores the Content-Type header. That’s why this command:

grep PHP_SESSION_UPLOAD_PROGRESS access.log

returns nothing on a default server, whether you’ve been attacked or not. An empty result here doesn’t tell you anything.

Where grep actually works: AWS WAF and logs with request body?

Grep only works on logs that stored request bodies:

  • AWS WAF. If you use the managed rule group for PHP applications (AWSManagedRulesPHPRuleSet), version 2.3 adds the PHP2Shell_BODY rule. Requests that match receive the tag awswaf:managed:aws:php-app:PHP2Shell_Body. Look for that rule or tag in your AWS WAF logs. AWS made version 2.3 the default on October 22. If your web ACL fixes a static earlier version, the rule isn’t active.
  • A WAF or reverse proxy configured to log request bodies. If you enabled that logging on purpose, search for the string from October 17 onwards.

There’s a limit worth keeping in mind. AWS WAF only inspects the first part of the body: 8 KB on Application Load Balancer and AppSync, and 16 KB by default (up to 64 KB) on CloudFront and API Gateway. What exceeds that limit isn’t inspected, so a clean WAF log is a good sign, but not proof.

How do you review a server that doesn’t store request bodies?

If you can’t see the signature, work with the rest of AWS’s checklist. These steps go from least to most effort.

Indicators of compromise (IOC): investigate, don’t block

AWS published six source IP addresses:

IP Address Date Observed
103.216.220.92 Oct 17, 2026
31.223.184.166 Oct 21, 2026
37.19.200.5 Oct 21, 2026
134.209.159.64 Oct 21, 2026
103.114.163.188 Oct 21, 2026
95.217.98.37 Oct 21, 2026

AWS clarifies that they belong to shared anonymization infrastructure and aren’t exclusive to this activity, so it recommends investigating any match rather than blocking the addresses. In the two default formats, the client IP is the first field, so a match is easy to find:

grep -E '^(103\.216\.220\.92|31\.223\.184\.166|37\.19\.200\.5|134\.209\.159\.64|103\.114\.163\.188|95\.217\.98\.37) ' access.log

Run it on the file specified by your access_log directive (nginx) or CustomLog (Apache), including rotated copies covering October 17. A match on a POST request deserves a closer look. No matches don’t rule you out: at the time this note was published (October 23), the list only reflects what AWS saw on its own sensors and will probably grow.

Review POST requests from October 17 onwards

In both formats, the request line starts with the method, so "POST filters the log to the requests that matter. Look for POST to paths that normally don’t receive them, bursts from the same IP, and 500 responses.

Read PHP and server error logs

AWS recommends enabling error logging and reviewing Apache, FPM, or proxy logs for repeated warnings, deprecated function notices, fatal errors, and timeouts on paths an attacker can reach. It also warns that the absence of errors is not proof of security.

Review the server itself

AWS’s last recommendation is to look for unexpected process executions or modified files on your web application servers. If something doesn’t add up there, treat the server as compromised, whatever the logs say.

What to do while you investigate?

Update first. The patched versions are PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35. According to AWS, updating is the only complete solution for both CVEs and there’s no configuration-only workaround that covers both.

While you can’t update, AWS recommends these steps:

  • Set session.upload_progress.enabled=0 and restart all PHP workers. AWS suggests keeping it disabled even after patching. You can confirm the active value in the output of phpinfo().
  • On PHP 8.5, set fatal_error_backtraces=0 and restart all workers.
  • Consider blocking multipart/form-data uploads if your application doesn’t need them, after checking what would break.

PHP 8.1 and earlier no longer receive official fixes. If you’re using one, check with your distribution or extended support provider for a backport, or plan moving to a supported branch.

To confirm which version you need, see our guide CVE-2026-109618 and CVE-2026-109619: which PHP version you need and when the patch comes out. To check if your PHP is vulnerable, use the official Codex skill.

Why does October 17 matter beyond PHP?

AWS draws a broader lesson from this case. Coordinated disclosure is still the right model, but an embargo only protects defenders until someone else finds the same flaw. AWS also notes that AI-assisted analysis makes it cheaper to turn a patch diff into a working exploit, so the time between patch and exploitation is shrinking.

For systems exposed to the internet, AWS recommends three changes:

  • Measure critical vulnerability patch windows in hours, not 7 or 30-day SLAs.
  • Start investigations from the first known exploitation, not from the advisory date.
  • Invest in detections that don’t depend on a signature for a specific CVE.

The PHP2Shell signature came after the first attacks. The question you can answer today with your logs is the same one you could have answered on October 17: what POST requests reached your PHP that shouldn’t have?


What about you? Does your stack store request bodies somewhere: in the WAF, in the proxy, or nowhere?

Where could you search for PHP_SESSION_UPLOAD_PROGRESS today?
  • In AWS WAF logs
  • In another WAF or proxy that stores request bodies
  • Nowhere: I only have default access logs
  • I prefer another option (tell us which)
0 votantes

Comment below or, if this article reached you by email, reply directly to the email: your reply gets published here.

Related: CVE-2026-109618 and CVE-2026-109619: which PHP version you need and when the patch comes out