ソフトウェアセキュリティのアンチパターン

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

アンチパターンを知ることにより失敗を防ぐ。これはデータベース設計やソフトウェア設計で多く利用されている手法です。今回はソフトウェアセキュリティのアンチパターンを紹介します。

このエントリは不定期にメンテナンスするつもりです。

ソフトウェア以外のセキュリティ(リスク)を考慮しない

ニュースで大きく報道されるなセキュリティ問題の原因が、物理的なモノ(人、デバイスなど)やネットワークのセキュリティ問題であることは多いです。セキュリティ対策がなかったり、あっても著しく不十分だったりする場合があります。

ソフトウェアの信頼境界限界は同一プロセス/スレッドまでです。基本的にこれを越えるモノは全て信頼できません。信頼するには信頼可能にする為のセキュリティ対策が必要になります。セキュリティ対策後に”残ったリスク”を正確に識別して評価/対応するプロセスが無い/不十分であると致命的な問題になる場合があります。

参考: なぜセキュアなシステムが作れないのか?

インターネット上のWeb環境の場合、クライアントは一切信頼できません。デバイス(PCや携帯)のセキュリティが維持されている状態を保証することも利用者の選択もできません。ネットワークも一切信頼できません。これはHTTPSを使っていても同じです。例えば、HTTPS通信も復号している企業は多く存在し、WiFi接続でDNSが攻撃者が用意した物1である可能性もあります。攻撃者に管理者権限まで取られているとHTTPSには意味がありません。これらはどうしようもないので、クライアントは一切信頼できない、を前提に作るしかありません。

 

リスクの発生源に近い場所で対策しない

ソフトウェアセキュリティの最大の脅威はインジェクション攻撃です。インジェクション攻撃は外部からのデータによって発生します。アプリケーションは外部からどのようながデータが送られてくるか制御できません。インジェクション攻撃の大半がアプリケーションが受け入れ不可能なデータ(=正しく処理できない不正データ)のインジェクションによって発生します。

リスクの発生源に最も近い場所でリスクに対応すると最も効果が高くなります。リスクの発生源から遠くなればなるほど、様々な処理(コードの実行)が行われ、その過程でセキュリティ問題が発生する可能性を高くします。残念ながら大半のアプリケーションがリスク発生源から最も近い場所でリスクに対応できる設計になっていません。リスクの発生源に近い箇所とは入力と出力の部分です。

  • 入力 – 攻撃用データを受け付ける部分
  • 出力 – 攻撃用データを出力し外部システムを誤作動させる部分

ロジックでもセキュリティ問題が発生しますが、入力と出力対策でセキュリティ問題の大部分を廃除/緩和できます。セキュアコーディングでは入力と出力の両方で、確実にリスクを廃除/緩和します。

 

セキュリティの目的を考慮していない

セキュリティ(安全性)を確保する目的は”安全性を必要としているモノを利用する事”が目的です。ITシステムの場合、

  • リスクを許容可能な範囲に抑えてITシステムを利用する

が目的です。ITシステム”全体”として利用するリスクを許容範囲内に抑えなければ、ITシステムの利用は不合理であることになります。セキュリティ対策の目的が”脆弱性対策をする”に入れ替わってしまっていると思われる事例は少くありません。”脆弱性対策”は”目的”を達成する為の手段に過ぎません。手段と目的の勘違いはよくある間違いで、ITセキュリティ対策でもよく見られます。

無計画に手段(脆弱性対策)だけを導入しても目的は効率良く達成できません。目的を明確にし、適切な手段を体系的に取り入れる、これを行わないと無駄と無理が発生します。

参考: セキュリティ対策の目的と手段たった2つの質問で判る、ITセキュリティ基礎知識の有無

ソフトウェアの安全性を考慮する場合、目的をもう少し狭めて理解り易くできます。ソフトウェアのセキュリティ問題の大半はインジェクション攻撃によるソフトウェアの誤作動です。ソフトウェアの誤作動に着目する場合、目的を以下のように設定できます。

  • ソフトウェアを確実に正しく(開発者の意図通り)に動作させる

この目的は後述する「ブラックリストを利用する」アンチパターンとならないよう、ホワイトリスト型の思考で設定しています。「ソフトウェアを正しく(開発者の意図通り)に動作させる」を目的とするとセキュアコーディングの原則も理解しやすくなります。2:

ブラックリスト型で考えると

  • ソフトウェアに脆弱性を作らない

といった目的を設定してしまいがちです。しかし、この考え方では十分な安全性を確保できないことがここ20年、30年くらいで実証済み3です。過去20年、30年の間、ほとんどのソフトウェアは「ソフトウェアに脆弱性を作らない」をセキュリティ対策の目的として作られてきましたが、残念ながら十分に安全になったとは言えないのが現状です。つまり「ソフトウェアに脆弱性を作らない」(≒ 脆弱性を潰していく)という考え方もアンチパターンの1つです。

 

リスク発生箇所の一部に対応して十分だと勘違いする

