2019/02/24)今のプログラムに足りないモノでセキュリティ向上に最も役立つ考え方のトップ2つ挙げなさない、と言われたらどの概念/原則を挙げるでしょうか?私ならゼロトラストフェイルファーストを挙げます。極論すると、この2つ知って実践するだけでセキュアなソフトウェアを作れるようになるからです。この2つだけでは十分ではないですが、これを知って、実践しているだけでも開発者は今のコードより段違いにセキュアなコードが自分で書けるようになります。一つ追加するなら多層防御(縦深防御)を加えます。これはゼロトラストとフェイルファーストから導き出せる概念です。ゼロトラストとフェイルファーストで検証を行うと自然と多層防御になります。多層防御はセキュリティ対策の基本ですが、特にソフトウェアでは実践されている、とは言えない状況です。これら3つはとても有用な概念で単純明解な概念ですが、知られていなかったり、誤解されていたりすることが多い概念/原則と言えるかも知れません。関連TL;DR;ソフトウェアセキュリティに限らず物理的セキュリティ/ネットワークセキュリティも基本的な考え方は同じです。失敗するモノはできる限り早く失敗させる例えば、以下のような考え方がゼロトラストとフェイルファーストです。セキュリティが必要なビルなどでは入館時に「入館して良い人か?」全員を検証する。(ゼロトラスト)宅配業者であると確認して入館させた人は社員と同じようにビル内の施設にアクセスできてはなりません。検証にはレベル/制限/検証の範囲があるが、それを無視して”チェック済みだからOK”としてしまう間違いは多い。一般に検証は多層構造で行います。重要な部分に個別にセキュリティ対策、フロア/部屋/キャビネットなどに個人毎のアクセス管理機構など、があっても、外部からの訪問者は入館時に「入館を許可できるか?」検証する。敷地への立ち入りからチェックできるなら、門でチェックする。Trust)とは言葉通りの意味で、「何も信用しない」です。プログラミングに於けるゼロトラストは以下になります。他人のコード/データは信頼しない自分のコードも信頼しない正常に動作している(様に見える)コードも信頼しないプロセス/スレッドの外からのデータは信頼しない正しい/妥当なコードなのか?正しい/妥当なデータなのか?これらをハッキリさせるには正しさ/妥当性を検証しなければ信頼できません。どのようなコード/データなのか?検証なしに、どこの馬の骨のコード/データなのか分らないモノを信頼するのは自殺行為に等しいです。検証内容を正確に理解せず、単純に検証済みだから、と全面的に信頼してしまうのも同じく自殺行為に等しいです。信頼可能な範囲は検証した内容に限られます。当たり前の話だね、と思うかも知れませんが大半のソフトウェア開発ではこれが当たり前ではありません。外部ネットワークと直接やり取りする公開Webアプリでさえ、この当たり前が出来ていないのが現状です。中身の処理も知らずに出鱈目なデータを受け入れてもライブラリ/フレームワークなどが適当に処理してくれるこの考え方で攻撃されてしまった/攻撃可能になってしまった事例は枚挙にいとまがありません。ただのテキスト/数値データにプログラムが書けてしまい不正なデータを受け入れて攻撃された、データ不整合やエラーが発生してはならない部分でエラーが発生し問題なった、こういった事例は山程あります。出鱈目なデータを処理させない責任を持つソフトウェアはアプリケーションです。基本的にライブラリ/フレームワークは”汎用的”に作られており、できる限り多くのアプリケーションで利用できるよう緩いデータ検証機能しか持っていません。アプリケーションは”専用”のソフトウェアであり”特定のデータ”を処理するように設計/構築されます。”ソフトウェアの役割”から出鱈目なデータを処理させない第一の責任を持つソフトウェアはアプリケーションであるライブラリ/フレームワークなどの「無効なデータ処理防止/廃除機能は基本的には多層防御」であり、最初の防御機能ではない(責任は持つが第一の責任を持たない)となります。セキュリティの基礎ソフトウェアに完全にゼロトラストを実装すると完全な「契約プログラミング/契約による設計」を実装した時と同じ状態になります。完全な「契約プログラミング/契約による設計」を実装した状態とは、全ての入力と出力およびアプリケーション状態を全ての関数/メソッドで完全に検証する状態になります。何も信頼しない(ゼロトラスト)だけではITシステムは作れない何も信頼しない/できない状態ではITシステムは作れません。プログラムも作れません。利用する全てのライブラリ/フレームワークのコードと動作を完全に検証し「信頼できるモノ」にすることは一般に不可能です。利用する全てのコードを検証する場合、開発者が直接利用するAPI実装を検証するだけでは全く不十分です。そのAPIが利用するライブラリ、更にはOSのシステムコールの実装コード、コンパイラが出力するマシン語まで検証して初めて”完全に検証した”ことになります。コードを全て検証する、は論理的には可能ですが現実的には不可能です。このOSなら、このライブラリなら検証なしでも信頼できるモノとして利用します。完全に検証していないコードを信頼できるモノとしても意図通りに動作するとは限りません。出鱈目なデータで正しくエラー処理が行われるか、は保証されません。出鱈目なデータに脆弱なコードは多く存在し、攻撃に利用されている現実があります。コードが信頼できる、としても出鱈目なデータでの動作は保証されていない状態でプログラムを作ることになります。コードの動作を信頼しない状態でプログラムを作るとエラーチェックコードが冗長になり過ぎて、コードの見通しが悪くなる上にエラーチェックコードが原因で実行速度が極端に落ちます。C/C++でプログラム開発をしている方ならValgrindと呼ばれるメモリ管理チェッカーを使ったことがあると思います。Valgrindはメモリ管理関数をオーバーライドすることにより、後付けで全てのメモリ管理状態のエラーをチェック可能にします。メモリ管理の状態をいちいちチェックするのでプログラムの実行が数倍、数十倍遅くなります。身近な例ではPCREライブラリ(Perl互換正規表現ライブラリ)の文字エンコーディングチェック機能があります。PCREはデフォルトでUTF-8文字エンコーディングのバリデーション(データの妥当性検証)を行います。これは不正なUTF-8文字エンコーディングが渡された場合の誤作動を防止する為のセキュリティ対策です。この文字エンコーディング検証機能が有効な場合、正規表現マッチの性能が数百倍以上遅くなることが知られています。(少なくともHaskellのPCREライブラリバインディングはUTF-8文字エンコーディングバリデーションを省略するオプションが指定可能。PHPは指定できません。)このように何も信頼しない”ゼロトラストだけ”でITシステムを作ると、使い物にならないシステムが出来上がったり、使い物になっても最適化された状態の性能とは程遠い性能にしかならない、といった問題が発生します。何も信頼しない(ゼロトラスト)だけではITシステムは作れないのです。出鱈目なデータでも「正しく動作すること」を保証するのは困難ですし、そもそも原理的に無理だったりします。何も信頼しないところから始めてセキュリティを構築しなければなりませんが、”ゼロトラストだけ”ではITシステムは作れないことが分かりました。この問題を解決するには信頼に足ることを検証し、信頼できるモノのコアとなるモノを作ります。他人の書いたコードは完全に信用することはできません。自分の書いたコードと自分が取り扱ったデータは信頼できる状態にし、信頼するモノのコア、になるようにします。自分が書いた全てのコードが信頼に足ることを検証する自分が取り扱う全てのデータが信頼に足ることを検証する信頼するモノにするためには、自分(自分達)が書いたコードは信頼に足ること、自分(自分達)処理するデータは妥当であること、を検証/保証します。他人が書いたコードは信用しないので、そのコードが想定する妥当なデータの範囲内で利用しなければなりません。(=仕様外の出鱈目なデータは絶対に他人のコードに渡さない。例外は他人のコードが完全かつ問題無い形で出鱈目なデータを処理することを保証できる場合、つまり完全に信頼できる場合に限る)自分が取り扱うデータは全て信頼に足る(=仕様内のデータ)データであることを検証します。〜ソフトウェアの場合、”信頼境界”を定め、その境界を超えるデータの妥当性(完全性/機密性など)を検証します。書いた通り、他人のコードに対しては出鱈目なデータを絶対に渡さないように保証します。(セキュアコーディング原則7)コード/データの妥当性を検証すれば、その”検証内容の範囲内”でコード/データを信頼することが可能です。他人のコードはその入力/出力/処理の仕様を把握し、その範囲内で利用するように自分のコードを書くようにします。”検証したモノ(コード/データ)は無条件に信頼できる”としてしまうのはよくある勘違です。”検証内容の範囲内”でしか信頼できない、つまり条件付きでしか信頼できない事に注意します。セキュアコーディングでは自分のコードは信頼のコアとなるコードになりますが、自分のコードも信頼しません。このためセキュアコーディングの第7原則の「出力を無害化する」は入力バリデーションやロジックバリデーションと無関係に全て無害化します。ライブラリ開発者とアプリケーション開発者の責任は同じではない同じ開発者/プログラマといっても、ライブラリ開発者とアプリケーション開発者の責任は同じではありません。ライブラリプログラマの場合、出鱈目なデータで出鱈目な動作になったとしてもライブラリ利用者の責任にすることも可能です。間違った使い方(≒出鱈目なデータ)を渡した開発者が悪い、とする仕様にしても何の問題もありません。アプリケーションプログラマの場合、出鱈目なデータで出鱈目な動作になってしまう責任はアプリケーションプログラマが持ちます。出鱈目なデータを送ってきた送信者/利用者が悪い、出鱈目なデータで適当なエラーとならなかったライブラリが悪い、とすることは出来ません。アプリケーションの場合、そもそもエラーとなってはならない箇所でエラーになること自体が問題になることがあります。出力時のエラー発生はアプリケーションに構造的問題がある、とされても仕方ありません。フェイルセーフ頼みはNG非効率なセキュリティ対策だらけの現状そもそも全てのコード/データは信頼できないモノであることが前提条件です。アプリケーションがコードとデータを信頼可能な状態してから利用する責任を持っています。関わらず脆弱性/セキュリティ問題は呼び出された側に脆弱なコードがあったら、呼び出された側の責任であるとする考え方で大半のアプリケーションが設計/構築されています。この考え方がアプリケーション脆弱性の大きな原因となっています。ライブラリなどは不正なデータでエラーになる事もチェックしていますが、基本的には妥当なデータで正しく動作するように設計/検証されています。そもそも”仕様として”不正なデータではエラーにならず、不正な結果を出力してしまうライブラリも当たり前にあります。ライブラリ等は”汎用的”に作られており、どうしようもないデータ以外、何が妥当なデータであるか?関知しないのが普通です。不正なデータでも「正しくエラーになる」ようなチェックを行っているつもりでも、チェック漏れで不正な動作を許してしまうプログラムも数えきれないくらいあります。”組み合わせ爆発”により”不正なデータで正しくエラーになる”まで完全にチェックするような余裕は開発者にはない”形式的検証”と”組み合わせ爆発”から学ぶ入力バリデーションアプリケーションでは、不正なデータによる不正な動作をライブラリなどの責任する、と考えるのは脆弱な考え方です。不正なデータでも「正しくエラーになる」ようなチェックを行っているつもりでも、チェック漏れで不正な動作を許してしまうライブラリは数えきれないくらいある、そもそもデータの正しさをチェックさえしないライブラリもある、と判っているからです。ライブラリやフレームワークは汎用的に作られているので、特定用途のアプリケーション用に「正しくエラーになる」は構造的に実現できません。ライブラリやフレームワークだけで”訳の分らない不正なデータを与えられても、正しくエラーになる”という状態を達成することは不可能です。汎用的に作られているからです。一部の出鱈目なデータに対して正しく対応できても、アプリケーションで対策すべき全ての出鱈目なデータに対して「適切」に対応することは無理です。不正な文字エンコーディング処理をライブラリ任せにすることは不可能です。構造的に文字エンコーディングのバリデーションはアプリケーションの責任であり、ライブラリの責任することはできません。結局、ソフトウェア製品としてユーザーに機能を提供するアプリケーション開発者が責任を持ってコード/データが正しく処理されることを保証(≒検証)するしかありません。Fast)とは失敗するモノはできる限り早く失敗させる原則です。多くの開発者は既にフェイルファーストを原則として実践していると思います。年齢データを使った処理を行う前に行うを実践していると思います。フェイルファースト原則を全てのデータに適用しているWebアプリケーションはほぼ皆無なのが現状です。全てのデータは検証/保証なしに信頼できません。プロセス/スレッドの外からのデータは信頼しないこれが原則です。全ての外部からのデータは検証し、検証エラーになるようなデータはできる限り早く廃除するがベストプラクティスになります。10では廃除するだけでは脆弱なアプリケーションである、と一歩踏み込んだセキュリティガイドラインになっています。今後はこれがWebアプリセキュリティの標準になると考えられます。Tool)からの攻撃に対して、検知/ログ/対応することを求めています。DASTはアプリケーションが処理する入力に対して出鱈目なデータも送ります。フェイルファースト原則に従うとアプリケーション入力処理でこれらの出鱈目なデータは検知/廃除することになります。DASTはデバッグフラグや隠し機能の有無をチェックする余計なデータも送ります。セキュアなアプリケーションはアプリケーションが期待する入力データのみでなく余計なデータ、Webアプリなら余計やHTTPヘッダー/クエリパラメーター/POSTパラメーターも検出できなければなりません。今のところこのようなセキュリティ対策はほぼ全てのWebアプリケーションで実装されていません。27000は2000年から余計なデータの廃除を求めています)新しいセキュリティ要求事項に見えるかも知れませんがフェイルファーストで考えると、余計なデータがあるデータは入力データとして問題である、と理解るはずです。Fast原則を理解していれば、必要なセキュリティ対策である、と開発者自身で定義できると思います。完全または万能と誤解されがちなセキュリティ対策に「SQLインジェクション対策にはプリペアードクエリを使う」「OSコマンドを実行する場合にはコマンドと引数を分離する」があります。これらは個別のセキュリティ対策として万能ではありません。万能と考えると著しく危険なセキュリティ対策です。危険なのかはゼロトラストとフェイルファーストで考えると理解ると思います。ゼロトラスト、フェイルファーストも乱用/誤用が可能な概念ではありますが、正しく理解して適切に使えば万能の基本原則です。関わらず正しく理解されて利用されている、とは言い難いのが現状です。ゼロトラストとフェイルファーストは単純明快かつほぼ万能な原則なので、ITシステムを作る場合には常に意識しておきたい原則です。トップ10原則)も自然と導き出せます。Failure)を作らない”です。どんなに完璧に見える対策でも、一箇所で全てのリスクに対応/緩和するのは高リスクです。人は単純な間違いをします。全ての状況も想定できまねせん。プログラムでも「こんな間違いが!」「こんな状況が!」が原因で正しく動作しなくなることが普通にあります。Depth)も単純な概念です。リスクに対して複数の対応策を利用して対策する物理的/ネットワーク的なセキュリティ対策では当たり前に実践されている考え方です。ソフトウェアでは当たり前に実践されているとは言えないモノが多いです。ソフトウェア開発文化の問題ソフトウェア開発では開発文化的に多層防御が行われない素地があります。これは構造化プログラミングから出来上がった同じ処理はできる限り繰り返さないが原因でしょう。一般に”同じ機能の共通化”は良いことです。セキュリティ対策に於て”同じ機能の共通化”は良いこととは限りません。セキュリティ対策ではリスクに対して複数の防御策(多層防御)で対応すべきだからです。ソフトウェア開発/設計では”機能の構造化”を中心に考えます。手続き型/オブジェクト指向型/関数型などのプログラミングパラダイムに関わらず機能を分割し、構造化するがソフトウェア開発/設計の心臓部と言えます。同じ処理はできる限り繰り返さない機能を分割し、構造化するこの2つの概念が一緒になると”プログラム内部の一箇所にセキュリティ対策が集中”する”SPOF”だらけの”脆弱な構造”のソフトウェアになります。機能だけに着目し効率化を目指したソフトウェアだとSPOFを多数作ってしまいます。そこでソフトウェアの基本構造に着目した多層防御構造が必要になります。セキュアコーディングの構造/原理/原則まとめ基本的なセキュリティ対策はゼロトラストフェイルファースト﻿の考え方で開発者自身が導き出せます。歴史的/文化的な理由から無視されていることが多い多層防御も意識して取り入れると、効果的/効率的なソフトウェアセキュリティ対策を開発者自身が行えるようになります。セキュアコーディングの原則を知らなくても、自然と原則を実践できるようになります。今のソフトウェアセキュリティ対策の基本と構造はベストプラクティスからは程遠い状態です。GDPR施行など、セキュアな構造でないアプリケーションを開発/運用するリスクは年々高まるばかりです。基本さえ押さえておけば、裁判沙汰になっても不利にならないソフトウェアを作ることはそれほど難しいことはありません。起きてはならない場合、トランザクションはシリアライザブルPHPのOpenSSL関数を利用して暗号化する例2要素認証のTOTPとHOTP、どちらがより安全か?完全なSQLインジェクション対策JSONPは危険なので禁止インジェクション攻撃は3種類ある紹介エレクトロニック・サービス・イニシアチブのWebシステムのコンサルタントのブログです。PHPの開発、PHP4.x/5.xレガシーPHPセキュリティパッチサービスなどを行っています。ブログのサブタイトルを「書かない日記」としているのは、ブログを始めた時にあまり書かないつもりで始めたのでこのサブタイトルになっています。yohgaki@ohgaki.net私が記述したブログ中のコードは記載が無い場合はMITライセンスです。その他のコードは参考リンクなどのライセンス情報を参照ください。