【セキュリティ】SQLインジェクション脆弱性作ってみた

セキュリティ

今回は「SQLインジェクション脆弱性を作ってみた」ということで、SQLインジェクションの実例についてデモを踏まえて説明します。

SQLインジェクションはSQLというデータベースを操作する言語を利用して、サービスで使用しているデータベースを不正に操作する攻撃のことです。

もう少し詳しく知りたいという方は以下のリンクをご覧ください。

SQLインジェクションについて文字ではわかったけど、実際にどういう風に動作するのか見てみたいという方に向けて、今回動画を撮影しました。

またどんなプログラムなのか知りたい方向けに実際のコードについても掲載、解説を載せております。言語はPHPです。

SQLインジェクションの動作、コードがどんなものか気になる方は是非この先をご覧ください。

SQLインジェクションのデモ

まずはSQLインジェクションの動作のデモを見ていただきたいと思います。

内容

今回のデモの内容は、ユーザー名とパスワード名を入力するログイン画面でパスワードだけをSQLインジェクションで不正に突破するというものです。

正常な動作

ユーザー名とパスワードそれぞれに「tanaka」と入力すると正常にログインが完了します。これが正常な動作です。

不正な動作

ユーザー名は「tanaka」、パスワードに「'or '1'='1」を入力するとパスワードは正しくないにもかかわらずログインできてしまいます。不正ログインです。

実装と対策: PHPわかる人向け

実装

まずIDとパスワードを入力する画面のコードです。ここには特に脆弱性はありません。

<html> <head><title>脆弱なログイン画面</title></head>
<body>
    <form action="vulnerable_login.php" method="post">
        ユーザー名: <input type="text" name="id"><br>
        パスワード: <input type="text" name="pwd"><br>
        <input type="submit" value="ログイン">
    </form>
</body>
</html>

次にデータベースのユーザーのデータを確認して、実際にログインできているか表示する画面です。ここに脆弱性があります。

<?php header('Content-Type: text/html; charset=UTF-8'); $id = $_POST['id']; $pwd = $_POST['pwd'];  $db = new PDO('mysql:host=mysql;dbname=default', 'root', 'root'); // 脆弱性: ユーザーが入力した値をそのままSQLで利用している $sql = "SELECT * FROM users WHERE id ='$id' AND PWD = '$pwd'"; echo 'SQL: '; echo $sql; echo '<br>';
$users = $db->query($sql);
?>
<html>
<body>
<?php
    // SELECTした行が存在する場合はログイン成功とする
    if ($users->rowCount() > 0) {
        $_SESSION['id'] = $id;
        echo 'ログイン完了しました<br>';
    } else {
        echo 'ログイン失敗しました<br>';
    }
?>
<a href="login.php">戻る</a>
</body>
</html>

$sql = "SELECT * FROM users WHERE id ='$id' AND PWD = '$pwd'";というSQL文の組み立てるところで、ユーザーが入力している値をそのまま使用する実装になっていることがわかります。

この実装に対してデモで行った不正な入力を行うと以下のSQL文が発行されます。
SELECT * FROM users WHERE id ='tanaka' AND pwd = ''or '1'='1'

これは「idがtanakaでパスワードが空文字もしくは常に」という条件でログインが成立するという意味になります。

つまり不正なパスワード入力「'or '1'='1」を使うと正しいパスワードを使用しなくても、常にログイン可能になるというわけです。

対策

いくつかやり方はありますが、一番手取り早い方法はプレースホルダーという機能を使う方法です。

問題のコードの実装をプレースホルダーを使ったものに書き換えます。

...途中から表示 $db = new PDO('mysql:host=mysql;dbname=default', 'root', 'root'); $stmt = $db->prepare('SELECT * FROM users WHERE id = ? AND pwd = ?');
$stmt->bindValue(1, $id, PDO::PARAM_STR);
$stmt->bindValue(2, $pwd, PDO::PARAM_STR);
$stmt->execute();

?>
<html>
<body>
<?php
    // SELECTした行が存在する場合はログイン成功とする
    if (count($stmt->fetchAll()) > 0) {
        $_SESSION['id'] = $id;
        echo 'ログイン完了しました<br>';
    } else {
        echo 'ログイン失敗しました<br>';
    }
?>
<a href="login.php">戻る</a>
</body>
</html>

プレースホルダーという機能を使うと、ユーザーの入力した値をSQL文として認識しないようエスケープ処理を行ってくれます。

エスケープをしておけばSQLインジェクションは発生しないので、これで対策完了です。

さいごに

今どきのフレームワークを使っていれば、特に意識せずともSQLインジェクションは基本的に防ぐことができるはずですが、奇跡的に何も知らない現役のエンジニアが現場でこの手の脆弱性を作っていたりします。

僕も実際に見て驚愕しました。教科書に載ってる基本レベルのことなのにと。

自分もこの手の脆弱性を作らないようにしていきたいですし、この記事をここまで見てくださったみなさんにも作らないようにしていただけると嬉しいです。

今回は以上です。
最後まで読んでいただきまして、ありがとうございました。

コメント

タイトルとURLをコピーしました