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

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

- __『良いSQL』と『悪いSQL』__
- __『SQLインジェクション』とは…？__

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

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

## 環境

- apache2.4
- php7
- MySQL5.7

## 『良いSQL』&ensp;と&ensp;『悪いSQL』

### 良いSQL

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

### 悪いSQL

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

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

![rpa_3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255884/279aea8a-c64d-3d8e-3c44-bf09e950168e.jpeg)

ズバリ・・・

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

### 『SQLインジェクション』&ensp;とは…？

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

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

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

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

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

する攻撃です。

### ---&ensp;『SQLインジェクション』&ensp;の例題&ensp;---

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

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

例えば・・・

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

<img width="484" alt="sql_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255884/8392b43f-b5cd-0e22-691f-576aa17e9c03.png">

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

この`$password`に…

`'or'x'='x`

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

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

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

~~~sql
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インジェクション』の対策&ensp;【PHPの場合】

### 対策：&ensp;『プリペアドステートメント』を使う

『[プリペアドステートメント](https://www.php.net/manual/ja/pdo.prepared-statements.php)』を使い、実行したいSQL文をコンパイルすることで・・・

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

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

>[》【PDO】PHPでデータベースに接続する方法【INSERT編】](https://qiita.com/yaaabu51/items/11d09c0219907e63a484)

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

>仮に、パスワードに…

>`OR  'x' = 'x'`

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

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

##まとめ：&ensp;基本コーディング

~~~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) {

    // エラー処理

}
~~~