こんにちは!作者のActiveTKです。
皆さんはSQLインジェクションを知っていますか?
データベースを扱うとすぐに理解出来るのですが、データベースを使用した事が無いと理解が難しいです。
なので、今回はデータベースを使用した事のない方向けに書きます。「そんなの常識だよw」という方はブラウザバックして頂いて結構です。
1. データベースって何?
「フィールド」と「レコード」からなるエクセルのような物です。
ファイルと似ていますが、管理がしやすいという特徴があります。
以下、PHPでデータベースを利用するサンプルです。
<?php
$dbtest = new PDO("mysql:dbname={データベース名};host={ホスト名};charset=UTF8, "ユーザー名", "パスワード");
$res = $dbtest->query("select * from users");
$kekka = $res->fetchAll();
var_dump($kekka);
これを実行すると、usersというテーブルの中身を一覧表示出来ます。
queryの部分には「select * from users」と書かれています。
SQLサーバー上でこのSQL文が実行されたという事です。
select文では、データを表示する事が出来ます。
select {取得したい物} from {テーブル名} where {条件}
今回はusersというテーブルを使用するのでテーブル名にusersを当てはめます。
次に取得したい物と条件の部分です。
今回のように、テーブルの中身を一覧表示する事は滅多に無く、基本的に「ユーザー名(username)が○○のアカウントのパスワード(password)を取得」のような文を実行します。
それをSQL文にすると、
select password from users where username = '○○';
という文を実行する事になります。
2. SQLインジェクション
先ほどまでは、「決まった文」を実行していましたが、「入力されたユーザー名のパスワードを取得」という文を実行したい場合、
// $userに$_POST["username"]の値が入っている前提
select password from users where username = '{$user}';
という文を実行する事になります。
しかし、ユーザー名として「’ or 1 = 1 –」と入力されたらどうなるでしょうか。
(「–」で以降をコメントアウト)
select password from users where username = '' or 1 = 1 --'
という文が実行されます。
すると、1=1なので失敗値(false)は返ってきません。
「ユーザー名が合っていた」という処理が実行されてしまいます。
パスワードでも同じです。
しかし、問題はここからです。
不正にログインされても、被害は限定的ですが、
ほとんどのデータベースでは「;」を使用する事で複数のSQL文を実行する事が出来ます。
これを悪用して、「’ or 1 = 1; select * from users; –」と入力されたらどうなるでしょう。
select password from users where username = '' or 1 = 1;
select * from users;
--'
というSQL文が実行されて、テーブル内を一覧表示されてしまいます。(但し、結果をechoなどで表示していなければ見る事は不可です)
他にも、「’ or 1 = 1; update users set password = ‘1234’ where username = ‘ActiveTK’; –」と入力されたらどうなるでしょう。
update文を使用すると、値を上書きする事が出来ます。
select password from users where username = '' or 1 = 1;
update users set password = '1234' where username = 'ActiveTK';
-- '
という文がSQLサーバー上で実行されます。
このupdate文では、ActiveTKのパスワードを1234に設定しています。これにより、ユーザー名さえ分かっていればパスワードを書き換えて不正にログインする事が出来てしまいます。
3. 対策
とりあえず、ユーザーが入力した値を直接SQL文に加えないことが大切です。executeという関数を使うと、「?」を機械的に置き換える事が出来ます。
$dbh = new PDO("mysql:dbname={データベース名};host={ホスト名};charset=UTF8, "ユーザー名", "パスワード");
$stmt = $dbh->prepare('select password from users where username = ?');
$stmt->execute([$_POST['username']]); // 機械的に置き換える
$row = $stmt->fetch(PDO::FETCH_ASSOC);
もしくは、ホワイトリスト方式で英数字のみに制限するのも手です。
// 英数字判定関数(PHP)
function hankaku($text) {
// 英数字のみ
if (preg_match("/^[a-zA-Z0-9]+$/",$text)) return true;
// それ以外あり
else return false;
}
最後まで目を通して頂き、ありがとうございました。