セキュリティインシデントが勃発する昨今、セキュリティの重要性というのが一般的にも重視されるようになってきたと思います。
2014年にはSQLインジェクションを怠ってクレジットカード情報を漏洩させたシステム開発企業に損害賠償金を支払うよう命じるという判例も出ています。
(参考: https://blog.tokumaru.org/2015/01/sql.html)
つまりセキュリティをしっかりしていないシステムを作ること自体が、起訴されるリスクを孕んでいるということです。
なのでセキュリティ対策はしっかり行っていきましょう、ということでまずは上記の原因にもなったSQLインジェクションを確認していきます。
1. SQLインジェクションとは
簡単に言うと「ユーザーからの入力された値をSQL命令文を入力することでデータベースの不正利用をしようとする行為」です。
SQLインジェクションをされることによって起きる脅威としては
- データベースにある非公開にしたいデータが抜き取られる
- データベースにあるデータの改ざん、削除
- 認証を回避することにより、不正にログインされる
- ストアドプロシージャなどを使用した、OSコマンドの実行
データベースを使用しているシステムには起きうるものなので注意が必要です
2. SQLインジェクションへの対策
SQLの組み立てにプレースホルダを使用する
SELECT * FROM users WHERE email = $email and pass = $pass;
このようにusersテーブルからemailとpasswordを使ってユーザーデータを取得するSELECT文があるとします。
ユーザーからの入力をそのままSQL文として使用しています。
この時、emailに 1 or 1 = 1; select * from users where 1 = 1
セミコロンはSQL文の区切りとして処理されるので、
SELECT * FROM users WHERE email = 1 or 1 = 1;
SELECT * FROM users WHERE 1 = 1 AND pass = password;
この2つのSQLが処理され、1つ目のSQL文によってusersのすべてのデータが参照できてしまいます。
PDOを使用したプレースホルダはこのように書くことができます。
$pdo = new PDO('mysql:dbname=test;host=localhost', $userName, $password);
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email and pass = :pass;');
$stmt->bindValue(':email', $email); -- プレースホルダに値を入れる
$stmt->bindValue(':pass', $pass); -- プレースホルダに値を入れる
$stmt->execute(); -- sqlを実行
このようなプレースホルダは、予めSQL構文を決めておくことで、想定外のSQL文が入ることを防いでくれます。
SQLを文字列連結で行う場合は、エスケープ処理等を行うデータベースエンジンのAPIを用いてSQL文のリテラルを正しく構成する
プレースホルダが使用できない場合は、渡す値をすべてエスケープ処理を行う必要があります。
データベースエンジンによっては、専用のエスケープ処理を行うAPIを提供しているものがあります(たとえば、Perl ならDBIモジュールのquote()など)ので、それを利用することをお勧めします。
アプリケーションに渡されるパラメータを直接SQLに指定しない
プレースホルダのときにも書いていますが、リクエスト等で受け取ったユーザーから入力される値は直接SQL文に指定してはしていけません。
SELECT * FROM users WHERE email = $email and pass = $pass;