アプリケーションのセキュリティと必要十分条件

(Last Updated On: 2018年8月3日)

セキュリティと必要十分条件については他のエントリでも書きました。以前書いたエントリではミクロの視点から単純な加算関数で考えました。今回はもう少し大きなマクロの視点、アプリケーションのセキュリティの必要十分条件を考えてみます。

論理的なセキュリティを考える為には必要なので書きました。ここに書いたことは読み物としては退屈かも知れませんが、重要な事だと考えています。

必要十分条件

まずは必要十分条件のおさらいです。

2つの条件p、qにおいて
pならばq (p⇒q)
が成り立つとき,
「pはqであるための十分条件」
「qはpであるための必要条件」
また
「p⇒qかつq⇒p、の場合は必要十分条件」

http://kou.benesse.co.jp/nigate/math/a14m0121.html

必要十分条件は2つの条件、pとq、が互いに同値であることを言います。

 

前提条件

コンピュータープログラムが正しく動作することを証明するのは簡単ではありません。現在でも、厳密にコンピュータープログラムが正しく動作すること証明する確立した方法はありません。

コンピュータープログラムは正しく動作するには、複数/複雑な条件が必要なので単純化して考えます。コンピュータープログラムは、プログラムの大小を問わず、基本的に

  • 入力 → ロジック → 出力

の構造を持っています。関数もアプリケーションも同じです。この基本構造のみを考えることとします。

入力、ロジック、出力はそれぞれ入力処理、ロジック処理、出力処理以外の処理は行わないものとします。

  • 「正しい入力」とは、入力のコンテクストに対して、妥当な入力データとします。
  •  単純化の為「処理」は正しいものとします。
  • 「正しい出力」とは、出力のコンテクストに対して、妥当な出力データとします。

「正しい入力」であることを保証するにはISO27000などで推奨している入力バリデーションを行います。単純化の為にユーザーによる入力ミスはないものとします。

備考:”入力バリデーションエラー”と”入力ミスによるエラー”は根本的に異るモノですが、混同しているケースが多いので注意してください。”入力バリデーションエラー”は”仕様上あり得ないエラー”、”入力ミスによるエラー”は”仕様上あり得るエラー”です。

「正しい出力」を行うには出力対策の3原則

  • エスケープ
  • エスケープが必要ないAPI
  • バリデーション

の何れかを行いますが、ここでは3番目の「バリデーション」は単純化の為に利用しないものとします。

本来入力データのバリデーション処理は入力処理のコードが行うべきモノです。これは入力処理時には”入力データのコンテクスト”が明白に分かるからです。構造化できていないスパゲティーコードの典型例になりますが、入力データのバリデーション処理を出力時に行う、といった事も可能になるので出力処理ではバリデーションを利用しないことにします。

今回は例として、student テーブルのidカラム(テキスト型)を利用します。idカラムは、何でも構わないのですが、”英字一文字”+”数字9桁”とします。

実際のアプリケーションではもっと複雑ですが、ここまで単純化しても基本的な概念とその有用性を理解するには十分だと思います。

 

セキュアでないアプリケーション

まずは必要十分条件として成り立たないケースを考えます。現在構築されてるほとんどのアプリケーションは「入力の正しさ」は保証せずに、「正しい出力」を行うことにより安全性を維持するよう設計されています。

  • 入力バリデーションは行わず、出力のコンテクストに対して、不正な動作を行わない出力データとなるようエスケープかエスケープが必要ないAPIを利用する

このような考え方でセキュリティアーキテクチャーが成り立つか考えてみましょう。

アプリケーションの条件、pとqは以下とします。

  • p : アプリケーションの動作が正しい
  • q : 出力が正しい

条件p、「アプリケーションの動作が正しい」場合、アプリケーションはセキュアであると言えます。条件として問題ありません。

条件qの「出力が正しい」とは「出力コンテクストに対して出力してもインジェクション攻撃などの”誤作動”をしない出力」とします。実はこの条件設定自体が怪しい設定ですが、ここでは眼をつむります。「出力が正しい」場合、アプリケーションはセキュアであると言えるとします。

p ⇒ q は

  • ”アプリケーションが動作が正しい”ならば”出力が正しい”

これは真です。正しい動作をするアプリケーションの出力が正しい出力であることは自明です。

必要十分条件となるには q ⇒ p が真である必要があります。

  • ”出力が正しい”ならば”アプリケーションが正しい”

話しを単純化する為に、出力対策の3原則の”バリデーション”は利用しない、としていることを思い出しましょう。エスケープかエスケープが必要ないAPIしか出力に使えません。例えば、SQLインジェクションの場合で以下のようなクエリ

INSERT INTO student (id, name) VALUES  ($id, $name);

※ id、nameカラムともにテキスト型

の$idにSQLインジェクションの典型的な攻撃パターンである

"'A1234', 'foobar'); DROP TABLE mytable; --"

が設定されたとします。

攻撃用文字列を含む$idはエスケープまたはプリペアードクエリによって無害化されるので、SQLインジェクションはできません。

