X

完全なSQLインジェクション対策

(Last Updated On: 2018年12月14日)

不完全なSQLインジェクション対策だけで、SQLインジェクション対策は万全、と誤解しているケースが少なくないです。

  • プリペアードクエリ/プレイスホルダを使ったSQLインジェクション対策でOK

は誤りです。「とにかくプレイスホルダを使おう」では脆弱性は無くなりません。

簡単な証明:プリペアードクエリ”だけ”では、識別子(カラム/テーブル等)を使うソートクエリ、特定カラム抽出クエリを”原理的”に無害化できない。識別子のエスケープ/バリデーションが必須。(問題はコレだけはありません)

似たような間違いに「出力対策をするのがセキュリティ対策」だとする考え方があります。こういう考え方になる原因はセキュリティ設計や原則を理解していないことにあると思われます。

アンチプラクティスであっても正しく動作するならまだ良い方です。しかし、論理的・原理的に出力対策”だけ”では正しく動作するアプリケーションは作れません

参考:

前提条件

完全なSQLインジェクション対策では入力対策も欠かせません。入力処理では、入力パラメータを厳格にバリデーションします。これは重要なセキュリティ対策ですが、重要でないと誤解しているケースが多々あります。入力処理も重要ですがここでは詳細は省略します。

参考:

OWASPセキュアコーディング – クイックリファレンスガイドにはセキュリティ対策の第一番目の要素として、セキュアに入力をバリデーションする方法が具体的に記載されています。ISO 27000/ISMSはより具体的なデータバリデーション仕様を紹介/要求しています。

完全なSQLインジェクション対策では入力処理時に以下のバリデーションがされていることが必須です。

  • 文字エンコーディングのバリデーション
  • 数値パラメータのバリデーション(範囲を含む)
  • その他、当たり前のバリデーションすべて(ヌル文字や制御文字など、不正な文字でおかしな動作をするDBがある)

セキュアなプログラムの基本構造は以下の図のようになります。

完全なSQLインジェクション対策 – 出力対策ではコンテクストを考える

前置きが長くなりましたが、本題のSQLクエリをサポートするデータベースシステムに対する完全な対策を紹介します。

入力処理の時点ではデータがどのような出力に使われるか判りません。例えば、ユーザー名はデータベースにも、メール、HTML、JavaScript、XPATHクエリ、LDAPクエリ、IMAPクエリ、コマンド実行、ファイルのパス、その他、色々なモノに利用される可能性があります。

出力のコンテクストが重要です。出力コンテクストによって、安全な出力が方法が異るからです。データベース、ブラウザなど、出力先だけでなく、出力のコンテクストが重要です。

SQLインジェクション攻撃の完全防止にも出力コンテクストを考慮した対策が必須です。データベースではSQL言語以外に、正規表現/XML/JSONなど別の言語や表現がサポートされています。以下のどれが欠けても、不正なデータ操作が行われる可能性があります

  • パラメーターの無害化(エスケープ、バリデーション、プリペアードクエリなどのAPI)
  • 識別子の無害化(エスケープ、バリデーション)
  • LIKEクエリの無害化(エスケープ、バリデーション)
  • 正規表現の無害化 (エスケープ、バリデーション)
  • XMLとXMLクエリの無害化 (エスケープ、バリデーション、APIの利用)
  • JSONデータの無害化 (エスケープ、バリデーション、APIの利用)
  • その他、全てのコンテクストの無害化

出力対策では出力先のコンテクストが非常に重要ですが、これを疎かにしているケースは少なくありません。これを理解していないとSQLインジェクション対策として「パラメーターへのインジェクションを防止するプリペアードクエリだけ使えば大丈夫」と誤解します。1

完全なSQLインジェクション対策には”エスケープ”と”バリデーション”が欠かせないです。

  • エスケープや入力バリデーションはSQLインジェクションに対するセキュリティ対策ではない

とする主張や考え方は明らかな誤りであった、と言えます。

プリペアードクエリ/プレイスホルダは一部しか対応できない不完全な対策

SQLクエリにはSQLクエリパラメーター以外に、識別子、LIKEクエリ、正規表現、XML、JSON、場合によってはOSコマンド実行まで、様々な出力コンテクストが存在します。全ての出力コンテクストが安全に処理できなければなりません

全てのコンテクストに対応しないプリペアードクエリ/プレイスホルダ(普通はパラメーターにしか対応していない)が不完全なセキュリティ対策であることに議論の余地はありません。

