なぜPHPアプリにセキュリティホールが多いのか?

第29回SQLインジェクションの復習

セキュリティは古くて新しい問題です。SQLインジェクションも古くからある問題ですが現在の問題です。対策は比較的簡単なのですが今でもなくなりません。と言うよりも今でも現役のセキュリティ上の問題で十分注意が必要です。この連載でも何度かSQLインジェクション対策について簡単に取り上げています。

今回はSQLインジェクションを復習してみたいと思います。

SQLインジェクションとは

SQLインジェクションはプログラマが意図しないSQL文を実行させる攻撃で、2種類の攻撃方法に分類できます。

  • 直接SQLインジェクション
  • 間接SQLインジェクション

直接SQLインジェクション

直接SQLインジェクションとはクエリ文字列、フォーム、クッキー、HTTPヘッダ(User Agent, Refererなど)に不正なSQL文を挿入して実行させる攻撃です。

攻撃例

http://example.jp/login.php?username=admin&password='+OR+1=1;--
サーバ側のSQL文
"SELECT * FROM users WHERE username = '".$_GET['admin']."' AND password ='".$_GET['password']."';";

SELECT * FROM users WHERE username = 'admin' AND password = '' OR 1=1;--';

間接SQLインジェクション

間接SQLインジェクションとは直接入力したデータを利用しないで、一旦データベースやファイルなどに保存されたデータを利用して不正なSQL分を実行させる攻撃です。

攻撃例

Step1:SQLインジェクションを行うデータを保存させる。

Username: admin';--
Password: hogehoge

Step2: 保存されたデータを利用してSQLインジェクションを実行
SELECT * FROM users WHERE username = 'admin';--' AND password = 'hogehoge';

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

前項までのように単純な直接SQLインジェクションや間接SQLインジェクションではなく、より高度なブラインドSQLインジェクション攻撃も広く知られています。

ブラインドSQLインジェクションとはデータベース構造を知らずにSQL文を実行する攻撃手法です。SQLデータベースサーバ持つascii関数、substring関数などの文字列関数やbenchmark関数などを利用してデータベース構造を解析します。ブラインドSQLインジェクションはすべてのSQLインジェクション脆弱性で行えるわけではありません。しかしブラインドSQLインジェクションが実行可能な場合は非常に強力です。

SQLインジェクション/ブラインドSQLインジェクションで可能な攻撃の例

  • データベース構造の解析(データベース名、テーブル名、フィールド名など)
  • データベースユーザの取得
  • ファイルの読み書き
  • コマンドの実行
  • シェルの取得
  • リモートシェルの取得

このような攻撃を自動化するツールも多数公開されています。別の機会でこのツールは紹介したいと思います。

最近のSQLインジェクションのセキュリティアドバイザリには、そのまま利用可能なブラインドSQLインジェクションの攻撃コードが添付されている場合も少なくありません。

下記の例では数値型ので入力を期待しており、入力のバリデーションも行われず、出力時のエスケープも行われてないことが分かります。

例:asciiを使用したブラインドSQLインジェクション
def generate_url(self, ascii):
    return self.url +
    "auktion_text.php?id_auk=1+and+1=1+and+ascii(substring((SELECT%20"
    + self.table_field + "%20FROM%20" + self.table_prefix +
    "fh_user+WHERE+iduser=" + str(self.id) + "%20LIMIT%200,1)," +
    str(self.charn) + ",1))%3E" + str(ord(ascii))
例:benchmarkを使用したブラインドSQLインジェクション
http://server/[JOOMLA_PATH]/index.php?option=com_kunena&Itemid=86&func=announcement&do=show', link='0wn3d', task='0wn3d' WHERE userid=62 AND 1=if(substring(@@version,1,1)=5,benchmark(999999,md5(@@version)),1)/*
http://server/[JOOMLA_PATH]/index.php?option=com_kunena&Itemid=86&func=announcement&do=show', link='0wn3d', task='0wn3d' WHERE userid=62 AND 1=if(substring(@@version,1,1)=4,benchmark(999999,md5(@@version)),1)/*

これらは典型的なMySQLサーバ用のブラインドインジェクション攻撃用コードです。下の例はURLを使ったブラインドインジェクションの行い方だけが例示されたケースですが、上の例は至れり尽くせりでそのまま自動化されたブラインドSQLインジェクション攻撃に使えるスクリプトになっていた例です。

SQLインジェクション対策

SQLインジェクション対策の基本はエスケープです。プリペアードクエリもSQLインジェクション防止に使えますが、SQLデータベースにはプリペアードクエリが無いものも多く利用されています。

最近広く利用されているSQLiteなどでは数値型のデータ型がありません。入力バリデーションが甘いアプリ、エスケープもしないアプリでは簡単に間接SQLインジェクションが可能になります。

SQLインジェクション対策

  • パラメータを全て文字列として取り扱いエスケープする
  • SQL語句やテーブル、フィールド名はホワイトリストでバリデーションする
  • 文字エンコーディングを厳格に取り扱い、文字エンコーディング指定にはAPI(pg_set_client_encoding, mysql_set_charset)を利用する
  • プリペアードクエリが利用可能な場合はプリペアードクエリを利用する
  • 読み込み専用のアカウントも利用する

エスケープには必ずマルチバイト文字列に対応したデータベース専用のエスケープ関数pg_escape_string, mysql_real_escape_stringを利用してください。データベースの文字エンコーディングにバイナリかバイナリに近い文字エンコーディング(ASCIIなど)を指定すると脆弱性の原因になります。必ずShiftJIS, EUC-JP, UTF-8など実際に利用している文字エンコーディングを指定してデータベースを作成します。

パラメータをすべて文字列として取り扱った場合、SQLパーサの処理が増えるため、多少パフォーマンスに影響します。パフォーマンスへの影響が気になる場合は「本物」のプリペアードクエリの利用を検討するとよいでしょう。

PHPに限らずデータベースアクセスを抽象化するライブラリのプリペアードクエリAPIは、データベースのプリペアードクエリを実際に使用していないことが多くあります。パフォーマンスを向上する目的で擬似プリペアードクエリを使用しても意味がないので注意してください。

SQLインジェクションはコンテンツの改ざんにも利用されます。読み込みしか必要ないリクエストの処理に読み書き可能なアカウントでアクセスすることが常態化していませんか? セキュリティ対策の基本原則に「最小権限の原則」があります。必要最小限の権限のみ与えることにより、万が一問題が発生した場合に被害を最小限に食い止めるための原則ですが、かなり重要なデータを取り扱うサービスでも使い分けされていない例が多くあります。

まとめ

紹介したブラインドSQLインジェクションが可能な脆弱性は第28回 あまり語られないセキュリティの基本 ─⁠─ トラストバウンダリで解説したトラストバウンダリの考え方、厳格な入出力管理の考え方を実践していればほぼ確実に防げた脆弱性でしょう。

SQLインジェクションは非常に重大なセキュリティ上の問題です。しかし、JavaScript/HTMLインジェクションに比べれば対処策も簡単で、ほぼ確実に防げる攻撃です。SQLインジェクション対策はクエリ出力時にだけ完全に対処していれば防げる脆弱性だからです。

SQLインジェクションに脆弱なアプリは絶対に作らないよう注意しましょう。

おすすめ記事

記事・ニュース一覧