インターネット上に16億件の企業ユーザーのメールアドレスとパスワードが公開されている、とニュースになっています。ほとんどの漏洩元はこれらの企業ではなく、他の一般公開されているWebサービスなどのアカウント情報漏洩※だと考えられています。
この事例から、非常に多くのWebサービスが情報漏洩を伴うセキュリティ問題を抱えているにも関わらず、気がつくことさえも出来ていないという現状があると考えるべきでしょう。(もしくは気付いていても公開していない)
侵入されたことを検知/対応する技術の導入も重要ですが、ここではなぜWebセキュリティはここまでダメになっているのか?を考えます。
※ 情報漏洩にはベネッセのケースのように内部の人による持ち出しも含まれていますが、外部の攻撃者が盗んだ情報も少なくないと考えるべきでしょう。
セキュリティの基本ができていない理由
「セキュリティの基本ができていない」が原因!と言ってしまえば簡単です。実際、オープンソースのWeb開発フレームワークやWebアプリケーションでも、基本ができないモノが山盛りです。
「セキュリティの基本ができていない」理由が重要でしょう。その前にどの基本ができていないのか幾つかリストアップしてみます。
- エラー処理/例外処理の基本ができてない
- プログラム上のリスクの見える化ができていない
- 入力処理の基本ができていない
- テキスト処理の基本ができていない
- 出力処理の基本ができていない
- Webシステムはインターネットに晒されている高リスクシステムと認識&対応できていない
それぞれの基本と原因を考えてみます。
エラー処理/例外処理の基本
実用的なプログラミングの基本中の基本とは何でしょうか?よく「実用的なプログラムの多くはエラー処理/例外処理コードで占められる」と言われています。実際、エラー処理/例外処理を考えないプロトタイプはかなり短時間で作れます。実用的なプログラムにするには、割と簡単なプログラムでも相当なエラー処理/例外処理用のコードを書くことになります。
実用プログラムでは、エラー処理/例外処理は有用な処理を行うビジネスロジック処理と同じくらいの根幹となる大切な処理と言えます。大切な処理であるエラー処理/例外処理には絶対的な基本原則があります。
- フェイルファースト原則 – 失敗するモノはできる限り早く失敗させる
エラー処理/例外処理の基本ではないか!当たり前すぎる!プログラミングの基本中の基本!と思うかも知れません。しかし、これが出来ていません。。
エラー処理/例外処理が必要となる最大の原因はプログラムが処理できない”無効なデータ”です。出来の良いプログラムでは、”無効なデータ”はフェイルファースト原則に則りできる限り早い段階で廃除する、ことになります。
都合が良いことに、致命的なセキュリティ問題の大半は”攻撃者が送ってきた無効なデータによる誤作動”です。フェイルファースト原則に従い、できる限り早く無効なデータの処理を終わらせると大多数の致命的なセキュリティ問題を廃除できます。しかし、一般的なWebアプリケーションはこうなっていません。
エラー処理/例外処理の基本ができていない理由
「入力バリデーションはセキュリティ対策ではない」とする意見を聞いた事がある方が少なくないと思います。ここまで酷い例でなくても「入力バリデーションはセキュリティ対策として信用できない対策なので重要ではない」「入力バリデーションはセキュリティ対策というよりアプリケーション仕様である」といった意見を聞いたことがあると思います。
国際情報セキュリティ標準やCERTのセキュアコーディングを考案しているセキュリティ専門家はフェイルファーストによるデータエラー処理/攻撃データ処理を想定/啓蒙しています。しかし、残念ながらそうではないケースも多くあります。
エラー処理/例外処理の基本ができていない最大の理由はセキュリティ専門家と呼ばれる人達が「プログラミングの基本中の基本」を正しく説明しない/無視していることが大きな原因だと思われます。その最たる例がIPAの旧セキュアプログラミング講座だと思います。
フェイルファーストで入力データエラー処理をするどころか、最後にエラー処理を行うフェイルラストがセキュアプログラミングであるかのような解説をしています。これではセキュア(安全な)プログラミングではなく、インセキュア(危険な)プログラミングです。
上記は2017年のPHPカンファレンスの「セキュアコーディングの原則」と題したスライドです。ファイルファーストどころかフェイルレイテストが最も良いセキュリティ対策であるかのように解説されています。
よく知っているセキュリティ専門家やセキュリティ機関が「入力バリデーション(=フェイルファースト原則に従ったデータ検証でエラーにする手法)はセキュリティ対策ではない/重要ではない」としてしまうと、認知バイアスの一種である「身近な情報による誤謬」により普通の人は誤った情報でも信じてしまいます。
※ 身近な情報による誤謬とは、誤った情報でも身近な人や知っている組織などの情報の方を正しいと誤認してしまう認知バイアス。
本来、IPAなどは機関はこのような認知バイアスによる誤解などを解く立場にある機関ですが、IPAから率先して誤解を広めていました。去年やっと修正されました。多くの開発者が誤解してしまっても仕方ない状態だったと言えるでしょう。その責任は誤った情報を啓蒙した人達/組織にあります。
プログラム上のリスクの見える化
現代のプログラミングは必要な機能を抽象化する作業である、と定義できます。複雑な機能も関数やクラスに抽象化して、簡単に取り扱えるようにします。機能を抽象化すると実装コードは見えなくなります。
しかし、抽象化の最大の目的は実装コードを”見えなくする”ことではありません。
一番の目的は、プログラムが何をするのか、開発者に見えるようにすることが目的です。実装コードを見えなくすることが最大の目的であれば、関数名やクラス名などは解りづらい名前でも何の問題もないはずです。しかし、関数名やクラス名は「プログラムが何をするのか、開発者に見えるようにする」為にとても重要です。
1000行程度の簡単なプログラムなら抽象化しなくても全く問題ありません。しかし、ある程度の規模を超えると適切な抽象化は必要です。適切な抽象化はプログラムを簡単にし、プログラムが何をするのか見える様にします。
※ 機能見える化の例外:プログラムが何をするのか理解りづらくする抽象化もあります。これは主に拡張性を向上する目的で抽象化した場合に発生します。多くのOOデザインパターンがこれ。
セキュリティ処理も同じように抽象化しても構わないですが、それはプログラム機能の抽象化と同じように「見えるように抽象化」されている必要があります。
※ セキュリティ見える化の例外: フェイルセーフ対策は見えなくても構わない。というよりもフェイルセーフ対策は見えないところで対策することが多い。
プログラム上のリスクの見える化ができていない
この問題もフェイルファースト原則を誤解/理解していないことが原因で、芋づる式に作ってしまった問題に分類されると思います。
本来の抽象化の目的は複雑な処理でも「何をやっているのか見えるようにすること(解るようにすること)」です。しかし、抽象化の副作用というか、表裏一体の効果というか「複雑な処理を見えないようにする」部分に着目し、これをセキュリティに”応用”したセキュリティ対策”のみ”が強調されすぎているように感じています。
プログラム(コード)でリスクが判らないように作れば、安全なプログラム(コード)が書けなくても当たり前です。
見えない所でセキュリティ対策を実装する、が悪い訳ではありません。見えない所で実施するセキュリティ対策のほとんどは「フェイルセーフ対策」です。フェイルセーフ対策も重要です。
しかし、フェイルセーフ対策”だけ”だったり、フェイルセーフ対策に”頼る”セキュリティ対策ではダメなセキュリティ対策です。
プログラムの抽象化と同じく、セキュリティ対策も見えるようにしなけば開発者が本当に安全にプログラムが動作するのか判らなくなります。
アプリケーションソフトウェアでは開発者がソフトウェアセキュリティに対してほぼ全ての責任を持ちます。アプリケーション開発者がライブラリや外部システムの責任にして構わないセキュリティ問題は、それらのソフトウェアに対して妥当なデータを渡にも関わらず誤作動してしまったケースに限られます。
ソフトウェアセキュリティ確保の方法を詳しく説明すると長くなりすぎるので、不十分であってもできるだけ簡潔に済ませます。
セキュリティ問題の大半は不正なデータによるプログラムの誤作動です。従って不正なデータを無くせば、セキュリティ問題の大半を防止できます。
セキュアコーディング/セキュアプログラミングでは、入力データは完全にバリデーション(原則1)します。バリデーションしないデータは完全に信頼できるデータ(自分の書いたコード中のリテラルなど)のみです。入力データの妥当性(≒安全性)は入力処理時に「見える化」します。
セキュアコーディング/セキュアプログラミングでは、出力データは完全に無害化(原則7)します。出力データの無害化処理は入力データのバリデーションの有無、データの信頼性に無関係に独立して行います。つまり、出力の時点でデータの無害化(≒安全性)を「見える化」します。
セキュリティ対策を「見える化」するのがセキュアコーディング/セキュアプログラミングです。「見えない箇所にあるセキュリティ対策に”頼る”」のはセキュアコーディング/セキュアプログラミングではありません。
プログラム機能の抽象化が「プログラムが何をするのか見えるようにする」のと同じように、プログラム安全性の抽象化は「データ/コードがどのように安全性を維持しているのか見えるようにする」ようになっていなければなりません。
フェイルファースト原則なし、検証なしに「中身はよく知らないけど、多分適当にセキュリティ処理をしてくれているはず」「セキュリティ処理はAPIの責任」とするのはセキュアコーディング/セキュアプログラミングとは正反対の考え方になります。
アプリケーションセキュリティ維持の責任は、アプリケーション開発者にあります。フェイルファースト原則で対応しないとどうやっても上手く行かないモノが色々あります。文字エンコーディングデータのバリデーションはその良い例です。
壊れた攻撃用の文字エンコーディングを受け入れ、ライブラリなど任せにすると何時まで経ってもマトモに動作するソフトウェアは原理的に作れません。
入力処理の基本
超基本的なフェイルファースト原則から勘違いしてしまうと、後は芋ずる式に勘違いが広がります。
入力処理の役割は、外部からやってくるデータの妥当性検証(入力バリデーション)です。フェイルファースト原則を無視すると妥当性検証なしのデータをプログラムに渡すことになります。このような仕様のアプリケーションは致命的な脆弱性を持ちがちです。
ここ最近だけでもStruts 2やDrupalで、本来はプログラムコードなど書けないハズのデータにプログラムが書けてしまい、任意コード実行を許していた事例があります。
入力処理の基本ができない理由
もう既に説明してしまいました。プログラム構築の基礎の基礎であるフェイルファースト原則の無理解/無視が原因です。付け加えるならゼロトラスト概念の不在も理由でしょう。
ゼロトラスト原則はフェイルファースト原則と同じくらい基本的なセキュリティ原則/概念です。フェイルファーストと同じくらいの基礎概念ですが、基礎的すぎて解説がほとんどされていない状態です。
- ゼロトラスト – 何も信頼しない、検証した範囲内で信頼する
体系的なセキュリティ標準やガイドラインでは必ず「リスク分析」を行うようにとしています。リスク分析を行う場合「これは大丈夫だろう」といった希望的観測に基づいて「リスクはない」とはしません。全てに対して「ダメかも知れない」を前提としてリスク分析を行います。つまり、リスク分析は何も信頼しない(ゼロトラスト)状態から始めます。
リスク分析を理解すれば、ゼロトラスト(何も信頼しない)という考え方の説明は必要ない、とも言えますが誤解だらけの現状だと説明の必要があると思います。
このような状況でも「Webアプリケーションのリスクは判っているので、リスク分析などは必要ない」とするセキュリティ専門家も居たりして、誤解は広がってしまいました。「未検証の入力データ」はセキュリティ以外の大きな原因です。
初版のOWASP TOP 10は「未検証の入力データ」が第一位の脆弱性としていました。「入力対策があれば出力対策は必要ない」(セキュアコーディング原則では入力対策と出力対策は”独立”した対策です、念の為)といった誤解もあり、2版から「この項目は削除したが変わりなく重要で他の脆弱性対策の項目で入力バリデーションをすべしとした」とする注釈付きで削除されました。何時まで経っても状況は良くならないからか、2017年版OWASP TOP 10では強化された内容になって戻ってきました。
※ OWASPは組織として、入力データバリデーションは最も重要なセキュリティ対策の1つである、と一貫した考えであることはOWASP Secure Coding Quick Reference GuideやOWASP Code Review Giudeなどからも分かります。しかし、正反対の解説をするセキュリティ専門家のスライドを見たことがあります。
テキスト処理の基本
Webアプリではテキスト処理が多用されます。HTTPもHTMLもJSONもSQLも、その他のインターフェースもテキストインターフェースばかりです。当然テキストを安全に処理する基礎知識が必要になります。
テキストインターフェースなので「テキスト」を出力することになります。安全な出力の基礎知識も必要になります。
複数のテキストインターフェースが多重化している場合の処理方法の知識も必要になります。
テキスト処理の基本ができていない理由
主にテキストインターフェースを扱うWebアプリには、正確なテキスト処理方法は欠かせない基礎知識です。残念ですが、これが欠けています。
SQLインターフェースは様々な意味で正確なテキスト処理、特にテキストインターフェースが多重化している場合の処理の教科書として適したインターフェースです。
SQLを使って安全にデータを保存するにはエスケープが欠かせません。にも関わらず10年以上前から「SQLインジェクションを防ぐには(≒SQLを安全に使うには)プリペアードクエリさえ使っていればよい」とする誤ったキャンペーンが行れています。その結果、簡単かつ完全に防げるはずの識別子によるSQLインジェクション脆弱性とSQLクエリに関連するJSON/XPath/XML/正規表現インジェクション脆弱性が多くのアプリケーションに残ったままになる、といった事態になっています。
MicrosoftやOracleといった大DB会社でさえ、RDBMSのAPIの中からパラメーターのエスケープAPIを削除し、識別子のエスケープAPIを提供しない、といった脆弱なAPIセットのままで直す様子がありません。
テキストインターフェースとそのインターフェースに於けるエスケープの意味を理解していればこのような状況にはならなかったと思われます。
SQLデータベースの標準仕様にはエスケープ仕様があります。SQLはまだ良い方でパラメーターのエスケープ仕様が定義されていない標準仕様さえあります。エスケープは脆弱性の原因であるからエスケープ仕様さえ必要ない、と勘違いしてテキストインターフェースを作ってしまった失敗例がXPath 1.0です。
※ XPath 2.0仕様では誤りに気付いてエスケープ仕様が追加されています。
SQLクエリは完全な安全性(完全な無害化)の保証が可能なインターフェースです。容易かつ完全に対策可能なSQLクエリでさえ安全になっていない原因は、一部のセキュリティ専門家やソフトウェア開発のプロ中のプロ(RDBMS開発者はプロ中のプロとして間違いないでしょう)を含む人たちが、長期的かつ大量に誤ったメッセージを開発者に送り続けていることにあるでしょう。
これも強力な「身近な情報による誤謬」の原因になったと思われます。
出力処理の基本
出力は複数コンテクストが多重化しているケースも考えた上 で(例:JSONデータをHTML上に出力する場合、文字列データのクオートエスケープだけではNG)、3つの対策を実施すれば完全に無害化できます。しかし、それだけでは十分ではありません。
出力対策”だけ”では正しく処理することは原理的に不可能です。
データを正しく処理するには、データの妥当性検証が欠かせません。
出力処理の基本ができていない理由
最初のフェイルファースト原則を無視/勘違い/知らないことにより、出力処理でも芋づる式の間違いが起きています。
フェイルファースト原則を適用した上で、出力処理を行わないと何時まで経ってもソフトウェアはセキュアになりません。出力対策の半分の役割はフェイルセーフ対策だからです。
セキュリティ対策は出力対策”だけ”では論理的に不可能であるにも関わらず、長期間に渡って出力対策”だけ”がセキュリティ対策であるかのような解説がまかり通っています。
最悪の事例はSQLインジェクション対策でしょう。既に記述した通り、プリペアードクエリ”だけ”で対策が完結するとした解説は未だに多いです。
SQLに対するインジェクションを完全に防ぐには、SQL識別子のエスケープ/バリデーション、LIKEクエリのエスケープ/バリデーション、が欠かせません。
さらにSQLに含まれたJSON, XPath, XML, 正規表現といった多重化されたコンテクストのデータに対するエスケープ/バリデーション処理がないと、SQLを使ったインジェクション攻撃が可能になります。
エスケープ仕様は決めているのにエスケープAPIがないといった脆弱なAPI仕様が放置されていることも、一般開発者が基礎的なテキストインターフェース処理を理解することを妨げ、身近な情報による誤謬を広めていると思われます。
最大の原因は「エスケープは必要ない」とした誤ったセキュリティ対策キャンペーンではないでしょうか?
テキストに意味を持たせたテキストインターフェースを使っているのに、エスケープが必要ない、といった開発環境は通常在りません。エスケープ処理は出力セキュリティ対策として重要な意味を持っています。プログラマがテキストインターフェースを使っている限り、これは変わりません。
Webシステムはインターネットに晒されている高リスクシステムと認識&対応できていない
オフィスなどの物理的なセキュリティ、ネットワークのセキュリティなどでは、守りたい物は可能な限り直接”外部”に晒されないよう設計し、必要であれば何重ものリスク対策を行います。
リスクの塊であるインターネットとの境界(Webサーバー)で境界防御なし
ソフトウェア、特にWebサーバーアプリでは
- 守りたい物は可能な限り直接”外部”に晒されないよう設計
どころか、ソフトウェアの内部奥深く、多重のセキュリティも無し、でリスク対策を実施するのがベストプラクティス、であるかのようなベストプラクティスもどきのアンチプラクティス解説が普通に見られます。
アンチプラクティスを実践し、基本構造から問題があるセキュリティ設計では、セキュアなアプリケーション作りが何時まで経っても無理であっても仕方ありません。
結局、どういうことなのか?
Webシステムのセキュリティ、ソフトウェアのセキュリティがダメなままであり続けている原因は何なのか?理由は明きらかでしょう。
論理的にセキュアなソフトウェアを作る為に必要な基礎の基礎を誤解/無視していることが原因です。基礎の基礎を勘違いしてボタンの掛け違いが芋づる式に連鎖し、脆弱なソフトウェアのままである状態が続いています。
- 境界防御の概念が欠けている
- これにより
- 境界でのデータバリデーションがない
- 当然、攻撃可能面を管理する構造もない
- 攻撃を検知/対応する仕組みも実装されない
- 効果的な多層防御が実装されず場当たりの防御となる
- 結果、リスク/脆弱性の管理が不可能になり脆弱なままとなる
個別のセキュリティ対策も重要ですが、セキュリティ対策の構造化がないことが、非常に脆い(=攻撃に弱い)ソフトウェアの原因になっています。
なぜこのような事が起きるたのでしょうか?それはコンピューターサイエンスやエンジニアリングを真面目にやってきた先人からの積み重ねを無視したセキュリティ概念にあると思います。
- 入力対策はセキュリティ対策ではない/セキュリティ対策として重要ではない
- ブラックリスト型対策もホワイトリスト型対策と変わらないセキュリティ対策である
- 出力対策だけでセキュリティ対策できる
- エスケープがあるからインジェクションが起きる
- ダメな入力はサニタイズして使えばOK
- セキュリティ問題はバグだから単純にバグが起きた箇所、それに近い箇所で直せばOK
- セキュリティ問題は出鱈目な入力に対応できないライブラリやフレームワークが原因
これらは全てダメなセキュリティ概念です。複雑な処理の構造化が必要であること同じく、セキュリティ処理の構造化も欠かせません。そもそも境界防御の概念なしのセキュリティ対策はあり得ません。
外部からのでデータは”全て”妥当であるかバリデーションしてからでないと使えません。プログラムが正しく動作するには妥当なデータが必須だからです。(例:整数データに文字列データでは正しく動作できない)
論理的に物事を理解し対応するには基礎の基礎から勘違い/誤解していると、何をやっても頓珍漢なことになってしまいます。
基礎的な部分で勘違いをしていると、ボタンの掛け違いが続いてしまい、大きな遠回りをしてしまいます。遠回りで済めばまだよい方で目的地に何時まで経っても到達しない結果となってしまいます。
時々は基礎の基礎、原点と言える部分に立ち返って、現状は大丈夫なのか?考えてみるのも良いのではないでしょうか?例えば、先日書いたこれなど。
別の視点からの考察 – セキュリティ対策=リスク廃除ではない
今でもどういう論理になっているのか理解できないのですが、
- セキュリティ対策=リスク廃除だけ
と考えてしまうケースがあります。これが、とんでもない勘違い/間違い生んでいます。
情報セキュリティ対策はセキュリティ対策の一種です。リスク対策にはリスクを減らすことばかりでなく、リスクを増やす対策も含まれます。特定のリスクに対する対策を実施すると、今まで無かった別のリスクを増やす、一部のリスクしか廃除/削減していない、といった状態はごく一般的にあります。新機能の追加など純粋にリスクを増やす場合もあります。
このエントリでは幾つかのセキュリティ認識の問題点を説明しました。割とよく問題になる誤解に、セキュリティ対策にはリスクを増やす/残す対策も含まれる(ISO 27000のセキュリティ対策の定義でも明記されている)があります。これを認識せずに作ってしまうセキュリティ問題も少なくありません。
リスク削減/廃除だけに着目し、残存/増加リスクを無視
よくある失敗パターンは「リスクの廃除/削減”だけ”」に着目し、「リスクの残存/増加」を無視してしまう事です。「SQLインジェクション対策はプリペアードクエリ/プレイスホルダ”だけ”で十分」とする勘違いはこの典型的な例です。プリペアードクエリ/プレイスホルダは”SQLパラメーター”のリスクは廃除します。しかし、他のリスクは廃除しません。沢山のリスクが残ったままになります。
残存/増加リスクを無視してしまう理由は、そもそもリスク分析を自分で全く行わないことが原因なのでは?と思えています。リスク分析はセキュリティ対策の基礎の基礎の基礎です。
完璧なセキュリティ対策を可能な限り一箇所にまとめて対策 – 構造的なリスクを無視
時々見かける関連した勘違いに、
- 一箇所で完全にリスクを廃除できる対策が最も優れた対策で他は必要ない
これはセキュリティ対策の基礎である境界防御、多層防御を無視した考え方です。
攻撃可能面の管理は物理セキュリティ、ネットワークセキュリティでは当たり前に利用されているにも関わらず、インターネットに接続したWebアプリケーションでは利用されていません。アプリケーションの奥深く、APIのセキュリティ対策”だけ”に依存するアプリケーションが大半です。これではアプリケーションの内部奥深くのアプリ/ライブラリ/システム仕様などの脆弱性を、どうぞ攻撃してください、とするに等しいです。ソフトウェアは常に変化しています。何時まで経っても安全になるはずがありません。
手段と目的の取り違え – 誤ったリスク排除手段
致命的な失敗パターンとして、手段と目的の取り違え、もあります。リスク管理(=セキュリティ管理)の目的は「リスクが在るモノを許容範囲内のリスクに抑えて利用する」です。手段と目的の取り違えもおかしなセキュリティ対策の大きな原因になっています。
手段と目的の取り違えと同類の誤りに、原因と結果の取り違えもあります。X Path 1.0標準に文字列パラメーターのエスケープ方式が未定義だった、というとんでもない間違いは
エスケープ方法の定義があるから(原因)、エスケープ漏れが発生しセキュリティ問題が発生する(結果)
といった原因と結果の勘違いあったと考えられます。エスケープ方法の定義が原因ではなく、正しくは単純に
エスケープ漏れが発生すると(原因)セキュリティ問題が発生する(結果)
となります。クオートして定義する文字リテラルが存在するにも関わらず、文字リテラルのエスケープ方式を定義しなかったXPath 1.0は常軌を逸した標準定義だった、と言わざるを得ません。(XPath 2.0では普通にエスケープ方式が定義されています)
「SQLクエリにはプリペアードクエリ/プレイスホルダだけ使えば良い」とする勘違いはこれと同類です。