UUUM攻殻機動隊(エンジニアブログ)

UUUMのエンジニアによる技術ブログです

危険な危険なSQLインジェクション

エンジニアのspin13です。

今回はSQLインジェクションについてです。

SQLインジェクションとは

入力パラメータの検証不備等の原因で、予期せぬSQL文が発行されてしまう 予想していないパラメータが注入(inject)される

SQLインジェクションで何が起こるのか

  • データの窃盗
  • データの改変 SQL文で書けるようなことはなんでも起こり得る

<?php
if(!empty($_POST)){
    $db =  new PDO('mysql:dbname=test_db;host=localhost', 'user', 'password');
    $user = $_POST["user"];
    $pass = $_POST["pass"];
    echo "発行クエリ<br>";
    $query = "SELECT * FROM users WHERE name='{$user}' AND pass='{$pass}'";
    echo $query;
    echo "<br>";
    $result = $db -> query($query) -> fetchAll();
    echo "<br>";
    if($result){
        echo "成功";
    }else{
        echo "失敗";
    }
}
?>

上記はPOSTでユーザとパスワードを受け取ってDBに該当ユーザのレコードが存在するかどうかを確認するSQL文を発行します。

ユーザに user1
パスワードに password
と入力してsubmitしてみます。 出来上がるSQL文は、
SELECT * FROM users WHERE name='user1' AND pass='password' です。

[test_db]> select * from users;
+------+-------+-----------+
| id   | name  | pass      |
+------+-------+-----------+
|    1 | user1 | password  |
|    2 | user2 | changed   |
|    3 | user3 | password3 |
+------+-------+-----------+

このDBに対してSQLを実行します。

[test_db]> SELECT * FROM users WHERE name='user1' AND pass='password';
+------+-------+----------+
| id   | name  | pass     |
+------+-------+----------+
|    1 | user1 | password |
+------+-------+----------+
1 row in set (0.01 sec)

データが存在するのでログインに成功します。

[test_db]> SELECT * FROM users WHERE name='hoge' AND pass='fuga';
Empty set (0.00 sec)

存在しないユーザとパスワードの組み合わせではもちろんレコードが存在しないので失敗します。

次にSQLインジェクションで強制的にログインしてみます。

ユーザに user1 ' AND 1=1 -- '
パスワードに hoge
と入力してsubmitしてみます。 出来上がるSQL文は
SELECT * FROM users WHERE name='user1 ' AND 1=1 -- '' AND pass='hoge'

さてDBで叩いてみましょう。

[test_db]> SELECT * FROM users WHERE name='user1 ' AND 1=1 -- '' AND pass='hoge';
    -> ;
+------+-------+----------+
| id   | name  | pass     |
+------+-------+----------+
|    1 | user1 | password |
+------+-------+----------+

user1のレコードが返ってきてしまうんです!

なぜこのようなことが起きてしまうのでしょう。

解説

受け取ったパラメータをPHPのプログラム側で $query = "SELECT * FROM users WHERE name='{$user}' AND pass='{$pass}'";

として展開しています。

userに user1 ' AND 1=1 -- ' と入れると、
name = ' user1 ' AND 1=1 -- '
SQLでは -- 以降はコメントとして扱われるので、 AND pass='{$pass}'" が無視されてしまうのでパスワードに何が入っていても関係なくなってしまうんですね。

対策

プレースホルダを使いましょう

$state = $db-> prepare("SELECT * FROM users WHERE name=? AND pass=?");
$state-> execute(array($user, $pass));

このようにすればSQLの本体部分とパラメータ部で切り離されて、プレースホルダで与える部分は値としてしか解釈されないのでSQLインジェクションで不正なパラメータを注入するのが困難になります。

不正な値を受け取って実行できる状態でなくするのが大切です。 最近ではライブラリ側が自動でエスケープ処理等をしてくれるので意識することは少なくなっていますが、原理を知っておくときっと何か役に立ちます。

おまけ

user: aaaaaa' ;UPDATE users SET pass=‘’ – ‘ pass: aaaa

SELECT * FROM users WHERE name='aaaaaa' ;UPDATE users SET pass='' -- '' AND pass='aaa'
[test_db]> select * from users;
+------+-------+------+
| id   | name  | pass |
+------+-------+------+
|    1 | user1 |      |
|    2 | user2 |      |
|    3 | user3 |      |
+------+-------+------+
3 rows in set (0.00 sec)

▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