とあるネットワーク技術者の防御法

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

今回は「とあるネットワーク技術者がいかにしてネットワークシステムのセキュリティを守っているのか?」という話です。

「え?!」と思うハズですが、最後までお読みください。

「ネットワークシステムのセキュリティを守る、とあるネットワーク技術者」では長過るので、以後「技術者A」とします。

※ このブログの本題はアプリケーションセキュリティです。ネットワークセキュリティの話は反面教師です。
※ 書きかけです。図はまた時間がある時に書きます。

論理、原則と方法

システムを論理的に防御するには、その論理が必要です。技術者Aは論理構築からはじめました。

安全なシステム構築の”論理”

技術者Aは、脆弱性がある限り安全なシステムに成り得ない、この点に着目し、以下の”論理”に至ります。

  • 安全なシステム → 脆弱性が無いシステム

論理的な正しさ(必要十分条件を満たしているか)は p → q の反対、q → p が成り立つ事で確認できます。

  • 脆弱性が無いシステム → 安全なシステム

脆弱性が1つも無いシステムが安全であることには間違いありません!

間違いようがありません。”論理”として完璧です。

安全なシステム構築の”原則”

技術者Aは”安全なシステムの論理”から次の”原則”を導きました。

  • 全ての脆弱性は廃除/修正されなければならない

脆弱性を残せば安全なシステムではなくなります。論理的に全ての脆弱性を廃除することが必要であることは間違いありません。

脆弱性問題に対して、問題発生箇所から遠い場所で対応したつもりだった、といった事があると困ります。そこで、技術者Aは次の”原則”も採用します。

  • 脆弱性は発生箇所に近い場所で完璧に廃除/修正しなければならない

ネットワークシステム上に脆弱なデバイスがある場合、そこからセキュリティ問題が拡大します。技術者Aは、脆弱性の発生源を全て断つ必要があることから原則を導きました。

上記の2つ”脆弱性が無いシステム”の”原則”として十分です。

安全なシステム構築の”方法”

次に技術者Aは”脆弱性が無いシステム”を目指す”方法”を考えます。

脆弱性が無いシステムにするには、全ての脆弱性を1つ残らず廃除しなければなりません。そこで、技術者は安全なシステムを作る、以下の”方法”を採用します。

  • 脆弱性が無いシステム
    → 脆弱性1つ残らず脆弱性を廃除/修正する
    → 一つずつ全ての脆弱性を廃除/修正する

脆弱性を1つでも残してしまうと安全なシステムになりません。一つずつ全て廃除/修正するまでには時間がかかりますが、”原則”として「全ての脆弱性は廃除しなければならない物」です。

  • 方法1:一つずつ全ての脆弱性を廃除/修正する

この”方法”にも間違いはありません。既に考えた”原則”と同じであり、この方法が誤りであることは、原則に照らし合わせてあり得ません。

脆弱性は無限にある訳ではなく有限です。一つずつ廃除/修正していけば必ず全ての脆弱性を廃除/修正でき安全になる、と技術者Aは考えたのです。

効率的に「一つずつ全ての脆弱性を廃除/修正する」には、原則である「脆弱性は発生源から完璧に廃除/修正しなければならない」を守る必要がある、と技術者Aは考えました。

”発生源”を修正しない限り脆弱性は無くならないので、安全にはなりません。発生源が遠く離れた場所で行う小手先の防御などではなく、”発生源”から対処しなければなりません。多少の手間は必要でも、できる限り完璧に発生源から修正/廃除が必要である、と技術者Aは結論します。

「脆弱性は発生源から完璧に廃除/修正しなければならない」は”原則”ですが、そもまま”方法“にもなります。

  • 方法2:脆弱性は発生箇所に近い所で完璧に廃除/修正する

原則”のままの、解りやすい”方法”であり、これが間違っていることはあり得ない、と技術者Aは考えました。

※ ここまでで「こんな設計あり得ない」と思った方も多いはずです。しかし、こんな設計のモノが世の中には沢山あります。最後までどうぞ!

安全なネットワークシステムの実装

安全なネットワークシステムの”論理”、”原理”と”方法”は非の打ちどころなく出来上がった、と技術者Aは満足します。後は設計/実装するだけです。もう一度、”論理”、”原理”と”方法”を確認します。

論理

  • 脆弱性が無いシステム → 安全なシステム

原則

  • 全ての脆弱性は廃除/修正されなければならない
  • 脆弱性は発生源から完璧に廃除/修正しなければならない

方法

  • 一つずつ全ての脆弱性を廃除/修正する
  • 脆弱性は発生箇所に近い場所で完璧に廃除/修正する

これらを使えば完璧に安全なネットワークシステムを構築できるはずだ、と技術者Aは自信を持ちます。

ネットワークセキュリティ設計

技術者Aは次の図のようなアーキテクチャー(構造)のネットワーク設計を行いました。

(ネットワーク構成図を入れる予定)