例えば、ある程度本格的にデータを取り扱うシステムはソートカラムを指定できます。カラム名は識別子なのでプリペアードクエリ/プレイスホルダで分離できません。この為、カラム名でSQLインジェクションが可能になっているコードは少なからず存在します。ビジネスデータを取り扱うアプリの場合、”少なからず”ではなく”当たり前”に存在します。

プリペアードクエリ/プレイスホルダでは、SQL言語の一部である識別子やLIKEクエリに対するインジェクションにさえ対応できません。(注:LIKEクエリインジェクションも正規表現インジェクションと同様に不正なデータ取得を許してしまうケースがある)

参考:SQL識別子のエスケープ

プリペアードクエリ/プレイスホルダでは、正規表現やXML、JSONなど、現在では当たり前のデータベース機能を安全に使うことも出来ません。

高度なストアードプロシージャー言語を持つRDBMSの場合、ストアードプロシージャー言語がサポートする出力コンテクスト全てが安全でないとRDBMS経由であらゆるインジェクション攻撃を行える可能性があります。

プリペアードクエリ/プレイスホルダはDoS攻撃にも対応できない

以下のクエリを考えると、プリペアードクエリ/プレイスホルダはインジェクションによるDoS攻撃にも対応できないことが明白です。

SELECT * FROM some_table WHERE type = 'some_type' LIMIT 100000000;

limit句以降に100000000が挿入(インジェクション)2されて困るシステムは数えきれません。

単純な整数パラメータに対するインジェクション攻撃でさえ、プリペアードクエリ/プレイスホルダだけでは完全に対応できません

参考:

正規表現をサポートするデータベースの場合、ReDoS攻撃に対する対策も欠かせません。これもプリペアードクエリ/プレイスホルダでは対策できません。

入力バリデーション

出力時の対策はセキュアなアプリケーションの十分条件を満す対策です。入力データのバリデーションは必要条件を満す対策です。「不正な値」は誤作動の原因になります。

  • ユーザーID/グループID(数値、文字列)が意味を持ち、権限などに影響する

数値/文字列が意味を持つ場合、「不正な値のインジェクション」により、開発者が予定していない動作をする可能性があります。これには不正操作だけでなく、サービス妨害(DoS)なども含まれます。例えば、

  • SELECT * FROM mytable LIMIT 999999999
  • 壊れた文字エンコーディングによるDoS(システムの何処かに保存された不正文字エンコーディングが発生させる例外/エラーはDoSの原因になる。例えば、今のブラウザは壊れた文字エンコーディングで”真っ白”なページを表示する。DBで不正文字エンコーディングエラーになる動作もDoSの原因になる)
  • ReDoSによるDoS

必要十分条件を満たしていないとデータベースを利用した攻撃の可能性を廃除できません。入力バリデーションと入力エラーチェックは「完全なSQLインジェクション対策」に欠かせません。

入力値の種類は3種類しかなく、不正な入力値の受け入れはメリットのないリスクの受け入れです。特にアプリケーションにとってはメリットが一切ないリスクの受け入れ3です

※ 攻撃用入力の大半がアプリにとって”不正な入力”です。アプリに一切不要な”不正な入力”を受け入れる=攻撃者の都合のよい攻撃しやすい脆弱なアプリ、です。2017年版のOWASP TOP 10から10番目の脆弱性として登録されています。

アプリケーションレベルのソフトウェアに於て、入力値をバリデーションするセキュリティ対策は必ず行うべき必須中の必須セキュリティ対策で、入力バリデーションはソフトウェアセキュリティの第一原則です。

バリデーションにはの3種類があります。

  • ”入力バリデーション”(不正入力データ廃除)
  • ”ロジックバリデーション”(入力ミス処理)
  • ”出力バリデーション”(不正出力データ廃除)

全て大切ですが、最も重要なバリデーションは最初のバリデーション4、つまりアプリケーションに対する入力処理のバリデーションです。これはコンピュータプログラムの”動作原理”に基づきます。

  • 原理:コンピュータプログラムは”妥当な入力”でしか”正しく”動作できない。

つまり、大前提として”妥当な入力”でしか”正しく”動作するプログラムは作れないのです。(正しく動作するプログラム=脆弱性がないプログラム)

残念ながらほとんどのアプリケーションに十分な入力バリデーション処理がありません。ほとんどのアプリケーションは不十分なロジックバリデーションしか持っていません。