しかし、student.id に “’A1234′, ‘foobar’); DROP TABLE mytable; –” が保存されてしまうような出力が”正しい出力”と言えるでしょうか?普通は正しくありません。ここでも”数字一文字+数字9桁”がidカラムの形式の仕様なので正しくありません。

よって

  • ”出力は正しい”ならば”アプリケーションが正しい”

は偽となり、”出力は正しい”は”アプリケーションの動作が正しい”であるための必要十分条件ではありません

q ⇒ p が偽であることを証明するには反証が1つあれば十分ですが、ソフトウェアが正しく実行される、といった複雑なモノの証明は簡単ではありません。1つ反証があるから、といって完全に否定する訳けには行きません。しかし、モデル(条件)を比較し評価することは可能です。このモデルでは、不正なSQLを実行するSQLインジェクション攻撃はできなくても、簡単に不正な出力を行えます。例えば、”壊れた文字エンコーディング”や”制御文字を含む文字列”、”異常に長い文字列”などあらゆる不正な出力が可能です。

多少無理矢理な前提条件

条件qの「出力が正しい」とは「出力コンテクストに対して出力してもインジェクション攻撃などの”誤作動”をしない出力」とします。

も入っています。これはインジェクションが行えなくても”不正な出力”は不正です。しかし、実際にほとんどのアプリケーションは、この多少無理矢理な前提条件も入れつつ、”可能なかぎりセキュア”なアプリケーションを作ろうとしています。

不正入力が”ロジック”で処理されると、”不正入力を想定していないコードがどのような動作になるか判りませんし、不正な出力が行えるソフトウェアからの出力で、出力先のシステムがどのような動作になるか判りません。これでは何時まで経っても、どんなに頑張っても、満足できるレベルのセキュアなアプリケーションが作れないのは仕方ありません。

もし満足できるレベルのアプリケーションが作れたとしても、次の「セキュアなアプリケーション」のモデルに比べると、少しのミスやライブラリの脆弱性で簡単に問題が発生するモノになってしまいます。

 

セキュアなアプリケーション

今回は必要十分条件として成り立たつケースを考えます。セキュアコーディング/セキュアプログラミング/契約プログラミングで用いられる条件です。セキュアコーディングの第一の対策は入力バリデーションです。出力の無害化は重要ですが、入力バリデーションより下位とされています。

アプリケーションの条件、pとqは以下とします。

  • p : アプリケーションの動作が正しい
  • q : 入力および出力が正しい

前の例と同じ条件p、「アプリケーションの動作が正しい」場合、アプリケーションはセキュアであると言えます。条件として問題ありません。

条件qの「出力が正しい」とは「出力コンテクストに対して出力してもインジェクション攻撃などの”誤作動”をしない出力」とします。これは前の例と同じです。「入力が正しい」とは「入力コンテクストに対して妥当な入力」とします。既に書いた通りISO27000などで推奨している入力バリデーションを行うとします。

p ⇒ q は

  • ”アプリケーションの動作が正しい”ならば”入力および出力は正しい”

これは真です。正しい動作をするアプリケーションの出力が正しい出力であることは自明です。これは前の例と同じです。

問題は、q ⇒ p が真であり、必要十分条件となるか?です。

  • ”入力および出力が正しい”ならば”アプリケーションが正しい”

話しを単純化する為の、出力対策の3原則の”バリデーション”は利用しない、はここでも有効です。エスケープかエスケープが必要ないAPIしか出力に使いません。例えば、SQLインジェクションの場合で以下のようなクエリ

INSERT INTO student (id, name) VALUES  ($id, $name);

※ id、nameカラムともにテキスト型

の$id用の”入力”にSQLインジェクションの典型的な攻撃パターンである

"'A1234', 'foobar'); DROP TABLE mytable; --"

が設定されたとします。

ここではstudent.idの形式が”英字一文字”+”数字9桁”で構成される仕様です。この場合、$idとなる入力データはstudent.idのコンテクスト、つまり”英字一文字”+”数字9桁”の形式であることがバリデーションされます。それ以外の入力データはアプリケーションに受け入れられません。正しいデータがのみが保存され、不正な入力データを含む場合はプログラムの実行が入力処理で拒否されます。

出力対策は入力対策とは独立した対策として実行することになりますが、これは出力の正しさには影響ありません。

  • ”入力および出力は正しい”ならば”アプリケーションが正しい”

は真となり、”出力は正しい”は”アプリケーションの動作が正しい”であるための必要十分条件となります

ソフトウェア実行の正しさを証明することは難しいです。このため、ここで利用している前提条件(処理は正しい、出力バリデーションは行わない)で、この例の条件q(”入力および出力は正しい”)の場合でも反証となるケースがあります。

しかし、この例の条件の場合の方が反証となるケースがかなり少なくなることは明らかです。明らかでない、と思える場合はそれぞれの条件について反証となる事例をリストアップしてみると良いです。

 

問題設定の誤りと「手近な情報による誤謬」

「セキュアでないアプリケーション」の例のような間違いが起きる原因は、人の認知バイアスです。

手近な情報による誤謬
「人々はデータではなく、強い印象を受けたことや、最初に頭に思い浮かんだ事柄で物事を判断する」