ネットワーク全体を防御する全社レベルのファイアーウォール、各部署のサブネットワークを防御する部署レベルのファイアーウォール、ネットワークに接続されたデバイスレベルのファイアーウォールを作ります。

更にシステムとしての安全性を維持するにはデバイスで動作するソフトウェアのセキュリティも欠かせません。そこで、各デバイスのソフトウェア全ての脆弱性も

  • 一つずつ全ての脆弱性を廃除/修正する
  • 脆弱性は発生箇所に近い所で完璧に廃除/修正する

これらの”方法”を適用する、としました。

正しい原理、正しい原則に基づいた方法を適用しているので、確実に安全なシステムが構築できるハズです。

技術者Aはそれぞれのファイアーウォールの役割を、正しい原理、原則と方法に基き次の様に定義しました。

全社レベルファイアウォールの役割

技術者Aが考えた原理、原則と方法を適用すると、全社レベルのファイアーウォールはあまり重要ではありません。「脆弱性は発生箇所に近い所で完璧に廃除/修正する」モノだからです。

  • 全てのパケットを確実にサブネットワークに届ける

これしか役割はありません。在っても、無くても良いような存在であり、単純なルーターで十分です。

部署レベルファイアウォールの役割

部署Aは部署Bのネットワークに◯◯プロトコルを使ってアクセスできてはならない、といったビジネスルールをネットワークに実装する必要があります。部署レベルのファイアーウォールではビジネスルールに合ったフィルタリング処理が必要です。

「脆弱性は全て一つずつ廃除/修正する」モノなので、技術者は次の図ようにブラックリスト型で対応することにしました。

(ネットワーク構成図を入れる予定)

デバイスレベルのファイアウォールの役割

「脆弱性は全て一つずつ廃除/修正する」モノなので、ネットワークの最深部/エンドポイントとなるデバイスレベルのファイアーウォールが最も重要です。

  • 壊れたパケット/攻撃用パケットの破棄
  • デバイスにアクセス可能なネットワーク機器のアクセス制御

これら確実に行なえばネットワークの安全性も確実に確保可能です。

デバイスレベルのファイアーウォールの役割と責任は大きいのですが、ソフトウェアのセキュリティも欠かせません。「脆弱性は発生源から完璧に廃除/修正する」モノなので、最終的な安全性確保の役割と責任にネットワーク経由のデータを処理するソフトウェアにあるからです。

ソフトウェアにある脆弱性はソフトウェアの責任として、「脆弱性の発生源(ソフトウェア)から完璧に廃除/修正」しなければなりません。

顧客への自信作のネットワーク設計を提案

技術者Aは自信を持ってこの完璧なネットワークセキュリティ設計を顧客に提案しました。顧客は「こんな設計を正気で持ってきたのか?!契約は打ち切りだ、帰れ!」と怒ってしまいました。

技術者Aは「これだからセキュリティを論理的に考えられない馬鹿な顧客は相手にできないよ」と結論するのでした。技術者Aの”完璧”なセキュリティ設計は何時までも続く、顧客のある限り。。

※ 「こんなネットワーク設計をする技術者なんて一人もいない!」と思うかも知れません。しかし、90年代後半にファイアウォールの導入が進んだ頃のファイアウォールMLでは、信じられないかも知れませんが、ほぼこのままのネットワーク設計こそが正しい!とする技術者が実際に居ました

こんなネットワーク設計などあり得ない!

「こんなネットワーク設計などあり得ない!」と思った方が多いハズです。多いと言うよりほぼ全てのエンジニア全てが「あり得ないくらいダメ過ぎる設計だ」と考えるでしょう。その理由は以下のような物だと思います。

  • そもそも、入ってきてはダメなデータ、をネットワークに入れてはならない。(出してもならない)
  • ダメなデータを入れたり、出したりしないようにする役割と責任は基本的には”外側”のネットワーク – 会社、部署レベル – にある。(外側の境界で防御できるモノは可能な限り防御する)
  • デバイスレベルのセキュリティも必要だが、攻撃された時の「フェイルセーフ対策」であり、最初で最後の防衛線ではない。(デバイスレベルに攻撃データが到達した時点で半分くらいは負けの状態)
  • そもそも、全てのデバイスの脆弱性を廃除/対応する、は現実的には不可能(数が多い&変更が多い)

といった所ではないでしょうか?

しかし、このダメなネットワーク設計を”ソフトウェア設計”に置き換えると「そのまま」であるアプリケーションが数えきれません

よくある普通のアプリケーション設計

残念ながらごく普通にあるWebアプリケーション設計が今まで紹介してきた「こんなネットワーク設計などあり得ない!」と思えるネットワーク設計、そのままの設計になっています。

今ある普通のWebアプリケーションのデータの取り扱いは次の図のようなアーキテクチャー(構造)となっています。

(構成図を入れる予定)

