【SQL入門】『SQLインジェクション』について


この記事では、《SQLインジェクション》について、
業務を通して学習した内容を、備忘録としてまとめています。

  • 『良いSQL』と『悪いSQL』
  • 『SQLインジェクション』とは…?

こういった内容についてまとめています。

※本記事は、自分で学習したことのまとめ用として書いています。
尚、解説で誤った点があれば、スローして頂ければ喜んでキャッチしますのでお願い致します。

環境

  • apache2.4
  • php7
  • MySQL5.7

『良いSQL』 と 『悪いSQL』

良いSQL

  • メンテナンスしやすい
    自分が書いたSQL文を、他の人がメンテナンスすることもある
  • パフォーマンスが良い
    データ量、実行頻度、同時アクセスが増加しても、導入当初と同等のレスポンスが維持できる

悪いSQL

  • 必要以上のデータをメモリに読み込む
    パフォーマンスの劣化問題
    セキュリティ上の問題
  • 索引(index)を使用せず、全件検索する
    パフォーマンスの劣化問題
    (I/Oの増加、メモリの非効率的利用)

セキュリティの観点から見た 『悪いSQL』

ズバリ・・・

『SQLインジェクション』の脆弱性があるSQL文です。

『SQLインジェクション』 とは…?

『SQLインジェクション』とは・・・

データベースと連動したWebサイトで…

  • データベースへの問合せ
  • データベースへの操作

などを行うプログラムに、パラメータとしてSQL文の断片を与えることにより…

  • データベースの改ざん
  • データベースから不正に情報を入手

する攻撃です。

--- 『SQLインジェクション』 の例題 ---

例題として、認証を回避する『SQLインジェクション』があります。

SQL文は、よくログイン認証に使われますが、

例えば・・・

以下のようなフォームの入力値を代入した変数を利用して生成したSQL文trueであれば、ログインに成功するというモノがあります。

SELECT *
FROM
    users
WHERE
    user_name = '$user_name'
    AND password = '$password';

この$passwordに…

'or'x'='x

という不正な値を入力します。

SELECT *
FROM
    users
WHERE
    user_name = '$user_name'
    AND password = ''or'x'='x';

これだと分かりづらいので、改行すると…

SELECT *
FROM
    users
WHERE
    user_name = '$user_name'
    AND password = ''
    OR  'x' = 'x';

上記のように、WHERE句にORの条件が追加されます。

すると・・・

OR 'x' = 'x'trueとなるので、ログインに成功してしまいます。

上記は、SQL文の構造が変わってしまったため…

user_name = '$user_name'
AND password = ''

または・・・

OR 'x' = 'x'

trueになればログインできるSQL文に、変わってしまったということです。

『SQLインジェクション』の対策 【PHPの場合】

対策: 『プリペアドステートメント』を使う

プリペアドステートメント』を使い、実行したいSQL文をコンパイルすることで・・・

SQL文を固定し、構造が変わらないようにします。

『プリペアドステートメントの使い方』について詳しく知りたい方は、
下記を参照してみて下さい。

》【PDO】PHPでデータベースに接続する方法【INSERT編】

  • プリペアドステートメント
    SQL文で値がいつでも変更できるように、変更する箇所だけ変数のようにしたSQL文を作る仕組みのことです。 通常『プリペアドステートメント』は『プレースホルダ』を使うために作られます。
  • プレースホルダ
    ユーザが入力した内容を後から挿入するために、予め確保した場所のこと。

仮に、パスワードに…

OR 'x' = 'x'

と入力しても、SQL文がコンパイルされているので、OR 'x' = 'x'というパスワードを探しにいく。

当然、そのようなパスワードはないため、ログインできない。

まとめ: 基本コーディング

sample.php
<?php

try {

    // リクエストから得たスーパーグローバル変数をチェックするなどの処理
    $user_name = "ここにユーザー名が入ります";
    $password = "ここにパスワードが入ります";

    // データベース接続設定
    $pdo = new PDO(
        'mysql:host=サーバー名;dbname=DB名;charset=文字エンコード',
        'ユーザー名',
        'パスワード',
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
    );

        // パラメータ
        $spl = (
            'SELECT
                USER_NAME, PASSWORD
            FROM
                テーブル名
            WHERE
                AND USER_NAME = :USER_NAME
                AND PASSWORD = :PASSWORD'
        );

    // プリペアドステートメントを用意
    $stmt = $pdo->prepare($sql);

    // 値をバインドする
    $stmt->bindValue(':USER_NAME', $user_name, PDO::PARAMS_STR);
    $stmt->bindValue(':PASSWORD', $password, PDO::PARAMS_STR);

    // 結果を取得する
    $row = $prepare->fetch();

} catch (PDOException $e) {

    // エラー処理

}