次の「セキュリティ対策の構造(設計)を考えない」と関連しています。プログラムの構造は大別すると”入力処理”、”ロジック処理”、”出力処理”に分けられます。効率的にリスクに対応可能な最大の箇所は”入力処理”4、次が”出力処理”、最後が“ロジック”です。

セキュリティ対策は不測の事態も想定する物です。全体が容易に見通せる単純なプログラムなら一箇所/一部だけでリスク対策を行っても不測の事態(対策漏れ、利用するライブラリなどの問題、変更による脆弱性の追加/変化など)が発生するリスクを許容範囲内に抑えることも可能です。しかし、ある程度の規模のソフトウェアになると不測の事態が発生するリスクは許容できない状態になります。

例えば、単純なアプリで単純なSQLしかないシステムのSQLインジェクション対策ならパラメーター対策だけでも十分です。しかし、通常の業務アプリケーションなどではパラメーター対策だけだと全く不十分です。

参考: セキュリティ対策が論理的に正しいか検証する方法

参考リンクの論理的証明から分かる様に、SQLインジェクション対策をプリペアードクエリ/プレイスホルダだけ(=静的SQLだけ)で実現しようとするとダメなコードを書かなければなりません。ソート対象のカラムが10ある場合に、ほぼ同じプリペアード文を10書く、といったコードがダメなコードであることに異論がある開発者は居ないでしょう。更に、プリペアードクエリ/プレイスホルダだけのSQLインジェクション対策が不十分であることは容易に証明もできます。しかし、「プリペアードクエリ/プレイスホルダだけでOK」と勘違いしている事例に事欠きません。

因みにセキュアコーディング第一のベストプラクティスは”全ての入力をホワイトリストでバリデーションする”です。説明は省略しますが入力対策も論理的にも実証論的にも不十分であることが容易に証明可能であるにも関わらす、十分な対策になっていないソフトウェアばかり(=アンチプラクティスを実行している)、が現実です。

参考:CERT Top 10 Secure Coding Practices

 

セキュリティ対策の構造(設計)を考えない

セキュリティ対策の構造設計は信頼境界線を引き、境界線上で

  • 廃除するリスク
  • 残存するリスク(増えるリスクは忘れがちなので注意!)

を識別し、どのように対応/管理するのか?を計画する作業です。

フレームワークなどを使えばこの作業を簡略化できます。しかし、何も考えなくて良い、にはなりません。”廃除したリスク”と”残存するリスク”を識別するにはリスクそのものを識別しなければなりません。どんなプログラムであっても、通常は”入力”、”処理”、”出力”の3つに分けられます。”入力”、”処理”、”出力”、それぞれに適当なリスク廃除/緩和策を行い、残ったリスクを管理します。通常、残ったリスクで許容できないリスクは”多層防御”で対策します。セキュリティ対策の基礎ですが全く考えられていないケースが多数あります。

参考:信頼境界線の引き方と防り方 – セキュリティの構造と設計

ソフトウェアセキュリティの構造設計だけではなく、物理的/ネットワーク的なセキュリティ設計も欠かせません。物理的/ネットワーク的なセキュリティ設計の欠如は、致命的なセキュリティ問題の原因になります。物理的/ネットワーク的なセキュリティ設計の欠如が原因で致命的な問題が発生した例は大手企業が作ったシステムであっても、枚挙にいとまがありません。

 

ブラックリストを優先する

ブラックリスト型のセキュリティ対策は基本的には利用すべきではありません。セキュリティ対策にはホワイトリストを使うべきです。

  • ブラックリスト – 許可しないモノ、悪いモノを定義して禁止する
  • ホワイトリスト – 許可するモノ、良いモノを定義して許可する

ブラックリストは仕組みとして脆弱です。悪いモノを定義する場合、全ての悪いモノを列挙しなければなりません。全ての悪いモノを列挙するプロセスはミス/漏れが発生しやすいか、必ず漏れが発生します。このため、どうしようもない時以外はブラックリストは使わない、がセキュリティ対策の基本です。

参考: ホワイトリストとブラックリスト – Firewallの場合

黎明期のWebアプリケーションがJavaScriptインジェクションに対して多数の脆弱性を持っていた最大の原因は「ブラックリスト型の考え方で危険な変数のみエスケープしていた」ことにあります。現在のWebアプリケーションのテンプレートシステムは「ホワイトリスト型の考え方で、HTML出力が必要な変数のみエスケープなしで出力する」ので、昔に較べて大幅にJavaScriptインジェクション脆弱性が減りました。

 

攻撃可能でない脆弱性を放置する

セキュリティ脆弱性は必ずしも攻撃可能ではありません。脆弱であっても即座に攻撃可能とならない脆弱性、単体では攻撃可能にならない脆弱性も多数存在します。セキュアコーディングの考え方に従うと攻撃可能かどうか?に関係なく脆弱なコードを可能な限り廃除します。脆弱なコード、例えばエスケープ可能なのにエスケープしないで変数を出力する、は放置すべきではありません。セキュアコーディングでは入力と出力では全ての変数を厳密に安全であることを検証し保証します。攻撃可能ではない(と思う)からといって脆弱な状態で放置しません。