「人間がリスクを正しく判断できない一つは過信であり、もう一つは将来のあらゆる可能性を徹底して検証する能力の欠如だ」

不合理 誰もがまぬがれない思考の罠100 – スチュアート・サザーランド

不正なコードを実行できる脆弱性対策を考える時には「問題が発現した箇所に目が行き」、(不十分でも)問題に解決できると判った対応策がある、と場合は強い印象を受けるのは当然でしょう。”解決できる対応策”ある場合、もっと他に良い方法があったとしても、他の方法のメリットを検証しないで判断することはよくある事です。

 

セキュアなアプリケーションアーキテクチャー

ソフトウェアセキュリティの専門家が第一番のセキュリティ対策としている

  • 入力バリデーション

をアプリケーションの信頼境界線で行うモデルの方がセキュアなアーキテクチャーだと言えます。

CERTやSANSが第一番目のセキュリティ対策としている「入力バリデーション」が論理的かつ効果的であるのは当たり前です。しかし、残念ながら多くの開発者に十分理解され、受け入れられ、実装されている、という状況とは到底いえないのが現状です。

CERTは7番目の対策(セキュアコーディング習慣)として「出力の無害化」を挙げています。これは、多くの開発者が「出力対策に偏重したセキュリティアーキテクチャーを採用している現状」を踏まえた上で、敢えて7番目という低い順位に設定しているのではないかと考えています。

ゼロトラストで考え、契約プログラミングを実践すると、自動的にセキュアなアーキテクチャー(多重の信頼境界での防御)ができるようになります。入力データのコンテクスト、出力データのコンテクストの重要性も自然に理解できるようになります。契約プログラミングはお勧めです。

OWASP TOP 10もWebアプリケーションセキュリティガイドラインとして広く使われているガイドラインです。2017年度版からは、アプリケーションが入力バリデーションを行っていないと対策できない「不十分な攻撃防御」(Insufficient Attack Protection)が追加される予定です。「不十分な攻撃防御」とは、連続した攻撃を放置することなどを脆弱性とし、連続した攻撃が発生した場合、ユーザーのログアウト/IPアドレス・IPアドレスブロックの遮断などを対策をとることを求めています。

WAFでも実現可能ですが、アプリケーションレベルで実行可能な防御に比べると、かなり精度が劣るレベルの防御にならざるを得ません。そもそもOWASP TOP 10はWebアプリケーション開発者向けのガイドラインなので、アプリケーションに実装ではなくWAF導入ありき、で作られたガイドラインではないです。「不十分な攻撃防御」脆弱性はアプリケーションで対応すべき問題として捉えるべきです。

 

まとめ

入力バリデーションは最も重要なソフトウェアセキュリティ対策ですが、稀に「入力バリデーションは内部の脆弱性を隠すので良くない」といった見当外れの意見を聞きます。バリデーションによる攻撃可能面(Attack Surface)の削減が本当に良くないと思うのであれば、今すぐネットワークファイアーウォールを無効にしてインターネットに接続してどうなるか試してみるべきです。入力バリデーションをしないで得をするのは開発者でも利用者でもありません。セキュリティ診断業者か攻撃者が得をするだけです。

必ずしも特定の論理的なアプローチが問題に対して最適解であるとは限りません。特にITセキュリティ維持のように高い複雑性を持つ問題の場合、原則が常に最適解とは限りません。

例えば、ソフトウェア開発環境のセキュリティ維持に「一つ一つのソフトウェアを理想に近い形でリスク管理(セキュリティ管理)する」ことは非現実的です。ロクに調査も検証もしていないで”第三者のコード”を使ってみることも多いでしょう。この状況は”エンドユーザーが思い思いにインターネットからプログラムをダウンロードし、インストールして実行する”状態とあまり変わりません。

論理的/理想的には開発環境での利用だけでも、コード検査を行い、隔離されたテスト環境で十分にテストし、その後に利用すべきです。一旦、コード検査とテストを行ったプログラムでも、更新があるたびにコード検査とテストを行うべきであることも明らかです。しかし、これは現実的ではありません。ここでは紹介しませんが、開発環境には別のアプローチでセキュリティ対策を行うことになります。

ソフトウェア開発のセキュリティに関しては、歴史的な経緯からバッドノウハウが溜まっていて、それが継承され、当たり前になっている状況です。さらにセキュリティに詳しいと言われている人には、脆弱性には詳しくても、ソフトウェアセキュリティやセキュリティアーキテクチャーに詳しくない人も沢山いて、明らかにベストプラクティスに反するような主張をする方も少くありません。

これでは一般の開発者が混乱するのは当然でしょう。今では当たり前になった構造化プログラミングも定着するまで10年以上の時間が必要でした。ITセキュリティの向上が急務と言われてから30年近くになりますが、未だに「セキュアなソフトウェアアーキテクチャー」の基礎も理解されていない現状はもどかしい限りです。このエントリが「セキュアなソフトウェアアーキテクチャー」とはどんな物か?考える機会になると幸いです。

 

投稿者: yohgaki