今ある普通のWebアプリケーションでは、

  • ネットワークでいう所のエンドポイントである”データ出力”での対策に偏重している。データ出力の対策だけしかない物が多い上、出力先が対策していないなら出力先の脆弱性だとする。ダメなデータが入り放題かつ出放題。(ネットワークなら会社レベルのファイアウォールなし)
  • ”入力データ”の対策はあってもロジックレベル、最も入り口に近い部分での対策はほぼ無し。(ネットワークなら会社レベルのファイアウォールなし)
  • 入力データ検証コードはあってもその前にデータどう使われようが使ったコードの責任、出鱈目なデータでもそれを使った時のコードが対策していないことが悪い。(ネットワークなら全部エンドポイントのデバイスの責任にする)
  • データ検証が在ってもブラックリスト型、”Webフォーム”処理で使うデータだけ、それをロジック処理で行う。(ネットワークなら部署レベルのファイアウォール機能に頼る)
  • 明らかに不正なデータでも受け入れてサニタイズして使う。(ネットワークならパケットフィルタリングをブラックリスト型で構築)

もし、上記のような考え方/構造でネットワーク防御を行う!としたら、そのエンジニアはクビになりかねません。

しかし、こういった構造/設計を持つWebアプリケーションが当たり前一般的です。その理由は

  • 脆弱性が無いアプリ → 安全なアプリ

とする「原理」に基づいた

  • 一つずつ全ての脆弱性を廃除/修正しなければならない
  • 脆弱性は発生箇所に近い所で完璧に廃除/修正しなければならない

といった「原則」があります。

「こんなネットワーク設計などあり得ない!」といったお粗末な設計の原因となる考え方を、多くのソフトウェア開発者が「正しく、かつ効率的な考え方である」と認識してしまっているのです。

例えば、Railsアプリは脆弱なデータ処理に頼る構造になっています。Strong Parameterというパラメーターバリデーションは、データ内容の検証がありません。モデルに渡すパラメーターだけしかバリデーションしません。不正な入力データの処理は下層のクラス/メソッドに任されています。不正なパラメーターでもサニタイズを行いそのまま処理し攻撃があったことも判りません。サニタイズできないモノは例外が発生しますが、例外の発生が遅すぎます。ActiveRecord(ORM)で”データモデルのバリデーション”が可能ですが、多くのアプリで検証ロジックが不十分です。数値の範囲、文字列の長さのバリデーションさえなく、NOT NULLや参照整合性のチェックのみが普通です。”データモデル用のバリデーション”を厳格に行っていても、それは”アプリケーション入力のバリデーション”の代わりになりません。ActiveRecordを使わないモデルの場合もデータバリデーションが無かったり不十分で、下層のAPIに不正なデータ処理を任せています。

データとコード(機能)のセキュリティの切り分けが出来ていないことが原因です。

Railsのやり方がベストプラクティスだ、と思っている開発者は多い 1 と思います。しかし、データセキュリティの確保方法はアンチプラクティスの見本のようになっています。

Railsアプリを例にしましたが、他のWebアプリケーション/フレームワークも構造的に変わりがありません。

この状況の責任は開発者より”セキュリティ専門家”と言われる人達の責任の方が大きいと言えると思います。

脆弱なWebアプリとWebアプリファイアウォール(WAF)/Webアプリ脆弱性診断は最高のマッチング

外部からネットワークの脆弱性を行う場合に、全社レベルのファイアーウォールがない/部署レベルのファイアウォールも穴だらけの会社の診断は診断者(攻撃者)にとっては朝飯前の仕事です。かなり簡単に問題ヶ所を見つけられます。

Webアプリケーション開発者の1人として「最高のマッチング」と言われても全く嬉しくありませんが、脆弱なWebアプリとWebアプリファイアーウォール(WAF)/Webアプリ脆弱性診断は最高の相性であることは確かです。

ネットワーク設計で外側の境界防御がないと簡単に”内部”の脆弱性が攻撃できる事と同じく、アプリケーション設計で外側の境界防御がないと簡単に”内部”の脆弱性が攻撃できます。

  • Ruby/Python/PHPを使っていても、これらの言語のプログラムどころか、更に下層のC言語で書かれたライブラリの脆弱性まで攻撃できる
  • 関数の多くは契約プログラミングのようなデータ検証がなく(これは当たり前)、ちょっとした間違いで簡単に誤作動する(攻撃には”誤作動”が利用される)
  • 機能間の仕様の依存性と組み合わせが膨大になり、正常/インジェクション攻撃に脆弱なりやすく、DoS攻撃が容易になる。

ライブラリ仕様を設計し開発する場合、通常ライブラリを開発する場合は”汎用的”に作ります。汎用にするため”入力データ”に制限を設けないことが多いです、誤作動を起こすデータを含めて。

「そんな設計にするライブラリが悪い!」と思うかも知れませんが、複数DBをサポートするORMの多くが”SQL識別子”を安全に処理(無害化)できる仕組みを持っていません。持っている場合も”本来利用できるハズの識別子を使えなくする”といった形で実装しています。

