PHP Security Vulnerabilities: From File Uploads to Web Shells
PHP still runs a huge portion of the web, from WordPress and Laravel apps to long-lived custom platforms. PHP has matured significantly, but certain patterns remain a direct path from a web request to a shell on the server. Our research team has found and reported critical PHP vulnerabilities in open-source projects, including CVE-2026-33867, an RCE in AVideo.
Unsafe file uploads leading to web shells
The classic PHP compromise: an attacker uploads shell.php and the web server executes it.
Common causes:
- Uploads stored in a web-accessible directory where PHP executes.
- Extension checks bypassed with
shell.php5,shell.phtml,shell.php.jpgwith misconfigured handlers, or case tricks. - Trusting the
Content-Typeheader. - Image validation that only checks magic bytes, allowing polyglot files that are valid images and valid PHP.
Prevent: store uploads outside the web root, generate random names, allowlist extensions, verify content, and disable PHP execution in upload directories at the server level.
Local and remote file inclusion
// Vulnerable
include($_GET['page'] . '.php');
Attackers use path traversal to include other files, or PHP stream wrappers such as php://filter to read source code. Combined with log poisoning or uploaded files, inclusion can become code execution. If allow_url_include is enabled, remote files can be included directly.
Prevent: never include files based on user input. Map input to an allowlist of templates. Keep allow_url_include off.
Insecure deserialisation
// Vulnerable
$prefs = unserialize($_COOKIE['prefs']);
PHP's unserialize() can instantiate objects and trigger magic methods like __wakeup and __destruct, enabling gadget chains in frameworks and libraries. Phar archives can also trigger deserialisation through file functions on older PHP versions.
Prevent: use json_decode() for untrusted data. If you must use unserialize(), pass ['allowed_classes' => false].
Dangerous functions
Review any use of these with user-influenced input:
| Function | Risk |
|---|---|
eval(), assert() with strings | Code execution |
system(), exec(), shell_exec(), passthru(), popen(), proc_open() | Command injection |
preg_replace() with the /e modifier on old PHP | Code execution |
include, require | File inclusion |
unserialize() | Object injection |
extract() on request data | Variable overwrite |
Consider disabling unneeded functions with disable_functions in php.ini.
Type juggling
Loose comparison with == can produce surprising results, such as some strings comparing equal to 0 in older PHP versions, or "magic hash" strings comparing equal. This has led to authentication bypasses. Use === and hash_equals() for secrets.
SQL injection
Still common in legacy PHP. Use PDO prepared statements with bound parameters.
Configuration hardening
- Run supported PHP versions; end-of-life versions receive no security fixes.
display_errors = Offin production.expose_php = Off.- Remove
phpinfo()files. - Use
open_basedirto limit file access.
Free PHP2Shell scanner
We built a free scanner that checks PHP applications for upload and execution paths that could lead to a web shell. Try PHP2Shell, or start free for full testing.
Frequently asked questions
Is PHP insecure?
Modern PHP with a good framework and secure practices can be very secure. Risk comes mainly from legacy code, outdated versions and dangerous patterns.
Do frameworks like Laravel prevent these issues?
They provide safe defaults for queries, templating and CSRF, but developers can still introduce unsafe uploads, raw queries and deserialisation.
How do I find web shells on my server?
Look for recently modified PHP files in upload and writable directories, unusual file names, and code using eval, base64_decode and system together.