完全なSQLインジェクション対策

SQLシステムに限らず、出力対策は出力セキュリティ対策の三原則(+1原則)で完全に対応できます。

出力セキュリティ対策の三原則

  1. エスケープ
  2. API(エスケープが不必要なAPI)
  3. バリデーション

この3つのうち1つ欠けても完全な対策になりません。この三原則を完全に適用するには、出力先のコンテクスト(多くの場合はそのコンテクストにおけるエスケープ方法、エスケープできない場合はバリデーションでリスクを廃除する方法)を正しく理解する必要があります。

+1原則入力バリデーションです。

まずアプリケーションの入力処理で入力バリデーションをしておかないと、SQLインジェクションに限らず、ありとあらゆる攻撃のリスクを残します。データベースへのクエリで失敗するような入力はDoS攻撃の原因になります。SQLiteの場合、数値データのハズが文字列が入っている、といった出鱈目な状態になります。

識別子(テーブル名/カラム名など)はバリデーション無しでは非常に危険です。以下のクエリを考えると解ります。情報を盗み放題、改ざんし放題になる可能性があります。

$sql = ‘SELECT ‘.pg_escape_identifier($_GET[‘col’]) .’ FROM ‘. pg_escape_identifier($_GET[‘table’]);

まとめ

出力処理際にはコンテクストが重要であること、入力処理におけるバリデーションが重要であること、はセキュリティ専門家にとっては当たり前のこと5ですが、その当たり前が正しく啓蒙されていない時期が長くありました。今でも勘違いしている方が少くありません。

バリデーションしていないデータは危険です。

  • 壊れた文字エンコーディング攻撃に脆弱になる可能性がある
  • 特殊文字(ヌル文字など)によるセキュリティフィルタ回避の可能性がある
  • 過大/過少な数値による問題の可能性がある
  • 長大/過少な文字列による問題の可能性がある
  • 二次的な攻撃に利用される不正なデータが保存される可能性がある
  • 不正なデータによるDoS問題の可能性がある

ざっと挙げただけでも、バリデーションなしのデータには様々なリスクがあります。「バリデーションは必要ない」とする意見/コードはセキュリティ対策として論外ですが、まだまだ少くありません。

何が根本的な対策か?と区別し個別に対策(局所化/抽象化)する、という考え方もあるようですが、この考え方は意味がないどころか、リスク分析の観点から有害でさえあります。

セキュリティ対策の評価はリスク削減の費用対効果で決まります。対策が根本的かどうか?は「ITセキュリティ対策の目的」である「許容可能な範囲内のリスクに抑えてITシステムを利用する」に対して決定的な意味はありません。”セキュリティ対策”としては全く根本的対策ではないWebアプリケーションファイアーウオール6でさえ、費用対効果/リスク削減効果が優れているなら導入すべきです。

入力、出力、ロジック全てが根本的な対策であるので区別する意味はありません。 入力バリデーションと合わせて、ソフトウェア全体で安全性を高めることが必要です。

原理・論理・基本に基づく対策を行なえば、完全なSQLインジェクション対策が可能です。原理・論理・基本に基づくソースコード検査であればSQLインジェクションフリーであることを保証することも可能です。保証可能なので保証付き検査サービスもはじめました。

私はこういったサービスに頼らずとも、開発者の皆さん自身でセキュリティ対策できる、と信じています。原理・論理・基本を無視した危険な「セキュリティ対策」が蔓延し、セキュリティ業者の商機となっています。無駄にセキュリティ業者を儲けさせる必要は一切ありません。

参考:

「プリペアードクエリを使えばOK」と同じ誤解が「コマンドと引数を分離すればOK」です。これも危険な誤解です。

蛇足

なぜ繰り返し解説していることを解説しているのか?を説明する蛇足なので、興味がない方は読む必要はありません。

「根本的なことが重要で他はオマケ、入力バリデーションはセキュリティ対策としてオマケ」の考え方の出所を知らなかったのですが、徳丸さん(スライド p54)のようでした。セキュリティ対策ではない、から進歩しているのですが、これはいただけません。

バリデーションも重要なセキュリティ対策です。「元々実行すべきモノがセキュリティ対策でない」というロジックも聞いたのですが、出力対策も元々実施すべきモノなのでセキュリティ対策ではない、も成り立ってしまいます。何を言いたいのか理解できません。

