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

第28回あまり語られないセキュリティの基本 ─⁠─ トラストバウンダリ

何事も基本が一番大切です。システム開発/コンピュータプログラミングも同じく基本が大切です。基本を守らないシステム設計やコーディングは問題発生の原因となる可能性が高いことは経験上理解できると思います。

セキュリティ対策の基本も非常に大切ですが、何故か基本はあまり語られていません。今回は安全性確保の基本中の基本を紹介したいと思います。

あまり語られない核心

正しいセキュリティ対策を行うには、セキュリティ問題の原因を理解する必要があります。

セキュリティ対策の記事などでは個々のセキュリティ問題に対する具体的な原因と対策が紹介されていることが多いです。私のブログも含め個人ブログなどでは「こういう場合はこうする」といった簡単な対処例が紹介されることが多いです。例えば、⁠SQLインジェクション対策にはプリペアードクエリだけを使えば大丈夫」などです。

読者の方もセキュリティ対策はどうすればよいのか、具体的な対策⁠だけ⁠を知りたいと考えている方も多く、

Xの場合はYを行う

のような例外を無視したセキュリティ対策が広く流通しているのが現状です。Xの場合はYを行う、といったセキュリティ対策の解説は解りやすいですが問題も多いです。

例えば、⁠SQLインジェクション対策はプリペアードクエリだけ使えば大丈夫」との解説は間違ってはいませんが完全ではありません。人気の高い組み込み型データベースのSQLiteにはプリペアードクエリがありません。

またいくらプリペーアードクエリをサポートするデータベースサーバでプリペアードクエリを使っていても、

SELECT * FROM $_GET['table'] ORDER BY $_GET['order'];

のようなクエリを書いてしまってはSQLインジェクションを防ぐことはできません。⁠SQLインジェクション対策はプリペアードクエリだけ使えば対丈夫」は完全な対策ではないのですSQLインジェクションの回を参照⁠⁠。

JavaScriptiインジェクションも同じです。⁠GET, POSTで渡された<, >, ", &」だけエスケープすればよいとだけ解説されている場合が少なくありません。この連載で解説した通り、これだけではまったく不十分ですJavaScriptインジェクションの回を参照⁠⁠。

完全なJavaScriptインジェクション、SQLインジェクション対策を行うには、インジェクション攻撃の原因と対策の基本を正しく理解する必要があります。個々のセキュリティ問題を理解するこも重要です。しかし、もっと重要なことはセキュリティ維持に不可欠な基本的な概念を正しく理解することです。

現実世界でのセキュリティ対策

現実の世界でセキュリティ対策を行う場合はどのような対策が取られるか考えてみましょう。多少現実的なリスクが無いと話にならないので、アメリカの高校で実際に行われているセキュリティ対策を例に考えてみましょう。

入り口での対策

コロンバイン高校での銃乱射事件を覚えている方もいると思います。事件後、多くの高校でセキュリティ対策として危険物の持ち込みをチェックするガードマンと銃やナイフなどの危険物を検知するための金属探知機を入り口に設置しました。ガードマンはカバンの中身までチェックし、金属探知機に反応した生徒は身体検査をされるようになりました。

もし、学校への入り口でセキュリティチェックをしないのであれば教師がクラスで危険物の確認を行うことになるでしょう。しかし、教師がクラスの生徒が危険物を持ってきているか確認することは非効率です。

