入力バリデーションでほぼ全てのインジェクションリスクを回避/防止できるケースは以前書いたブログで紹介しています。
今回は趣向を変えて、入力バリデーションで許可してしまった文字で発生するリスクをざっと紹介します。
※ ここで紹介する考え方は脆弱なブラックリスト型です。より安全な方法はホワイトリスト型の考え方です。参考: ほぼ全てのインジェクション攻撃を無効化/防止する入力バリデーション
ヌル文字インジェクション
たった一文字ですがヌル文字(”\0″、URLエンコードなら”%00”)を許可してしまうとセキュリティ問題が発生します。ヌル文字には意味があるのが様々な問題が生まれます。
- ヌル文字はC言語では文字列の終端を表す
- 他の言語ではヌル文字が文字列の終端にならない
最も一般的な攻撃はC言語文字列に対する攻撃の1つであるファイルパスへのインジェクション攻撃です。
PHP/Ruby/Pythonなどではヌル文字は文字列の中では”ただの文字”です。特別な意味を持ちません。しかし、ファイルシステムへのアクセスにはC言語で記述されたAPIを利用します。この解釈の違いがセキュリティ問題の1つである、ヌル文字インジェクションです。
ヌル文字インジェクション攻撃の具体例
以下のようにヌル文字が含まれたファイル名があった場合で、最後の “.” 以下の拡張子が問題ない拡張子であることをバリデーションするコードがPHP/Ruby/Pythonであったとします。
“evil_javascript.js\0.jpg”
PHP/Ruby/Pythonでは “.jpg” ファイルであると判定してしまいます。
OSのファイルAPIは ”\0″ で文字列が終りとなるので
””evil_javascript.js”
と解釈します。画像のつもりでアップロードしたファイルがJavaScriptファイルになり攻撃に利用可能になってしまいます。
幸い今のPHP/Ruby/Pytohnではファイルパスにヌル文字が含まれるモノは異常なファイルパスとしてOSのファイルAPIに渡さずエラーにします。これで完璧だな!と思うかも知れませんが、ファイルパスだけでもそうではありません。
例えば、PHPの場合は入力パラメーターが”ファイルパスである”と組み込みPHP関数/メソッドで指定することにより”ファイルパスだからヌル文字があると異常なデータであると検出”します。PHP以外の言語でも同じような感じで実装してあります。
ヌル文字インジェクション対策はブラックリスト型対策
この”ファイルパスだからヌル文字があると異常なデータであると検出”させる方法はブラックリスト型の対策です。ブラックリスト型の対策は仕組的に脆弱でメンテナンスが必要な方式です。
例えば、PHPに”ファイルパス型の入力パラメーター定義”が導入された際にかなり多くの関数/メソッドに”ファイルパス型の入力パラメーター定義”を適用しました。しかし、PHPプロジェクトがデフォルトのソースに含んだモジュールに漏れ無く適用された、と言えるまでに数年間の時間が必要でした。
PHPに限らず、C言語で記述された拡張モジュールは多数あります。これらのサードパーティーモジュールの開発者はその言語のモジュール開発に精通している、とは限りません。うっかり/知らずに”危険なヌル文字入りのファイルパス”をそのままOSに渡してしまう可能性が十分にあります。
バイナリセーフ/非バイナリセーフな文字列処理
問題はファイルパスだけではありません。ヌル文字を普通の文字として取り扱う処理系をバイナリセーフと言います。反対にヌル文字を非バイナリセーフと言います。ヌル文字はファイルパスの取り扱いに限らず、様々な問題の原因なってきました。
PHPではPOSIX互換の正規表現関数は廃止されました。最も大きな理由はPOSIX互換正規表現関数(の多く)が非バイナリセーフだったからです。非バイナリセーフでないことを理解しないで使うと、正規表現でセキュリティ用のバリデーションを行っても簡単に回避できてしまいます。
こういった問題はシステムの何処でも発生する可能性があり、過去にはこの違いを利用してWAF(Webアプリファイアーウォール)を迂回できる、などを含め様々な問題が明らかになってきています。
そして、バイナリセーフ/非バイナリセーフの問題は今でも問題で、仕組的にこの問題が完全に解決することは、少なくとも近い将来はありません。
文字列にヌル文字を許さない処理系
ヌル文字があると、開発者が普通では思いつかないような攻撃が可能になる場合があります。文字列の中のヌル文字は十中八九は害悪でしかありません。このためシステム/APIの中にはヌル文字を許さないモノがあります。
ヌル文字とPostgreSQL
例えば、PostgreSQLの文字列型やJSON型にヌル文字は保存できません。
yohgaki@[local] ~=> insert into tbl (c) values (E'abc\0def'); ERROR: 22021: invalid byte sequence for encoding "UTF8": 0x00
ほとんど有害でしかないヌル文字がデータベースに保存できないだけなら何の問題もないのでは?と思うかも知れません。実際、エラーになって保存できないのはほとんどの場合で良いことです。しかし、こうやってエラーになること自体が問題の原因になります。
入力データがPostgreSQL以外のヌル文字を保存できる処理系で保存され、その後にPostgreSQLに保存されるとします。この場合、一旦妥当な入力データとして処理されてしまったデータが、PostgreSQLに保存できず困ったことになります。最悪、DoS状態に陥ることさえあり得ます。
特定の文字をプログラムの奥深くで禁止する、という動作の問題
iPhoneでは、特定の文字でフリーズしたり、クラッシュしたりする問題が時々見つかっています。ヌル文字で起きているのではありませんが、これと同じことがヌル文字で起こり得ます。
先に紹介したPHP/Ruby/Pythonのファイルパスにヌル文字を許さない仕様も同じです。攻撃者が何らかの方法でヌル文字をファイルパスとして保存でき、それが繰り返し利用されるようなコードがあると、ヌル文字を含むファイルパスでDoS攻撃が可能になります。
ヌル文字対策 – ヌル文字を入力バリデーションの許可文字に入れない
ほぼ全ての場合で、文字列中にヌル文字許されることはありません。特別にヌル文字を許可しなければならない場合にのみ、ホワイトリスト型で許可すべきです。
Fail Fast原則(失敗するモノはできる限り早く失敗させる原則)に従い対策します。こうしないと時々フリーズしたりクラッシュしたりするiPhoneのようなアプリになります。
改行インジェクション
テキスト型のデータではCR、LF、またはCR/LFが改行として扱われます。改行なんて普通の文字で特にセキュリティリスクがないのでは?と思うかも知れません。
しかし、改行には意味があるテキストインターフェースは複数あります。意味がある文字には大抵の場合でインジェクション攻撃が可能になります。
例えば、HTTPヘッダーやメールヘッダーは改行でヘッダーが区切られており、ヘッダーとボディーは連続する改行で区切られています。”改行”が”区切り文字”として利用され意味があるのでインジェクションができます。
HTTPヘッダーインジェクション
昔、HTTPヘッダーにインジェクション攻撃を行うHTTP Response Splittingが大きな問題になりました。キャッシュを汚染してページの改ざんしたり、JavaScriptインジェクションが可能でした。
今のインターネットRFCではこの攻撃の緩和策として、複数行に渡るHTTPヘッダーはサポートされなくなりました。PHPのHTTPヘッダー関数を設定するheader()関数は改行を含むデータを無効なデータとして処理しなくなっています。
PHPとHTTPヘッダーインジェクション
PHPの場合、HTTPヘッダーを設定する場合は必ずPHPのAPIを使わないと出力できません。header()関数が改行を無効データとするなら問題は全て完全解決している!と思うかも知れませんが、そうではありません。
PHPのように任意のHTTPヘッダーが設定しづらい環境でも、改行を含んだHTTPヘッダーデータを送れてしまうAPIがあります。
setrawcookie()は特殊なクッキーを送れるよう提供されています。この関数は値を全くエスケープしません。つまり何の保護もありません。クッキーヘッダーもHTTPヘッダーなので、余計なヘッダーを設定したり、不正にHTTPボディーを開始できます。
setrawcookie()を使うようなことはほとんどない、とは思います。しかし、何らかの理由で使った場合、開発者の責任で不正なヘッダーやボディを設定されないようにセキュリティ対策をしなければなりません。
PHPスクリプトからHTTPヘッダーを設定、改変できるAPIはほとんどなく、危険な物はsetrawcookie()とheader_register_callback()くらいです。しかし、C言語で記述されたサードパーティーモジュールは自由にHTTPヘッダーを設定可能です。C言語APIでは多い、というより当たり前ですが、HTTPヘッダーを操作するAPIを呼び出し側が妥当なデータ(パラメーター)で呼び出す責任を持っています。渡されたAPIはどんなデータであろうとそのまま処理します。
サードパーティーモジュールが改行文字まで受け入れる仕様であった場合、setrawcookie()を利用する時と同じように注意しないと秘密のページが公開ページになったり、JavaScriptインジェクション攻撃をされたり、改ざんされたページを送信する可能性があります。
HTTPヘッダーインジェクション問題が完璧に解決されて、Webアプリ開発者が全く気にしなくても問題は絶対に発生しない、という状態は何時まで経っても来ないでしょう、使っている言語に関わらず。
メールヘッダーインジェクション
昔、メールヘッダーインジェクション対策がなかった頃のPHPでは “To” (メールの宛先)や “Subject”(メールの件名)でメールヘッダーインジェクションが可能でした。
このため、HTMLフォームのデータをメールで送信するWebアプリで、メールヘッダーインジェクション対策がされていない場合、簡単に不正なメールをそこら中に送ることが可能でした。
メールヘッダーインジェクションが可能な場合、ヘッダーデータの中に送信先やメールのコンテンツを記載し、開発者が意図しないメールを自由自在に他人のWebサーバーから送ることができます。
メールヘッダーインジェクション対策とPHP
かなり昔にPHPのmail()関数は $to と $subject に対するメールヘッダーインジェクション対策は行われました。
bool mail ( string $to , string $subject , string $message [, mixed $additional_headers [, string $additional_parameters ]] )
しかし、$additional_headersパラメーターに対するメールヘッダーインジェクション対策は私が直すまで、全くありませんでした。直したPHPバージョンは5.6/7.0です。
この修正が入るまでは$additional_headersに設定される “Cc” や “Bcc” その他全ての追加ヘッダーのデータによって、メールヘッダーインジェクションが可能でした。その追加ヘッダーデータの1つでもWebアプリユーザーにより制御可能なデータが含まれている場合、攻撃者は自由自在に出鱈目なメールを送信可能でした。
因みに、現在のPHPでも$additional_headersによるメールヘッダーインジェクションは可能です。改行インジェクションにより、メールヘッダーとメールボディが分割されないような修正を加えただけです。メール内容の改ざんはできませんが、脆弱なPHPコードを書くと意図しない送信先にメールを送る、などの攻撃が今でも可能です。
完全にメールヘッダーインジェクションを防止する仕様にすることもできるのですが、今のところ誰も実装するつもりがないようなので、私が実装するまでこの仕様変更は実装されないと思います。
※ mail()関数の$addition_parametersはsendmailコマンドに渡すパラメーターを設定できます。これにはセキュリティ対策がないのでコマンドインジェクションを防止するにはコマンドインジェクションを防止するデータバリデーションが必須です。
他の改行インジェクション
”改行”に意味があるデータ/インターフェースには”改行インジェクションが可能”です。例えば、memcachedにはテキストインターフェースがあり、このインターフェースでは”改行”が意味を持っています。
memcachedへのアクセスがこのテキストインターフェースを使って実装されている場合、HTTPやメールのヘッダーインジェクションと同じ要領でmemcachedにもインジェクションが可能です。
改行が意味を持つインターフェースは結構あります。
改行対策 – 改行を入力バリデーションの許可文字に入れない
テキストデータであっても、改行が必要なデータはそれほど多くありません。Webアプリのテキストデータの大半が”改行”を全く必要としていません。というより大半が”改行”があると有害(=無効なデータ)です。
特別に改行を許可しなければならない場合に限り、ホワイトリスト型で改行を許可します。ヌル文字対策と同じく、Fail Fast原則(失敗するモノはできる限り早く失敗させる原則)に従い対策します。
まとめ
あれ?他には?と思ったかも知れません。他にも沢山インジェクション攻撃に利用可能な文字があります。
もっといろいろ書こうと思ったのですが、ヌル文字インジェクションと改行インジェクションをざっくり説明するだけで疲れてしまったので終わりにします。たった2種類の文字を説明しただけでも結構長くなってしまいました。他の代表的なモノだけを簡単に説明しても長大なブログになって誰も読まないと思います。
ポイントは、1文字でも意味がある文字には注意が必要、であることです。
誰にも「出鱈目なデータ」によるAPI利用の完全な安全性を保証できない
ユーザー/攻撃者が設定可能な入力データをそのままAPIに渡して、危険かも知れないデータが確実に問題なく処理されるか?100%の自信を持って「大丈夫だ」と言える開発者は居ないと思います。
このAPIには自信がある!といった場合でも、100も200も300、それ以上あるAPIの実装全てを正確に把握して、危険かもしれないデータをAPIに送っても「絶対に大丈夫である」と言いきれる開発者は1万人に1人も居ないと思います。
本当に出鱈目なデータをAPIに送っても、絶対に大丈夫である、と断言するにはPHP/Ruby/Pythonで書かれたAPIのコードを読むだけでは不十分です。C言語で実装されたPHP/Ruby/Pythonの関数、さらにその関数が呼び出す膨大な数のCのライブラリAPI全ての実装、OSのシステムコールレベルの実装まで知る必要があります。その上で、組み合わせて使ったケースでも問題がない事も確実に把握する必要があります。
※ 参考:壊れた文字エンコーディングは何もやっても正しく動作することを保証できない
様々な言語やライブラリ実装の実態を知っているので、出鱈目なデータを送っても大丈夫、とは私は口が裂けても言う事は出来ません。調べる事は不可能ですし、仮に調べられたとしても、常に変更が加わる生ものであるプログラムの現状を継続的に把握することは不可能です。
現状では、必須のセキュリティ対策用のAPIでさえマトモに定義されていなかったり、定義されていても不十分だったりするモノに溢れています。
効果的な対策がありセキュリティ専門家は何十年も前から提唱している
この調子では何時まで経ってもソフトウェアのセキュリティが十分か検証不可能では?と思うかも知れません。しかし、救いはあります。ほぼ全てのコードは「妥当な(正しい)データ」を受け取った場合に「正しく動作する」ように書かれています。バグがある事もありますが、少なくとも開発者の意図としては「正しく動作する」つもりで書いています。
対策はシンプルです。ソフトウエアセキュリティの専門家が1990年くらいから提唱しています。セキュアコーディング原則1の「全ての入力をバリデーションする」を適用するだけです。
安全なプログラムを作るには、全てのAPIに「妥当な(正しい)データ」を渡すように作るのが一番の近道です。そもそもプログラムは正しいデータとコードの両方がないと、正しく動作できません。データの問題はプログラムの機能構造の中に抽象化するのではなく、Fail Fast原則に従いできる限り早く「妥当な(正しい)データ」であることバリデーションします。
出鱈目なデータをAPIに渡しても開発者も利用者は損しかしません。遅すぎるデータバリデーション、不十分なデータバリデーションではどこで問題が発生するのか分かりません。データの問題をプログラムの機能構造の中に抽象化し個別に データの問題に対応する対策(ダメな所を定義/見つけて対策する方法)は構造的に脆弱なブラックリスト型の対策です。
脆弱なブラックリスト型の対策は容易に漏れが発生します。Railsセキュリティガイドではブラックリスト型の対策を次のように評価しています。
サニタイズ、保護、検証では、通常ホワイトリストの方がブラックリストよりも使用されます。
※ データセキュリティの維持は、サニタイズではなく、検証(バリデーション)を行うべきです。
ブラックリストではなくホワイトリストに基づいた入力フィルタを実施することが絶対重要です。ホワイトリストフィルタでは特定の値のみが許可され、それ以外の値はすべて拒否されます。ブラックリストを元にしている限り、必ず将来漏れが生じます。
脆弱なブラックリスト型対策で「データセキュリティ」を雑に扱うプログラムでは、脆弱性を攻撃する犯罪者とWebアプリファイアーウォールを提供したり外部からWebサイトを検査するセキュリティ業者が喜ぶだけです。
参考:
CERT TOP 10 Secure Coding Practices(セキュアコーディング原則)は全ての開発者が必修です。
契約プログラミングを採用するしないに関わらず、契約プログラミングの概念も必修だと思います。
Leave a Comment