言った方が徳丸さんの考えから直接影響を受けたのか判りませんが「元々実施すべきで効果はあってもセキュリティ対策とは違う」と考えていることは、これはこのやり取りからも明らかです。

意味が通らないセキュリティ概念でセキュリティ対策をしていては、何時まで経っても「セキュリティとは何が何だか分らない」と混乱してしまう状況は変わりません。意味が通らない概念だと解らなくて当たり前です。

理由: セキュリティ対策の目的と手段 〜 根本的誤りの元 〜

徳丸さんとの議論は終わっている(標準セキュリティの概念とは全く異る、と結論は出ている)ので、議論するつもりはありませんが、誤りの指摘はしないとならないです。

このエントリは最近作ったと思われる不適切な資料(p53)を見かけたので改めて不十分なSQLインジェクション対策の指摘しておきます。

出力のセキュリティ対策は「一部の脆弱性の為に、とにかく◯◯をする」では無く、出力セキュリティの三原則(まとめ参照)がとにかく出力のセキュリティ対策で行うべきことです。

  1. エスケープ
  2. API(エスケープが不必要なAPI)
  3. バリデーション

SQLシステムへの出力は以下のようなモノが在ります。

  • パラメーターの無害化(エスケープ、プリペアードクエリなどのAPI、バリデーション)
  • 識別子の無害化(エスケープ、バリデーション)
  • LIKEクエリの無害化(エスケープ、バリデーション)
  • 正規表現の無害化 (エスケープ、バリデーション)
  • XMLとXMLクエリの無害化 (エスケープ、バリデーション、APIの利用)
  • JSONデータの無害化 (エスケープ、バリデーション、APIの利用)
  • その他、全てのコンテクストの無害化

これを見れば、出力セキュリティの三原則(エスケープ、API、バリデーション)で考えなければならないことは明らかです。いい加減、ソースコード検査時に「SQLにはプレイスホルダだけ使っていればOK」とする脆弱なコードが無くなってほしいので、繰り返しになりますが指摘しました。

他の資料にも信頼境界線の考え方、リスク分析の考え方、ソフトウェアセキュリティの概念部分で根本的な誤りも見つけてしまいました。ツイッターで指摘したのですが無駄でした。ソフトウェアの信頼境界線が間違っている(アプリとデータベースが同じ境界内になっている)件を指摘した回答がこれです。

面倒ですが指摘しなければなりません。これも近日中に改めて誤りを指摘します。

追記:

なぜこういうセキュリティの基礎/基本を無視し、明らかな脆弱性(識別子の問題、不適切/不十分な入力データ検証など)を無視してしまうのか?その原因が解らないです。リスク分析はセキュリティ対策の最初の一歩ですが、本当のリスク分析をした事がないのかも知れません。


    1. コンテクストに加えて文字エンコーディングも重要です。出力先(この場合はSQLシステム)が期待する文字エンコーディングを使わないと誤作動する場合があります。通常、文字エンコーディングのバリデーションは入力処理で行うのでここでは省略しています。出力対策としては省略はしていますが、出力対策の前提として”文字エンコーディングが正しい”は必須のセキュリティ要素です。
    1. 通常はこのような異常な整数入力は入力データバリデーションで廃除されるべき。値をユーザーが制御可能な場合は入力エラー処理で廃除されるべき。
    1. アプリにとっては不正な入力値の受け入れは120%無用なリスク受容です。しかし、関数/メソッドなどでは必要なリスク受け入れである場合が多いです。全ての関数/メソッドで入力データバリデーションを行なえば、確実にコードの安全性は向上します。しかし、確実に遅くなります。これのバランスを取るのが契約による設計です。
    1. プログラミングの基礎原則にFail Fast原則(失敗するモノは早く失敗させる)があります。アプリケーション内でのバリデーションで最も早いバリデーションは、アプリケーション入力に対するバリデーションで最も重要です。 ただし、全てのバリデーションが大切です。最も重要 ≠ 他は不必要、です。
    1. OWASPセキュアコーディング – クイックリファレンスガイドの1番目と7番目
  1. WAF(Webアプリケーションファイアーウォール)で対応するセキュリティ脆弱性の多くは、問題の原因を”解決”するのではなく、”隠蔽”します。「WAFで仮想パッチをする」とよく言われるのはアプリ/システムの問題を”解決”せず”隠蔽”するからです。
yohgaki:
Related Post
Leave a Comment