PHP Security Vulnerabilities and Bypass Techniques in CTF Challenges
extract() Variable覆盖:
Related Functions:
extract(): Imports variables from an array into the current symbol table. Array keys become variable names and array values become variable values. When duplicate keys exist, the later value overwrites the previous one by default.trim(): Strips whitespace or other predefined characters from both ends of a string.file_get_contents(): Reads entire file contents into a string. This function can be combined with thephp://inputprotocol to read$_POST[]values.
Vulnerability Explanation:
When extract() is called with only one parameter, the default second parameter is EXTR_OVERWRITE, which overwrites existing variables when conflicts occur.
The challenge requires setting $shiyan variable and satisfying $shiyan==$contents to obtain the flag. Since we don't know the original $flag value, we can exploit the variable覆盖 to override $flag with a value that matches our controlled $shiyan.
Exploit payloads:
// Using file_get_contents() return value with PHP loose comparison (null == "string" ==》 true) http://challenge.example.com:9009/1.php?shiyan= http://challenge.example.com:9009/1.php?shiyan=&flag= http://challenge.example.com:9009/1.php?shiyan=&content=//file_get_contents()+php://input+post vulnerability?shiyan=xiao&flag=php://input(set POST parameter to xiao)
</div>Flag:
<div>```
flag{bugku-dmsj-p2sm3N}
strcmp() function syntax:
Parameters: str1 - First string str2 - Second string
Return Values: Returns < 0 if str1 is less than str2; > 0 if str1 is greater than str2; 0 if they are equal.
</div>strcmp() only compares strings. When passed an array, strcmp returns false. Since the comparison uses == (which only checks value equality after type juggling), we need both sides to be equal.
Exploit using array bypass:
<div>```
?a[]or?a[]=
eregi() function syntax:
ereg() function: int ereg ( string $pattern , string $string [, array &$regs ] )
Performs a case-sensitive search in string for substrings matching the regex pattern. If matches are found and a third parameter is provided, results are stored in the regs array. $regs[0] contains the full match, $regs[1] the first captured group, etc. Returns the length of matched string, FALSE on error, and 1 if no match or zero-length match.
</div>**eregi() has two vulnerabilities:**
1. %00 null byte truncation - treats %00 as string terminator
2. When the input is an array, it returns NULL instead of FALSE
`urlencode()` converts characters to hexadecimal with % prefix.
`urldecode()` decodes hexadecimal strings back to characters.
**Important Note:**
Most browsers URL-encode submitted values, and `$_GET`/`$_REQUEST` automatically perform urldecode on browser-transmitted values.
**Challenge Requirements:**
1. id must NOT contain the string "hackerDJ"
2. id after urldecode() must equal "hackerDJ"
Exploit payload:
<div>```
// Double URL encode part of "hackerDJ"
'h' hex code is 0x68
%68 becomes %2568
http://challenge.example.com:9009/10.php?id=%2568ackerDJ
// eregi() prevents "hackerDJ" in id. Direct URL encoding doesn't work because URL
// gets decoded once during $_GET processing, triggering "not allowed!"
// Double encode the hex values:
hackerDJ => %2568%2561%2563%256B%2565%2572%2544%254A
md5() vulnerabilities:
- md5() cannot process arrays - it treats arrays as 0. If two arrays with different values are provided, both evaluate to 0.
- PHP's hash comparison uses type juggling. Any hash string starting with "0e" is interpreted as scientific notation (0 * 10^n = 0). Different passwords with hashes starting with "0e" are considered equal.
Array bypass payload:
Using loose type comparison, MD5 of "QNKCDZO" produces 0e830400451993494058024219903391, interpreted as 0. Find another string with MD5 starting with "0e".
Flag:
ereg() function syntax:
- %00 null byte truncation
- ereg() only handles strings - arrays return NULL
strpos() function syntax:
is_numeric() function syntax:
Note: is_numeric() treats null byte %00 as non-numeric regardless of position. Space %20 is only treated as non-numeric when placed after the number. This is due to PHP's loose type conversion.
</div>Exploit payloads:
<div>```
?password=1337%00
?password=1337%20
?password=1337a
?password[]=1336 (or 1337...)
sha1() function syntax:
Parameters: str - Input string raw_output - If TRUE, returns 20-character raw binary format; otherwise 40-character hex string.
Returns sha1 hash as hex string.
</div>Exploit using array handling weakness:
<div>```
?name[]=1&password[]=2
MD5() function syntax:
Parameters: str - Original string raw_output - If TRUE, returns 16-byte raw binary format
Returns 32-character hexadecimal hash.
</div><div>```
// These special strings produce equal MD5 hashes:
QNKCDZO
0e830400451993494058024219903391
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
Exploit payload:
ord() function syntax:
Given $number=3735929054, convert to hex "deadc0de", prepend 0x for hexadecimal notation. Using == allows comparison between different number bases.
Exploit payload:
ereg() function syntax:
Requirement: Input must be digits 1-9 and contain "#biubiubiu". Note: # must be URL-encoded.
Exploit using array bypass:
preg_match() function usage:
Searches subject for pattern matches.
Parameters: pattern - Search pattern (string) subject - Input string matches - If provided, filled with search results: $matches[0] = full pattern match $matches[1] = first captured group, etc. flags - Can be PREG_OFFSET_CAPTURE (adds string offset) offset - Start searching from specified byte offset
Returns number of matches (0 or 1), FALSE on error.
</div>Exploit using array handling weakness:
<div>```
POST: password=1
POST: password[]=1