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