From Dangerous PHP Functions to Webshell Hunting
A DFIR practitioner deploys four PHP webshells (popen-based, proc_open-based, p0wny-shell, and Weevely) on a test web server, then tests whether common webshell YARA rulesets (nsacyber, Florian Roth's Thor rules) can detect them via Velociraptor hunts. The rules only catch p0wny-shell due to its many code-execution keywords, missing the simpler popen/proc_open shells and Weevely entirely, exposing brittleness in signature-based webshell detection. The author proposes a semi-automatic approach combining Velociraptor/DetectRaptor with manual review, web-log frequency analysis, and UAC-generated bodyfile/mactime timelines to hunt webshells more reliably.
What it is
A DFIR walkthrough of PHP command-execution functions that survive a naive disable_functions hardening, and the detection artifacts they leave. It names the concrete bypass functions, shows working PoC webshells, and dissects the fallback logic in p0wny-shell and the on-disk obfuscation of weevely.
How it works
The default disable_functions shipped in the shown php.ini (/etc/php/8.0/apache2/php.ini) blocks only the pcntl_* family — it does not block popen, proc_open, passthru, or preg_replace. So a shell can still fork a command via popen($_REQUEST['code'],'r') or a proc_open reverse shell. p0wny-shell probes each method in order and uses the first available one, so blocking a subset is worthless:
flowchart TD
A[executeCommand] --> B{exec?}
B -->|no| C{shell_exec?}
C -->|no| D{system + ob_*?}
D -->|no| E{passthru + ob_*?}
E -->|no| F{popen/feof/fread/pclose?}
F -->|no| G{proc_open/stream_get_contents?}
B & C & D & E & F & G -->|yes| H[run command]To block execution you must disable the entire chain, not just exec/system/shell_exec.
What to do
Extend disable_functions to cover the fallback set, e.g. beyond the obvious three, add popen, proc_open, passthru, pcntl_exec, and preg_replace. The HackTricks reference the post cites is the source for the fuller (still "possibly not exhaustive") list.
For hunting, the post argues Yara rules are weak against webshells and points instead at: manual analysis, frequency analysis of web-server logs (rarely-hit endpoints), and collection with Velociraptor and UAC.
A concrete weevely signature: the generated agent.php is a phar loader whose readable header is hex/octal-escaped and truncated by __HALT_COMPILER, e.g.
<?php include "\160\x68\141\x72\72\57\57".basename(__FILE__)."\57\x78";__HALT_COMPILER(); ?>That \160\x68\141\x72\72\57\57 decodes to phar:// — grepping for escaped phar:// plus __HALT_COMPILER in .php files is a usable hunt.
Caveats
The post itself flags that its bypass list is "possibly not exhaustive," so a disable-list approach is inherently incomplete — treat it as defense-in-depth, not a boundary. It also notes preg_replace reaches code execution indirectly (via the deprecated /e modifier calling system()), so its presence in the list is conditional on legacy PHP behavior.
Organisations named here
- nsacyber
- Oxdf
Technologies named here
- disable_functions
- PHP
- phpinfo
- WordPress
- p0wny-shell
- weevely
- HackTricks
- YARA
- nsacyber webshell Yara rule set
- Velociraptor
- Velociraptor-Exchange Generic.Webshells hunt
- DetectRaptor
- Thor Webshell Yara rule
- webshell-scan
- UAC
- The Sleuth Kit
- stat
- mactime
Techniques named here
- Webshell upload/execution via popen
- Webshell execution via proc_open
- Obfuscated PHP backdoor generation
- Webshell detection via Yara rules
- Manual webshell analysis / hunting
- Frequency analysis of web server logs
- Timeline analysis via bodyfile/mactime
- Process ancestry monitoring for webshell detection
Also named here
- Florian Roth
- Matthew Green
Related in Blue Team & Detection
AI-Coded Malware | Analyzing Vibe-Coded AD Enumeration | Huntress