金属探知機を各クラスに設置するのは無駄ですし、女生徒の身体検査をするために男性教師には必ず女性の補助が必要になります(逆も必要かも知れません⁠⁠。しかも、教室内でセキュリティチェックを行ったのでは、学校内で暴力事件を起こそうとしている生徒に対する抑止力になりません。

現実の世界では危険物や危険人物を除去するため、入り口でしっかり安全性をチェックすることが当たり前に行われています。中に重要な情報や人物が居れば居るほど、つまりリスクに対する損失が大きければ大きいほど入り口でのチェックは厳しくなります。

出口での対策

セキュリティ対策は入り口だけで行われるものではありません。現実のセキュリティ対策では外に漏れては困るものや漏れると都合が悪いものがでないよう出口でチェックすることは普通に行われています。衣料品で「検針済み」という表記を見た覚えのある方も多いと思います。衣料品などは誤って針が残っていないかすべての製品で検針している場合がほとんではないでしょうか? 食品でも誤って機械の破片などが入っていないかチェックする仕組みを導入している場合もあります。

衣料品に針、食品に金属などのチェックのように全量検査は行っていなくても、サンプルを抜き出し安全に出荷できるか規格にあった製品であるかチェックする品質管理は一般的に行われています。

会社や施設内での対策

一般の会社でもサーバルームへは権限を持った社員だけが入室できるようセキュリティ対策が取られているはずです。遺伝子組み替えを行うような研究施設では遺伝子を組み替えた生き物が外部に流出しないようセキュリティ対策をとっています。

出入口におけるセキュリティチェックに加えて、内部でもさらなる入退室の管理を行っていることが多くあります。これは会社の社員や研究所の研究員という分類からさらに細分化されたセキュリティ要件に合わせるために、追加の入退出管理を行わないと安全性を維持できないからです。

トラストバウンダリの管理がセキュリティ対策の基本

実世界ので出入口での管理や入退出管理の考え方は、プログラマがアプリケーションのセキュリティ管理を行う際の考え方に利用できます。しかし、残念ながら実世界のメタファを利用したセキュリティ管理が広く浸透していると言えない状況ではないでしょうか?

現実の世界と同じくコンピュータアプリケーションの場合もセキュリティ対策で最も重要なのはトラストバウンダリの管理です。トラストバウンダリを日本語に訳すなら信頼境界線となるでしょう。名前の通り線を引き、危険物が混ざっている可能性があっても境界線を超える時にチェックし、危険な物が含まれている物とそうでない物を区別する、トラストバウンダリ管理が実世界でもアプリでもセキュリティ対策の基本になります。

拙著Webアプリセキュリティ対策入門ではトラストバウンダリという用語は使っていなかったと思います。その代わりに徹底した入力と出力のチェックを行うようにお薦めしています。食品工場の製品に虫が混入したり、縫製工場の衣料品に針が混入しないようにするため、これらの工場で入り口と出口での徹底した検査が有効であることは議論の余地はないでしょう。プログラムも同じで徹底した入力と出力の管理が安全対策に欠かせません。

プログラムにおけるセキュリティ対策とトラストバウンダリ

トラストバウンダリのコンセプトは非常に単純かつ明快です。しかし、この基本が正しく理解されていないことが多いのです。例えば、数年前に某大手ベンダーがセキュアプログラミングの動画を公開し、トラストバウンダリを使ってどのように安全性を確保するか解説していました。その動画ではなんとトラストバウンダリの中にデータベースが含まれており、前提条件無くデータベースが信頼できるシステムとして扱われていました。

図1 間違ったトラストバウンダリの考え方
図1 間違ったトラストバウンダリの考え方

この連載の読者の皆さんの多くは、データベースを「信頼できる」ものとして取り扱ってはならないことは十分理解していると思います。しかし、数年前まではセキュリティの専門家であってもこのような基本的な間違いをしていました。これでは何時まで経ってもSQLインジェションが無くならないのも当然です。

信頼できる、安全である、と確証が取れたもの以外はすべて信頼できないと考えるのがトラストバウンダリの考え方の基本です。

図2 正しいトラストバウンダリの概念
図2 正しいトラストバウンダリの概念

現実の世界でも同じですが、バウンダリの中にあるからといってすべて信頼できる人物や物ばかりではありません。食品工場であっても、殺虫剤のように危険な物を意図的に入れる場合もあるでしょう。危険物であっても正しく使えば有効な物を持ち込まなければならないケースがあることは実世界でもアプリケーションでも同じです。

例えば、かなり厳重に入力チェックをしているWebアプリでもクッキーをバリデーションしていないアプリは沢山あるでしょう。バリデーション済みのデータが「入力」としては問題がなくても、ブラウザやSQLデータベースに安全に出力できないデータも沢山あります。クッキーはセッションIDのみに使用している場合など、クッキーはチェックする意味があまりないため、あえてチェックしていないことが多いです。

厳重に入力チェックをしているはずアプリでも、危険なデータや危険かも知れないデータやプログラムもバウンダリの中に含まれている場合があることを忘れてはなりません。常に「データのソースはどこなのか?」⁠データは安全なのか?」を意識しながらプログラムを作らないと本当に安全なアプリは作れません。

実際のバウンダリ管理

図2にはライブラリやデータファイルなど一般的には「信頼できる」と考えられているものも含まれいます。そこまで信頼しなかったら何も作れないのでは? と思われた方もいるでしょう。

確かに何も信頼しなかったらシステム開発はできません。しかし、確認もせず、信頼できる状態も維持しないで無闇に信用しては、セキュリティを維持できません。信頼できるよう確認したり、信頼できる状態を維持してバウンダリ(境界)を管理する必要があります。

先程の図には信頼できない物としてOSが入っていません。これは「通常のシステム管理を行っていれば、OSに脆弱性がない状態で不正なプログラムは入っていない」という前提があるからです。実際にはルートキットなどの不正なプログラムがOSに組み込まれている場合、すべての入力どころか、プログラム中で書き込んだメモリでさえ信用できなくなります。このようなリスクは現実に存在しますが、これを考慮したシステム開発は不可能です。現実のトラストバウンダリの管理では、最低限必要なセキュリティ対策は取られており、安全性が維持されていることを前提にアプリケーション開発を行います。

既に述べたように、図2にOSは信頼できる状態に維持されていると「仮定」してあえて入れませんでした。OSが信頼できない状況は現実に起こり得るのですが、OSさえも信頼できないことを想定してのシステム開発無理です。しかし、先程の図にはライブラリやWeb開発フレームワークは入れています。これは実際にリスクがあり、アプリケーション開発者の対処が必要であるので載せています。

例えば、PythonのWebアプリ開発フレームワークのDjango 1.0系ではREMOTE_ADDRにX-FORWORDED-FORに設定されたIPアドレスがセットされます。これはリバースプロキシが導入された環境を考慮した仕様と思われますが、この仕様により簡単に外部のユーザがIPアドレスを偽装できてしまいます。新しいDjango 1.1以降ではこの仕様は削除されましたが、1.0系では修正されませんでした。Django 1.0系のWebアプリをメンテナンスする場合、この仕様を知らなければ大きなセキュリティホールを作る可能性があります。

「最低限必要なセキュリティ対策が取られている」ものを拡大しすぎて解釈してならない別の例として、文字エンコーディングベースの攻撃に対する脆弱性を紹介します。PythonのPostgreSQL用のAPIには文字エンコーディングベースのSQLインジェクション対策用のAPIが用意されていませんでした。PostgreSQLが文字エンコーディングベースのSQLインジェクション対策用のAPIを用意してから約5年遅れて脆弱性に対応したAPIが用意されました。XMLライブラリも文字エンコーディングベースの脆弱性が発見されてから何年も遅れて対策がとられています。

文字エンコーディングベースの攻撃手法は正しい文字エンコーディングが利用されていることを確認すれば防げる攻撃です。一部不備が後に発見され修正されましたが、PHPのmbstringモジュールは文字エンコーディングベースの攻撃に対する対策をいち早く取り入れ、脆弱性があるライブラリやAPIであってもWebアプリ内でmb_check_encoding関数を使用することにより攻撃できないように利用することが可能でした。

XML関連ライブラリ等の文字エンコーディングベースの脆弱性は最近でも時々見つかっています。いい加減なデータをライブラリや関数やクラスに送信しても、適当に正しく処理をしてくれる、と考えるのは脆弱性の原因になります。

正しい出力の重要性

最近はフレームワークの普及もあって、入力を厳格にチェックするアプリケーションも増えてきました。しかし、入力チェックに比べあまり重要視されていないのが出力の安全性です。安全性と言うよりは出力の正しさ・正確性・妥当性と言ったほうが分かりやすいかもしれません。WebシステムはWebアプリだけで構成されていることはありません。データベースやメールを使わないシステムであっても、必ずWebブラウザがあります。

出力したデータを不具合なく処理するのは受け手側の責任だ、と考えることは間違いです。縫製工場が「服に針が入っていないか確認するのは消費者の責任だ」と言うことと変わりありません。

Webアプリは、Webアプリが出力するデータで周辺のシステムが問題なく動作する「安全なデータ」を出力する責任があります。WebブラウザはHTTPヘッダ、JavaScrirpt, CSS, HTML, XML、リレーショナルデータベースはSQL文、メールはメールヘッダが誤って解釈されないようデータを出力しなければならないのです。別の言い方すると、プログラマは出力先のシステムでプログラマが意図しない誤作動が起きないように出力を制御する責任があります。

正しい出力の基本の一つ目は「出力先で誤作動を起こさないよう、出力先の仕様に合わせて変数はすべてエスケープすること」です。二つ目は「エスケープできないもの」は本当に安全であるか十分に確認した上でエスケープしないで出力することです。

HTMLもSQLもCSSもJavaScriptも「エスケープできないものを除いてすべてエスケープする」という方針でコードを書くべきです。こうすれば安全性のチェックが容易になる上、⁠整数値であるはずだからエスケープは不必要なはず」など、単純な誤解によるエスケープ漏れを防止することができます。

まとめ

セキュリティ対策を考えるとシステム開発効率が落ちる、とよく耳にします。しかし、通常のシステム開発の必須要件の一つはセキュリティ対策です。効率のよいセキュリティ対策は、結局システムの開発効率と品質を向上させるのです。トラストバウンダリ管理でやるべきことは以下の2つです。

  • 入力チェック: 正しいデータであるか十分チェックする
  • 出力チェック: 出力先で誤作動なく処理できる出力であることを保証する

厳格な入力と出力の管理、厳格なトラストバウンダリの管理は、入出力データの安全性確認を容易にし、システムの開発効率と品質を向上させます。

セキュリティ問題とされる問題の多くはトラストバウンダリ管理に失敗していることが原因です。分かりやすく応用範囲が広いコンセプトですから、新人開発者だけでなく経験者の方も「トラストバウンダリを管理する」という考え方に基づくセキュリティ管理を行ってはいかがでしょうか?

おすすめ記事

記事・ニュース一覧