何をしても誤作動しないようにするには、ライブラリの開発者は”汎用”にするのか/しないとか、”無駄”/”やり過ぎ”と言えるチェックをするのか/しないのか、といった難しい選択をしなければならなくなります。

Webアプリ脆弱性診断

(解説図を入れる予定)

脆弱な構造のアプリケーションを外部から攻撃することは、堅牢な設計のアプリケーションを攻撃することに比べると比較にならないほど簡単です。アプリケーション開発者が書いたコード以外の言語/フレームワーク/ライブラリ(最下層のC言語ライブラリ/コードまで!)の脆弱性まで発見(攻撃)できます。

開発者が脆弱な構造のアプリケーションでも”その設計でOK”と信じていれば

  • 凄いな、こんなところの脆弱性まで見つけられた

と称賛されます。その脆弱性はアプリケーションが出鱈目なデータを送ったことが原因であっても

  • アプリケーション開発者の問題でなく、問題のコードを書いた言語/フレームワーク/ライブラリが悪い

ので開発者の責任ではない、となります。アプリケーション開発者の気分も悪くなりません。更に診断者は”顧客のシステムを利用”して見つけた

  • ライブラリの脆弱性を報告すれば、脆弱性研究者としての名も上がります

脆弱な構造のアプリケーションは一石二鳥の都合が良いシステムです。

現在のWebアプリケーションは不正なデータによるインジェクション攻撃やDoS攻撃に簡単に脆弱になる構造を持っているので、Webアプリファイアウォールを売るのにも役立ちます。売っているなら一石三鳥です。

Webアプリファイアウォール

(解説図を入れる予定)

本来あるべきアプリケーション外側の境界防御がないので、Webアプリは構造的に脆弱になり、システム内の奥深くのライブラリの脆弱性まで攻撃できてしまいます。そして、その脆弱性は”アプリケーションの責任ではない”のでアプリケーション開発者は対応する必要がありません。しかし、脆弱性は残ります。

そこでWebアプリケーションファイアウォールが登場します。脆弱性はアプリケーションの責任ではない、とするとライブラリなどの脆弱性が修正されるまで一時的にブラックリスト型で防止するのがよい、となります。

脆弱な構造を持つアプリケーションを放置/推奨するのはITセキュリティ業ビジネスとしてはとても理にかなっています。

セキュリティ業者は丸儲けだが開発者も利用者も損をしているだけ

本来はアプリケーション外側の境界防御で防止できる攻撃/脆弱性をシステム内部のライブラリの責任にしても、システム開発者も利用者も何も得をしません。

脆弱な構造を持ったシステムは”正しく動作させること”が困難になるので開発コストもかかります。攻撃可能な脆弱性が出来やすい構造は損でしかないのは言うまでもありません。

脆弱性診断サービスやWebアプリファイアウォールを提供している人/会社がジョブセキュリティを目的に一石三鳥の

「脆弱な構造のアプリケーション構築を推奨している!」

とまでは思いませんが、実際に同じ人が脆弱な構造のアプリケーション構築を推奨し、Webアプリファイアウォールや脆弱性診断サービスを売っているケースを見ることがあります。

  • アプリケーションの入力バリデーションは脆弱性を隠すので良くない

と悪気なく言い、脆弱性診断を行い、Webアプリファイアウォールも販売しているのです。脆弱性を見つけて直す事がセキュリティ対策の目的になっているので、これでも何も問題ない、と考えているのだと思います。

IPAまでおかしなセキュリティ対策を推奨していたので、こういった製品やサービスを提供している人/会社でも、”被害者”の側面はある、とは言えるかも知れません。(脆弱な構造/設計を啓蒙していたら加害者でもありますが)

”攻撃データ”に着目したWebアプリ脆弱性診断やWebアプリファイアウォールを提供しながら、”攻撃データ”に着目した堅牢なアプリケーション構造を否定する思考には首をひねらざるを得ません。

アプリケーションの構造が堅牢になると、Webアプリ脆弱性診断やWebアプリファイアウォールの価値は低くなりますが、価値が無くなる訳でもありません。私も可能ならば両方使うことを推奨しています。

何が間違っていたのか?

技術者Aは「何を間違えた」のでしょうか?

その理由や間違いは、今までこのブログに何度も書いてきています。一番大切なのは

  • コンピューターは”正しい/妥当なデータ”でのみ正しく動作する

というコンピューターの動作原理を忘れない事です。セキュリティ対策の目的はITシステムが正しく動作して使える、使えるべき時には使える、信頼して使えるようにする事にあります。

”脆弱性を修正する”は1つの手段にしか過ぎません。これが目的化すると説明してきた通り、誰も得をしない脆弱な構造でも問題ない、と勘違いしてしまいます。

”脆弱性を全て修正する”といったブラックリスト型の対策ではなく、”コンピューターを正しく動作させる”とするホワイトリスト型の対策でなければ、情報セキュリティ対策は効果的に機能しません。

合成の誤謬

技術者Aの論理や原則は必ずしも間違ってはいません。間違っていない、というより「正しい事」を考えていました。少なくとも、論理的には誤りである、とは言えません。

