When the critical PHP2Shell vulnerabilities (CVE-2026-109618 / CVE-2026-109619) dropped, the security community scrambled for detection tools. However, standard scanners have a fatal flaw: they fail against modern WAFs. If a server sits behind Cloudflare or Akamai, standard header grabbing returns nothing.
To solve this, I developed http-vuln-php2shell.nse, an open-source Nmap Scripting Engine (NSE) module.
The response has been incredible—over 217 clones on GitHub in the first few days. But getting here wasn't just about writing Lua; it was about solving specific architectural challenges to make the script reliable, cross-platform, and WAF-resistant.
Here is a deep dive into the technical hurdles we solved and how the script works under the hood.
🛑 The 3 Technical Challenges We Had to Solve
- The "Silent" WAF (Cloudflare/Akamai)
If an infrastructure sits behind a CDN, the HTTP X-Powered-By and Server headers are usually stripped or replaced. A passive scan returns nil.
The Solution: I implemented a multi-layered inspection. If the headers lie, the script relies on passive fingerprinting via the PHPSESSID cookie (Technique C). Even if Cloudflare hides the server version, it often fails to strip the session cookie, revealing the underlying PHP backend.
- False Positives on Patched Servers
Just detecting "PHP" isn't enough; you need to know if it's vulnerable.
The Solution: We implemented a strict syntactic evaluation of the PHP version. The script compares the detected version against the patched thresholds for October 2026 (PHP 8.2.35, 8.3.36, 8.4.27, 8.5.12). If the server runs PHP 8.5.12, the script returns INFO (not vulnerable). If it runs PHP 7.4, it flags it as LIKELY VULNERABLE.
- The Nmap vulns Library Bug on Windows
This was the hardest part. Nmap has a native vulns library to format vulnerability outputs beautifully. However, on Windows (Nmap 7.98+), the vulns.Report:new() object has a nasty habit of crashing the entire script (ERROR: Script execution failed).
The Solution: I discarded the buggy vulns library and built a custom native reporting engine. By using Lua's table.insert, the script perfectly mimics Nmap's official VULNERABLE: output block without any dependencies, ensuring 100% cross-platform stability.
🔍 The 5 Inspection Vectors
The engine doesn't just do a single request; it runs a cascading behavioral inspection designed for black-box environments:
· A & B (Headers): Parses X-Powered-By and Server for quick wins.
· C (Stateful Fingerprinting): Looks for PHPSESSID cookies in the root response.
· D (Forced Exceptions): Injects malformed parameters (/index.php?[]=) to force the server to reveal Stack Traces (if display_errors is on).
· E (Differential Verification): Compares a baseline request against a malformed input. If the malformed input triggers a 500 error or PHP trace not present in the baseline, it flags anomalous backend behavior.
💻 The Result in Terminal (Two Real Scenarios)
To truly understand the power of this script, let's look at how it adapts to different server configurations. The output isn't always the same; it depends on what the server exposes and what the WAF hides.
Scenario 1: Fully Exposed Environment (Modern Unpatched PHP)
In this scenario, the server is completely exposed. It doesn't hide the Server header, and it leaks the X-Powered-By header. The script easily triggers Techniques A and B. Notice that it catches a modern PHP 8.x version that missed the October 2026 patch.
| http-vuln-php2shell:
| VULNERABLE:
| Title: PHP2Shell (CVE-2026-109618 / CVE-2026-109619) Potential Exposure
| IDs: CVE:2026-109618, CVE:2026-109619
| State: LIKELY VULNERABLE
| ALERTA (A): PHP2Shell potencial via X-Powered-By -> PHP/8.4.25
|_ ALERTA (B): PHP2Shell potencial via cabecera Server -> PHP/8.4.25
Scenario 2: WAF / Proxied Environment (Header Obfuscation)
Now, look at what happens when a proxy or WAF (like Cloudflare) is trying to hide the server version. The Server header is stripped, so Technique B fails. However, the script is resilient: it still catches the X-Powered-By header (Technique A) and, more importantly, uses passive fingerprinting (Technique C) to detect the PHPSESSID cookie, confirming the PHP backend even when the WAF tries to hide it.
| http-vuln-php2shell:
| VULNERABLE:
| Title: PHP2Shell (CVE-2026-109618 / CVE-2026-109619) Potential Exposure
| IDs: CVE:2026-109618, CVE:2026-109619
| State: LIKELY VULNERABLE
| ALERTA (A): PHP2Shell potencial via X-Powered-By -> PHP/5.6.40
|_ AVISO (C): Backend PHP detectado por cookie PHPSESSID (cabeceras de version pueden estar ocultas).
🚀 Try it in your lab
The tool is completely free, open-source, and auditable. You can clone it, spin up a vulnerable Docker container, and test it in seconds:
git clone https://github.com/matarturo/php2shell-nse-scanner.git
nmap -p 8080 -Pn -n --script http-vuln-php2shell --script-args http.useragent="Mozilla/5.0" 127.0.0.1
🔗 Repository & Documentation: https://github.com/matarturo/php2shell-nse-scanner
💬 Let's Discuss: WAF Evasion Tactics
To my fellow AppSec engineers and Red Teamers here on dev.to:
How often do you find servers that configure a reverse proxy (like NGINX or Cloudflare) to hide the Server header, but forget to filter the PHPSESSID or X-Powered-By headers?
I'd love to hear your experiences in the comments below. 👇
I want to thank the security community, including creators like Federico Miskinich from @hablemosdehackin in Instragram, who have independently tested this script and provided valuable feedback that helped refine its edge cases.
Top comments (0)