※ ここでいうセキュアコーディングとはCERTのセキュアコーディングを指しています。IPAではセキュアプログラミング講座としてセキュアコーディングとしては間違いだらけの資料を2016年まで公開していました。これではありません。

入力・出力処理以外にも脆弱なコードが存在します。通常、「攻撃可能かどうか?に関係なく脆弱なコードをすべて廃除」はできません。”脆弱なコードを全て廃除”しようとした場合、コードサイズやソフトウェア実行性能が要件を満たさなくなり「リスクを許容可能な範囲に抑えてITシステムを利用する」目的が達成できなくなる場合が多いからです。”脆弱なコードをすべて廃除したプログラム”は契約プログラミングを行い、開発モードでプログラムを実行した場合の状態になります。

すべての脆弱なコードを廃除する、は現実的ではありません。そこで、ソフトウェアのセキュリティ設計が必要になります。”脆弱なコード”とは関数/メソッドレベルで厳密に 入力/出力/状態を検証していないコードのことを指しています。契約プログラミングの検証を行わないコードのことを言っています。

参考: エンジニア必須の概念 – 契約による設計と信頼境界線

すべての脆弱なコードを廃除することはできませんが、計画的/安全に残すことは可能です。脆弱なコードは計画的/安全に残すという考え方が必要です。

 

セキュリティ専門家のアドバイスに従わない

一般開発者に対してソフトウェアセキュリティ専門家(コンピュータサイエンティスト)はセキュリティの基礎/基本は1990年代始めから啓蒙しています。

参考:セキュアプログラミング(防御的プログラミング)の歴史をざっと振り返る

防御的プログラミングの概念は60年代くらいからのプログラム実行の正しさを検証/保証する研究に基づいています。セキュアプログラミング(防御的プログラミング)の論理的な正しさに議論の余地はありません。にも関わらず多くの開発者、一部のセキュリティ専門家はソフトウェアセキュリティの専門家のアドバイスを軽視したり、無視しています。

参考:セキュアプログラミング第一位の対策、入力バリデーションはセキュリティ対策ではない?!

PHP 7.1.2からHMACを利用した鍵導出関数(HKDF – RFC 5869)が追加されました。RFC 5869はHKDFが強い鍵を導出する為に必ずsaltを利用することを推奨しています。例外はsaltの利用ができない時に限るとしています。しかし、PHPに追加されたHKDF実装ではsalt引数が最後のオプション引数として実装され、”例外を除き、必ずsaltを利用するAPI設計”になっていません。”脆弱な鍵導出を行いなさい”と推奨するAPI設計になっています。このAPI設計ミスはリリース版マージ前と後に指摘したのですが、開発者の誤解によりこうなってしまいました。こういった事例は少くありません。

参考:出鱈目なシグニチャのhash_hkdf関数を安全に使う方法

 

入力バリデーションを信頼境界で行わない

これは、上記アンチパターン

  • リスクの発生源に近い場所で対策しない
  • リスク発生箇所の一部に対応して十分だと勘違いする
  • セキュリティ対策の構造(設計)を考えない
  • 攻撃可能でない脆弱性を放置する
  • セキュリティ専門家のアドバイスに従わない

と関連するアンチパターンです。Cert Top 10 Secure Coding PracticesCWE/SANS Top 25 Monster MitigationsOWASP Secure Coding Practices Quick Reference Guideの第一のセキュリティ対策は入力バリデーションです。そして信頼境界での入力バリデーションが最も重要です。第一のセキュリティ対策を行わないのは、明らかにアンチパターンです。

 

まとめ

ここに挙げたアンチプラクティスは概ね情報セキュリティ標準やセキュリティガイドラインに記載されているベストプラクティスの反対の物になっています。他にも色々ありますが、特に気になるアンチプラクティスをまとめてみました。


  1. 不正なDNSなどの攻撃を防止/緩和する対策としてEV SSL、HSTSがあります。しかし、EV SSL、HSTSの利用率は高いとは言えません。 
  2. ソフトウェアを正しく(開発者の意図通り)に動作させる」だけを目的とした場合、ITセキュリティの目的である「リスクを許容可能な範囲に抑えてITシステムを利用する」の達成が危うくなります。あくまでも十分なITセキュリティを達成する為に利用する副次的な目的である事に留意。 
  3. 残念ながら90年代はじめから提唱されていた防御的プログラミングも、2000年代頃から提唱されたセキュアコーディングも十分に広く普及した、とは言えません。少なくとも防御的プログラミング/セキュアコーディングが徹底されていた、とは到底言えません。 
  4. 例えば、入力処理でハッシュ値がa-f, 0-9の32文字であることをあること検証/保証すれば、インジェクション攻撃による外部システムが誤作動するリスクはほぼゼロになります。 

投稿者: yohgaki