しかし、局所的には正しい事でも、正しい事を積み重ねるだけでは間違ってしまうことがあります。これは「合成の誤謬」として知られている、間違えてしまうとなかなか抜け出せない罠のような勘違いです。

論理の仮定として”全ての脆弱性をシステムから廃除/対応できる”としたことが誤りです。”全ての脆弱性をシステムから廃除/対応”は現実的には不可能です。IoTの脆弱性を見れば明らかです。脆弱性はどんどん生まれます。

※ エンドポイントの脆弱性を直さなくて良い、という意味ではありません。いくら脆弱性を一つ一つ潰しても”脆弱性が無くなることはない“とする仮定でセキュリティを確保しないと機能しない、という意味です。

身近な情報による誤謬

ソフトウェアのセキュリティ対策が技術者Aのような構造/設計になっている大きな原因の1つは「身近な情報による誤謬」(不合理 – 誰もがまぬがれない思考の罠100)だと思われます。

身近な情報による誤謬とは、近くにある情報や人から得た情報に論理性/合理性が無くても信頼性が高いと勘違いしてしまう誤謬です。

ソフトウェアのセキュリティ対策が始まったのは90年代からで、本格的になってきたのは21世紀に入ってからです。既に10年分くらいのWebアプリソースコードがあり、そのコードにはあるべきセキュリティ構造(特に入力データのセキュリティ対策)がありませんでした。脆弱な構造のアプリケーションが再生産され続けたことが1つ。もう一つの大きな原因は脆弱性研究家と呼ばれる一部のセキュリティ専門家が「結局は脆弱性を修正しないと直らない」とする意見2が、先の「合成の誤謬」を生んでしまったことが原因だと思われます。

「既に動作しているコードがそこにある」「セキュリティ専門家も一番の問題はデータ検証ではないと言っている」こういった身近な情報が誤謬を生んだ可能性が高いです。

ISOやCERTはデータ検証が重要である、と提唱していても「身近な情報による誤謬」は”規模”が大きくなれば成るほど、修正が難しくなります。OWASPのセキュアコーディングガイドはこのガイドを「不可知論的な文書」、つまり「セキュアコーディングの本質は人には理解できない」とまで書いています。

ベストプラクティスが直ぐに開発者に浸透しない、といった事は過去にも起こっています。ダイクストラがGo To Statement Considered Harmfulとする論文を書き、Goto文を使わない構造化プログラミング(関数/メソッドを使ったプログラミング)を提唱しましたが当たり前になるまで10年以上の年月が必要でした。今なら、Goto文を不必要に多用したプログラムを書くと、プログラマ失格の烙印を押されてしまいます。しかし、当時は大論争になりました。

セキュアコーディングの概念と方法論はコンピュータサイエンティストの間では論争になっていません。当たり前過ぎる概念と方法論で異論の余地が無いからです。

手段と目的の勘違い

技術者Aが考えた手段

  • 一つずつ全ての脆弱性を廃除/修正する
  • 脆弱性は発生箇所に近い所で完璧に廃除/修正する

これらは情報セキュリティ対策の手段に過ぎません。手段をセキュリティ対策の目的とする方を少なからず見かけます。”手段”は”目的”になりません。”手段”を”目的”にすると、ほとんどの場合は失敗します。上手くいっているように見えても無理と無駄が少くありません。

脆弱性を一つ一つ潰していっても、隠れた脆弱性や新たな脆弱性は次々と生まれます。”脆弱性は存在する”と前提とする”目的”と”手段”(後述)を設定しないと無理と無駄が発生し、致命的なセキュリティ問題の原因となります。

手段である脆弱性修正が目的化した為、「入力バリデーションは脆弱性を隠すモノだから良くない」といった意味不明な意見も聞いた事があります。アプリケーションが正しく動作する為には、正しい(妥当な)データ、が必須であるのにデータ検証なしでセキュアなアプリケーションになるハズがありません。

※ 90年代のファイアウォールMLでも同じ議論をしている人が居ました。ホワイトリスト方式のフィルタリングは脆弱性を隠すモノだからファイアウォールはブラックリスト型で対策し脆弱なアプリが修正されるべきだ、としていまいました。今、こんな意見をネットワークセキュリティのコンテクストで話をしたら失笑を買うだけですが、ソフトウェアセキュリティでは割と多くの方に真面目に受け取られてしまう、という現場を見た事があります。(PHPカンファレンス2014)

セキュリティ対策の目的と手段

「脆弱性を一つ一つ対策&廃除する」これだけではベストプラクティスどころかアンチプラクティス 3です。脆弱性対策はセキュリティ対策の手段でしかなく、目的ではありません。

ISO 27000の定義に基づくセキュリティ対策の目的

  • 情報システムを許容可能な範囲内のリスクに管理して利用する

となります。

  • 脆弱性は発生箇所に近い所で完璧に廃除/修正しなければならない

