PHP
SQL

SQLインジェクションについて

More than 1 year has passed since last update.

KIT Advent Calendar17日目です。みなさん初めまして。Qiitaすら初めましてです。@306_sanから枠をいただきました。

Webのセキュリティについて書こうと思ったのですが、範囲が広すぎるので今回はSQLインジェクションに絞って書きたいと思います。サンプルを書くところはphpとMySQLで書きます。

n番煎じかわかりませんし、詳しく書けていない部分も多いですが
ここで見たことは絶対に悪用しようとしないでください

環境

apache2.4+php7+MySQL5.7

SQLインジェクションとは

SQLインジェクション(SQL Injection)はデータベースに対する命令文の改ざんを行い意図しない操作を行うこと

データの盗難、改ざんだけでなくログイン認証の回避、ファイル操作等も行われる。

典型的な例

典型として認証回避のSQLインジェクションがあります。
データベースの処理はよくログイン認証に使われますが例えば以下ようなフォームからの入力値を受けとった変数を展開して生成したSQL文が成功すればログイン成功といったものです。

スクリーンショット 2016-12-16 11.18.32.png

sample.html
    <form action=login.php method="POST">
        <div>
            <label>ログインID</label>
            <input type="text" name="userid">
        </div>
        <div>
            <label>パスワード</label>
            <input type="password" name="password">
        </div>
        <input type="submit" value="ログイン">
    </form>
login.phpの一部
<?php

/*  
 *  $connはデータベース設定のための変数
 *  mysqli_num_rowsは結果の行数を取得する
 */
$conn=mysqli_connect('','','');
mysqli_select_db($conn,'sqli_test');

$userid = $_POST['userid']
$password = $_POST['password']

$sql="SELECT user_id,password FROM users WHERE user_id='$userid' AND password='$password';";
$result=mysqli_query($conn,$sql)
if(mysqli_num_rows($result)!=0){
    //1行以上ある
    echo "ログイン成功";
}else{
    echo "ログイン失敗";
  } 
mysqli_close($conn);
test.sql
CREATE TABLE `users` (
  `id` int(11) NOT NULL,
  `user_id` varchar(50) NOT NULL,
  `name` varchar(50) NOT NULL,
  `password` varchar(50) NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
)

このユーザーIDに 1' or '1' = '1';--(最後にスペースを空けてください) という文字を送信するとログインに成功してしまいます。

なぜログインに成功してしまったのか

1' or '1' = '1';--が入力された場合login.phpでは以下のようなsql文を発行します。
SELECT user_id,password FROM users WHERE user_id='1' or '1' = '1'-- AND password='$password';
まずSELECT user_id,password FROM users WHERE user_id='1' or '1' = '1'の部分ではuser_idが'1' か '1' が '1'なら真という条件文なので結果は必ず真になっています。この例の場合ではテーブル内のすべてのレコードが結果として得られます。
またMySQLでは--後はコメントアウトとして使用されるので後半のsql文は無視されます。ログイン認証のソースコードはレコードが帰ってくるかどうかで認証をしているのでこの例ではログイン認証ができてしまいます。

影響

  • 検索表示部分でのデータの盗難
  • DELETE文などの他のsql文の実行の可能性(データの改ざん)
  • ブラインドsqlインジェクション(後述)
  • etc...

ブラインドSQLインジェクション

SQLインジェクションの脆弱性があると表示部分がなくてもデータを推測していくことができます。推測の方法は各DBMSに依存する処理等を使いテーブル名やカラム名も取得されることもあります。これによって攻撃者は任意のデータを盗んでいくことができます。
上記のデータベースにSELECT if(substr((SELECT name FROM users LIMIT 1 OFFSET 0),1,1) = 'a',sleep(3),null);を入力した場合どうなるでしょうか

これはnameカラムの1文字目がaならば3秒待つというsql文です。このようなsql文を使用することで1文字を確定することができます。

私の環境では単に;で区切ってsql文を送信しても受け付けませんでしたがSELECT name,user_id FROM users WHERE user_id = '' UNION SELECT if(substr((SELECT name FROM users LIMIT 1 OFFSET 0),1,1) = 'a',sleep(3),null),'';のようにUNION句を用いることで実行することもできます。

対策

ここは言語毎で実装コードが変わってくると思いますのでコードは載せませんが個人的に気をつけたほうが良い事を列挙してみました。

  • 文字エンコーディングを指定
  • 静的プレースホルダの利用
  • sql文中の文字列展開はやめる
  • 型の指定
  • 詳細なエラーメッセージの非表示(攻撃に使われることがあるため)
  • 入力値のチェック
  • 正しいデータベースの権限を設定

参考

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践 2011/3/1 著者 徳丸 浩

パーフェクトPHP (PERFECT SERIES 3) 2010/11/12 著者 小川 雄大

SQL Injection Cheat Sheet

徳丸浩のtumblr ブラインドSQLインジェクションとは何ですか?」への回答

終わりに

初めて投稿させていただきましたが、自身の確認にもなったので良かったと思います。
私自身のセキュリティについて勉強中の身ですので抜けや間違いがあることも十分に考えられます。なのでここだけの情報を鵜呑みにしないようにお願いします。また、間違いをご指摘いただけるとありがたいです。

ありがとうございました。