60
54

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SQLインジェクションとその対策(PHP + PDO)

Last updated at Posted at 2019-12-20

この記事は、North Detail Advent Calendar 2019 の21日目の記事です。

はじめに

先日、社内で「安全なウェブサイトの作り方」を使った読書会が行われました :green_book:

SQLインジェクションについて議題に上がったので、
勉強がてら実際に試してみよう!というのが今回の趣旨になります。

SQLインジェクション?

SQLインジェクション(英: SQL Injection)とは、
アプリケーションのセキュリティ上の不備を意図的に利用し、
アプリケーションが想定しないSQL文を実行させることにより、
データベースシステムを不正に操作する攻撃方法のこと。

https://ja.wikipedia.org/wiki/SQLインジェクション

「Injection」という単語が「注入する」という意味のようです(知らなかった)。

アプリケーションが実行するSQL文に変なものを注入して
意図しない動作をさせる攻撃なので「SQL Injection」ってことですね :smiley:

実際に起こしてみる

データを作る

まずはMySQLにテスト用データを作成します。

CREATE TABLE users (
  id int NOT NULL AUTO_INCREMENT,
  name varchar(10),
  PRIMARY KEY (id)
);

INSERT INTO
  users(name)
VALUES
  ('フグ田サザエ'),
  ('フグ田マスオ'),
  ('磯野波平'),
  ('磯野フネ'),
  ('磯野カツオ'),
  ('磯野ワカメ'),
  ('フグ田タラオ'),
  ('タマ')
;

:arrow_down:
スクリーンショット 2019-12-19 9.53.14.png

:tangerine:
:cat:
:tangerine:

テスト用画面を作る

次は、IDを入力してユーザーを検索できる画面を作ります。

PDOクラスを使ってMySQLにアクセスしています。

injection.php
<?php
  try {
    $records = [];
    $id = $_POST['id'];

    $pdo = new PDO(
      'DSN',
      'USERNAME',
      'PASSWORD'
    );
    $prepare = $pdo->prepare('SELECT * FROM users WHERE id = '. $id. ';');
    $prepare->execute();
    $records = $prepare->fetchAll(PDO::FETCH_ASSOC);
  } catch(PDOException $e) {
    echo $e->getMessage();
  }
?>

スクリーンショット 2019-12-19 10.19.41.png
:arrow_down:
スクリーンショット 2019-12-19 10.20.07.png

うーんシンプル。

ここで…文字列結合を使ってクエリ組み立てていますね :imp:

$prepare = $pdo->prepare('SELECT * FROM users WHERE id = '. $id. ';');
$prepare->execute();

攻撃してみる

先ほどの例では「3」と検索して「磯野波平」のデータを取得しました。

じゃあ次は「0 OR TRUE; --」で…

スクリーンショット 2019-12-19 10.39.39.png
:arrow_down:
スクリーンショット 2019-12-19 10.21.06.png

users テーブルの全レコードがヒットしてしまいました :scream:

ユーザー入力をそのまま採用した結果、こんなSQL文になりました。
WHERE句が常にTRUEになるので、全てのレコードが対象になったんですね。

SELECT * FROM users WHERE id = 0 OR TRUE; --;

ちょっと付け加えて、お次は「0 OR TRUE; DELETE FROM users; --」で検索…

スクリーンショット 2019-12-19 10.40.14.png
:arrow_down:
スクリーンショット 2019-12-19 10.24.38.png

あれ?さっきと同じ??

MySQLを覗いてみると…

スクリーンショット 2019-12-19 10.24.46.png

な ん と い う こ と で し ょ う
匠の手によって全てのレコードが削除されてしまいました :skull:

先ほどの例とだいたい同じですが、
検索クエリを正常終了させた後にDELETE文が実行されちゃってますね…

SELECT * FROM users WHERE id = 0 OR TRUE; DELETE FROM users; --;

対策する

SQL 文の組み立ては全てプレースホルダで実装する。

「安全なウェブサイトの作り方」ではmysqliが使われていましたが、PDOでもプレースホルダを使って実装できます!

先ほどは文字列結合を使ってクエリを組み立てていましたが、
今度は「:id」のようにコロン+パラメータ名 の形でクエリ内に記載します。

そしてクエリ実行の前に bindValue 関数を使ってバインドします。
(bindParam 関数でもバインドできます。 変数が評価されるタイミングが異なるそうです。)

修正前
$prepare = $pdo->prepare('SELECT * FROM users WHERE id = '. $id. ';');
$prepare->execute();

:arrow_down:

修正後
$prepare = $pdo->prepare('SELECT * FROM users WHERE id = :id;');
$prepare->bindValue(':id', $id, PDO::PARAM_INT);
$prepare->execute();

プレースホルダを使ってバインドすると、クエリとして解釈されないようです。
あくまで「値」として扱われるため、変なものを注入した場合でもクエリとして実行されないんですね。

実際にさっきの攻撃を試してみましょう

スクリーンショット 2019-12-19 10.49.06.png
:arrow_down:
スクリーンショット 2019-12-19 10.48.46.png

何も検索されなくなりました!

スクリーンショット 2019-12-19 10.49.15.png

今度は磯野家も無事でした… :family_mwgb:

まとめ

SQLインジェクションによる攻撃と、その対策方法について実際に試してみました。

SQLインジェクションやプレースホルダによる対策自体は知っていて、学生時代にJavaで一度やったことある程度でした。
その時は理解が浅い状態だったので、仕組みも含めて復習できて良かったです :relaxed: :two_hearts:

明日は

「North Detail Advent Calendar」 22日目は @yamatohkd さんです! :clap:
スマホカメラで手のモーションを記録してUnityでピアノ演奏したかった

参考

60
54
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
60
54

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?