基本となるセキュリティ対策でさえありません。脆弱性の原因となるデータはできる限り遠い場所から対策し、適切な場所でフェイルセーフ対策を行います。(セキュアコーディング原則の1と7

セキュリティ対策の基本論理/原則の無視

ITセキュリティ標準では「セキュリティ問題を全て廃除&対策できる」とする考え方でセキュリティ対策を行いません。その上で、防御策を行う防衛線を定義しそこでリスク対策を行いリスクを軽減/廃除します。

セキュリティ対策の原則は「境界防御」です。全ての問題を廃除できないとする前提から境界防御は複数の境界線で行います。つまり、外側の境界から複数の防御を行う「多層防御」が基本です。

ネットワークのエンドポイントとなるデバイスに「悪意のあるデータを送り放題」な状態にし、悪意のあるデータに対する対策の責任をそのデバイスに全て負わせる。

これが誤りであることと同じく、

アプリケーションでもAPIや外部のシステムに「悪意のあるデータを送り放題」な状態にして、悪意のあるデータに対する対策の責任をAPIや外部システムに全て負わせる。

は誤りです。このような作り方のネットワークシステムで必要な安全性を確保できないことと同じく、アプリケーションに必要な安全性も確保できません。攻撃者にとって「望ましい対応策」であり、ガバガバに緩いセキュリティ対策です。

ネットワーク対策でも、物理対策でも、最も重要なセキュリティ対策はたとえそれが完璧 4 ではなくても、最も外側の信頼境界線上での境界防御です。外側の境界防御が強ければ強い程、防御することが容易になります。

例えば、ネットワーク防御で最も強力な防御はネットワーク”境界”を完全に断ち切る、ネットワークからの切断です。物理防御なら銀行の金庫室にコンピューターを入れ物理”境界”を完全に断ち切り、物理的に隔離すると強力な防御になります。

アプリケーションもネットワーク的/物理的(これは人も含まれる)な脅威を完全に廃除できるなら、最強の防御となります。ソフトウェア的な防御は必要でさえありません5

しかし、現実的にはネットを切断し、金庫室の中で、全ての物理的脅威(メディアや人)を廃除してコンピューターを”有用”に利用することは不可能です。

ITシステムを利用するリスク(≒脆弱性)は完全に廃除できるモノではありません。ソフトウェアの脆弱性に限っても不可能です。ライブラリ/APIで全ての脆弱性を廃除できるモノではないですし、廃除すべきモノでもありません。仮に全ての脆弱性を廃除できたとしても、ソフトウェアは常に変更と入れ替えが行われるモノです。新たな脆弱性が無いことは保証できません。

脆弱性を一つ一つ対策&廃除する(そして究極的には全ての脆弱性を廃除し安全なシステムにする)

は「何時まで経っても達成不可能な手段(目的)」です。

脆弱性は発生箇所に近い所で完璧に廃除/修正しなければならない

は、ベストプラクティスもどきのアンチプラクティスです。

デバイスレベルのセキュリティも必要だが、攻撃された時のフェイルセーフ対策であり、最初で最後の防衛線、ではない。(デバイスレベルに攻撃データが到達した時点で半分くらいは負けの状態)

ネットワークセキュリティなら”出鱈目なデータ”がデバイスに到達しても”半分負け”くらいで済みますが、アプリケーションセキュリティのコンテクストでは”出鱈目なデータ”が出力されたなら”完全に負け”です。

アプリケーション開発者はどうすると良いのか?

アプリケーション、特に今のWebアプリケーションで決定的に足りていないセキュリティ対策は入力データのバリデーションです。

勘違いがないようにもう一度書きます。「アプリケーション開発者以外の開発者」、例えばライブラリ開発者は、必ずしも入力データバリデーションをする責任が”ありません”。詳しい理由はここでは説明しません。

先ずは正しく境界防御

ネットワーク/物理的なセキュリティ対策と同じく、ソフトウェアのセキュリティ対策も外側の境界から多層の境界を設けて防御します。ソフトウェアの境界防御では入ってくる全てのデータの検証および不正なデータの廃除、出て行く全てのデータの確実な無害化を行います。取り敢えずは入出力データだけでも十分な対策を行なえば、かなり安全性が向上します。

今あるほとんどのWebアプリは入力データのセキュリティ処理が無い/不十分です。これではコードの内部奥深くで、スクリプト系の言語が利用するC言語で記載された機能にまで、簡単に誤作動を起こさせることが可能です。不正な入力データで誤作動しないことを保証するのは簡単な事ではありません

ソフトウェアのセキュリティ対策を極端に単純化した、出力時の脆弱性を一つ一つ出来る限り完璧にし、入力データ検証もできる限りソフトウェア内部の処理で行う、という考え方で開発すると攻撃者の手助けをしているようなものです。

境界防御はできる限り遠いところから始める、データセキュリティの確保をできる限り遠い所から始める、これが基本です。

Webアプリなら、データセキュリティの確保はブラウザのJavaScriptアプリから始めます。セキュアコーディング原則1の”入力をバリデーションする”では、全てのコンポーネントが入力バリデーションをするように指揮する、と書かれています。これをやりましょう、ということです。

サーバー側のWebアプリから見ると、WebブラウザのJavaScriptアプリは”ソフトウェアの信頼境界”の外にあるので、Webブラウザからの入力データはサーバー側Webアプリで全てバリデーションをしなければなりません。しかし、既にバリデーション済みのデータであることが前提であると、サーバー側ではより厳格に入力データバリデーションを行えます。(=より安全性の高い入力データ検証が可能)

ソフトウェア機能とデータセキュリティの設計は別物

データのセキュリティ対策はソフトウェアの内部に抽象化するのではなく、できる限り外側から正しい(妥当な)データであることを保証します。ソフトウェアの機能にセキュリティ対策を追加しても上手くいきません。機能設計とデータセキュリティ設計は異なる物として設計する必要があります。

遅すぎるデータセキュリティ対策はフェイルセーフ対策

データセキュリティ(≒ データの正しさ/妥当性保証)はできる限り早い段階で保証します。

データ出力の時点でデータの正しさ/妥当性を保証しようとしても無駄です。出力時点でデータ検証なしに出鱈目なデータを無害化できたとしても、出鱈目なデータは出鱈目なまま、です。妥当なデータに対する対策を除くと、遅すぎるデータセキュリティ対策は余計な操作ができないようにする為のフェイルセーフ対策です。

攻撃者は出鱈目なデータを使い、何とかしてソフトウェアを誤作動させようとします。アプリケーションが出鱈目なデータを出力する=バグ=ソフトウェア開発者の負け、です。

よくある勘違いに、信頼できるデータ = (何をやっても)安全なデータ、があります。正しくは

  • 信頼できるデータ (何をやっても)安全なデータ

です。入力データ検証により、データが信頼できるモノ、になったとしても、そのデータにどのように使っても安全であるとは限りません。出力データが無害であることを保証する責任は出力コードにあり、入力検証コードにはありません。

原理から導き出せる原則と方法を採用

原理から間違えていると出鱈目な原則と方法になってしまい、効果的な対策になりません。「境界防御」も「フェイルセーフ対策」も無い、Webアプリケーションは攻撃者の都合良いオモチャでした。

実際、「文字列でもないIDや数値/特定形式文字列に”攻撃用のデータ”をインジェクション」し、「こう誤作動させることができた」と自慢大会のようになっていました。

  • 数字のハズのデータにインジェクションしコードを実行できた!
  • 形式が決まっているハズのデータにインジェクションしコードを実行できた!
  • ヌル文字インジェクションで不正ファイルをアップロードできた!
  • 改行文字インジェクションで不正メールが遅れた!Webページ改ざんができた!
  • 壊れた文字列のインジェクションでコードが実行できた!ページが改ざんできた!
  • 大きすぎるデータでコードをインジェクションできた!

本来、アプリケーション内に入れるべきでない「不正な入力データをインジェクション仕放題」のプログラムだったので攻撃者には格好のオモチャだったのです。

その一方で攻撃の発生箇所に近い場所での対策(=遅すぎる「フェイルセーフ対策」)こそがセキュリティ対策である、として単純な入力データ検証で防止可能な攻撃の対策は「ダメな対策」だとして無視するように啓蒙までする始末でした。

最近のソフトウェアはかなり「フェイルセーフ対策」が進んできたので、攻撃者の望み通りの”誤作動”をさせることは難しくなってきています。しかし、出鱈目なデータに対する出力対策は「フェイルセーフ対策」なので、問題が先送りされているだけです。出鱈目なデータが先送りされる動作を利用した攻撃は今でも可能になっています。その良い例がサーバーサイドリクエストフォージェリで、制御システムやERPの攻撃に利用されています。

趣味の保護/ジョブセキュリティとしてはかなりよい成果だったと言えます。今でもアプリケーション開発者は脆弱なアプリケーションを日々開発してるのですから。

SQLインジェクション対策でさえ「プリペアードクエリ/プレイスホルダだけを使えば良い」、コマンドインジェクション対策でさえ「APIでコマンドと引数を分離さえすれば良い」といった雑な対策が今でも当たり前で、これにより攻撃可能な箇所を残しています。

全て、正しい原理、原則と方法に基づいたセキュリティ対策を実施していないことが原因です。

正しいセキュリティ概念を身につける

体系的なソフトウェアセキュリティには概念が最も大切です。概念から間違っていると明後日の方向のセキュリティ対策になってしまいます。セキュアなソフトウェアの作り方の概念だけでも知っていれば、開発者かなりセキュアなアプリケーションを自分で作れるようになります。単純なWebアプリ脆弱性診断で1つも脆弱性が発見されないようなアプリケーションを作ることはそれほど難しいことはではありません。

正しいセキュリティ概念を知っていれば、ブラックリスト型の対策を”主たる対策”として採用することもあり得ません。

  • 一つずつ全ての脆弱性を廃除/修正しなければならない
  • 脆弱性は発生箇所に近い所で完璧に廃除/修正しなければならない

これらはどこからどう見てもブラックリスト型の対策です。正しいセキュリティ概念を理解すればこのような、誤った原則が正しい、と勘違いすることも無くなります。

攻撃者にとって好都合なセキュリティ対策ではNG

  • 一つずつ全ての脆弱性を廃除/修正しなければならない
  • 脆弱性は発生箇所に近い所で完璧に廃除/修正しなければならない

これらのブラックリスト型の対策では、許容可能な範囲のリスクで利用できるアプリケーションを作ることは困難です。簡単に”大きな穴”が開いてしまうからです。

攻撃者が困るアプリケーションセキュリティとは「アプリケーションが正しく動作することを保証する構造とコードを持つ」ことです。セキュアコーディング原則、さらに基本となる概念を理解すると開発者自身でセキュアなアプリケーションを作れるようになります。

まとめ

架空の技術者Aのネットワークセキュリティ設計から、今のソフトウェアセキュリティ設計の現状を紹介しました。

セキュリティ対策の論理と目的から大きく勘違いするととんでもなく大きな無駄をしてしまいます。かなり長いブログになりましたが、これだけでは書ききれないくらい問題があります。できるだけ過去のブログへのリンクを付けたので、そちらが参考になります。(それでも足りない、それくらい問題だらけということです)

マトモなソフトウェアセキュリティ専門家は何十年も前から一貫して正しく妥当なセキュリティ対策を推奨しています。セキュリティに詳しい人、というより脆弱性を攻撃することが好きな人、におかしなセキュリティ対策を推奨しているケースが多いように感じます。

今年のDEFCONが急にキャンセルされ、今後は開催しないことが決まったようです。この理由は「脆弱性を攻撃することが好きな人、におかしなセキュリティ対策を推奨しているケース」が無視できないからでは?とも感じています。

国際情報セキュリティ標準、CERT(米カーネギーメロン大学)、OWASPなどの標準/推奨を頑なに無視する理由は何なのでしょう?宗教的な何かを感じます。そうでない方は論理的に考えられた国際情報セキュリティ標準、CERT(米カーネギーメロン大学)、OWASPなどの標準/推奨事項を理解し実践するのが得策です。

最後に、最も重要なアプリケーションセキュリティ対策である入力バリデーションはセキュリティ対策ではない/重要な対策ではない、と主張する人がいて困る場合やそういったアドバイスをする人がいる場合は(ないとは思いますが、そのアドバイスをする人を信じている場合も)その人に責任を取ってもらうのが一番です。

アプリケーションレベルの入力バリデーションがセキュリティ対策でない/重要ではない、とするなら入力バリデーションがない/不十分なことによって発生した問題のコスト/責任を全て取ってください。契約書はコレです。

とリスクを移転すると良いでしょう。アプリレベルの入力検証はセキュリティ対策でない/重要でない、と自信を持って勧めているなら問題なく契約できるハズです。

「これで良いのか?」と思うかも知れませんが、”リスク移転”も立派なセキュリティ対策(ISO 27000)です。全ての信頼できない入力はバリデーションしてから使うように、としているのもISO 27000です。

ホテルのシステムが脆弱でGDPR違反となり制裁金が課されるかも知れない、と話題になっています。もし契約が必要な場合は急がないと契約してくれなくなるかも知れません。

 

参考: 紹介したダメなネットワーク設計とほぼ同じアプリケーション設計が良いとしている例


  1.  実際、多くのWebアプリフレームワークはRailsと同じような構造を持ち、Railsアプリと同じようにデータセキュリティの確保が甘過ぎます。 
  2. 必ずしも「結局は脆弱性を修正しないと直らない」と主張し始めたセキュリティ専門家がセキュリティの基礎の基礎を誤解していた、とは言えません。問題はその後、周りの人たちの誤解ではないでしょうか? 
  3. 廃除/緩和可能な脆弱性は廃除/緩和すべきである、ということには変りありません。「脆弱性を一つ一つ対策&廃除する」これだけ、だとアンチプラクティスです。似たような例には「SQLクエリにはプレイスホルダ/プリペアードクエリを使う」があります。”これだけ”だとアンチプラクティスです。 
  4. そもそも入力データ検証はデータの妥当性を検証する責任があるだけで、出力時の安全性には責任を持たない。妥当なデータであることは「プログラムが正しく動作する」ための必須条件であるので、必ず必要なセキュリティ対策、となる。整数や英数字のみのデータに対する出力対策は主たるセキュリティ対策”ではなく”、フェイルセーフ対策。 
  5. 90年代までのソフトウェアは、脅威はあっても、今の基準からするとソフトウェア的な防御はほとんど無い状態で作られていました。この頃、防御的プログラミングが提唱されたのですが、防御的プログラミングが技術者Aのような間違いを作る遠因になったのでは?と考えています。これについては別の機会に書きたいです。 

投稿者: yohgaki