LoginSignup
235
244

パスワードがハッシュ値で保存されているサイトのSQLインジェクションによる認証回避の練習問題

Last updated at Posted at 2023-09-17

SQLインジェクションによる認証回避

SQLインジェクションによる影響として、情報が漏洩するとか、データが勝手に更新されてしまうなどとともに、認証回避の例がよく紹介されます(私の本でも取り上げています)。

典型的な例は下記のとおりです。

// $id と $password は外部からの入力
$sql = "SELECT * FROM users WHERE id='$id' AND password='$password'";

このケースで、\$idに適当な値、$passwordとして「'OR'a'='a」を入力すると、生成されるSQL文は下記となります。

SELECT * FROM users WHERE id='XXX' AND password=''OR'a'='a'

ANDとORでは、ANDの方が先に計算されます。id='XXX' AND password='' は偽ですが、'a'='a'は常に真なので、WHERE句全体は常に真になります。これでパスワードを知らなくてもログインできるというわけです。

パスワードがハッシュ値で保存されているとどうなる?

しかし、今どきはパスワードはハッシュ値で保存することになっています。なのでログイン処理は以下のようになっているはずです(PHPの場合)。

$sql = "SELECT * FROM users WHERE userid = '$userid'";
$stmt = $pdo->query($sql);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
    echo "ログイン成功:" . htmlspecialchars($user['userid']);
} else {
    // ログイン失敗

この処理ですと、SQLクエリの結果、ユーザーがヒットして、かつユーザのパスワードハッシュ値が、入力値から計算したハッシュ値と一致していなければなりません。PHPのpassword_verify関数(マニュアル)がこのチェックをしています。なので、「'OR'a'='a」などで認証回避できません。

練習問題を作ってみた

この条件でSQLインジェクションによる認証回避の練習問題を作ってみました。以下はログイン処理の中身です。

login.php
<?php
$userid =  $_POST['userid'];
$password = $_POST['password'];
$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
try {
    $pdo = new PDO("sqlite:../db.sqlite3", null, null, $options);
    $sql = "SELECT * FROM users WHERE userid = '$userid'";
    $stmt = $pdo->query($sql);
    $user = $stmt->fetch();
    if ($user && password_verify($password, $user['password'])) {
        echo "ログイン成功:" . htmlspecialchars($user['userid']);
    } else {
        echo "ログイン失敗";
    }
} catch (Exception $e) {
    echo "エラー:" . htmlspecialchars($e->getMessage());
}

データベースの中身

init.sql
CREATE TABLE users (id int, userid varchar(64), password varchar(128),  email varchar(128), PRIMARY KEY (id));
INSERT INTO users VALUES (1, 'admin', '$2y$10$7sTu7b3VW6cMreA6QrdYkOQpPP9hSiXLDpuAzElGHios2N/y5Ct2C', 'admin@example.jp');
INSERT INTO users VALUES (2, 'alice', '$2y$10$fxOHmQr3NB4xfvkxOnTTqudbLGUQj4LCgScg9vmsNnpTpRuG4lWMW', 'alice@example.jp');

ログインフォームを含めた完全なソースコードは、GitHubのリポジトリを用意しました。以下のコマンドでインストールできます。Windows、Mac、Linuxに対応しているはずです。

$ git clone https://github.com/ockeghem/SQLi-to-bypass-auth.git
$ cd SQLi-to-bypass-auth
$ docker compose up -d

http://localhost:7890/ にアクセスすると以下のログインフォームが表示されます。
image.png
攻撃の結果、以下の状態(ログイン成功:admin)となれば攻撃成功です。
image.png

想定正解については、数日後に公開したいと思います。
2023/9/20追記。解答編を書きました。

発展問題(2023/9/18追記)

adminでログインできたという方向けに発展問題を。

  • ソースコードの情報は使ってよい想定ですが、ソースコードの内容に依存しない解法も考えてください
  • SQLインジェクションを使ってSQLiteのバージョンを表示してください
  • SQLインジェクションを使ってテーブル情報(テーブル名や列名など)を表示してください

※ これから解かれる人もいるので、X(Twitter)やブックマーク、この記事のコメント欄などに解答やヒントは書かないでください。ご自身のブログやQiita等に書くのはかまいません。

235
244
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
235
244