ソフトウェアの品質を学びまくる2.0

ソフトウェアの品質、テストなどについて学んだことを記録するブログです。旧ブログからゆっくり移行中です。http://blog.livedoor.jp/prjmng/

【翻訳】ソフトウェアセキュリティに関するブラックボックステスト技法

はじめに

 Software Testing Geniusという天才的な名前のブログに、以下のエントリーがありました。

www.softwaretestinggenius.com

 ソフトウェアテスト技法ポジショニングマップでいう上象限のお話で、テスト設計より後の、自動テストとして実行できるテストのカテゴライズのお話でした。聞いたことのないものもあり、勉強になります。

 筆者の方から、「参照元をちゃんと表示しておいてくれれば記事を使ってもいいよ」って許可をいただきましたので、翻訳を載せてみます。
 訳が疑わしい場合は、申し訳ありませんが原文をあたってくださいね!誤りを指摘していただけるとなお嬉しいです。恥ずかしながら、特に(4)、(8)あたり内容の理解が・・・。
 なお、本文中の太字はわたしが付与したものです。

ソフトウェアセキュリティに関するブラックボックステスト技法

 eビジネスやeコマースの必要性が高まるにつれて、またWebにアクセスするアプリケーションが増えるにつれて、システムがセキュアであることの必要性も増している。
 セキュリティテストの専門家も、攻撃者に見つけられる前にセキュリティホールを塞ぐための「攻撃駆動テスト」(attack-driven testing)を計画することに長けてきている。セキュリティの欠陥は、偶発的なミスやふざけ過ぎから深刻な犯罪まで、様々な理由に起因する。

 セキュリティテストで使われるブラックボックス的技法について述べる前に、ファズテストとして知られている重要なテスト技法について洞察しておこう。

 ファジングあるいはファズテストとは、ソフトウェアのブラックボックステストの手法だ。Wisconsin大学のB Miller教授によって開発され、広められたのは、1988年に遡る。

 ファジングはソフトウェアの強力な自動テスト手法である。セキュリティホールとなりうる脆弱性が存在しないことを確かめるために、アプリケーションへの入力値として境界値や無効値を網羅する。ファイル・ネットワークプロトコル・APIコールなどから同じようにデータを取得することができる。

 セキュリティのテストに用いられるブラックボックス的技法は、以下のようなものである。

ファズテスト(Fuzz Testing)

 システムをクラッシュさせるために大量のデータを注ぎ込むことにより、ソフトウェアやネットワーク、OSのセキュリティや信頼性についての問題点を暴き出すのがファジングの方針である。システムに投入されるランダムなデータは、「ファズ」と呼ばれる。

 問題の箇所をテスト担当者が見つけられれば、ツールはその問題の原因になりそうな点を示すために展開される。このようなツールはファザー、あるいはファズテスターと呼ばれる。ファジングは、ソフトウェアセキュリティや、ソフトウェアテストエンジニア、QAエンジニアにより広く使われている。

負荷テスト(Load Testing)

 QA部門が直面する攻撃のうち、もっともよくある状況が、「サービス拒否」(Denial of Service: Dos)だ。Dos攻撃の大半は、負荷に基づくものである。
 負荷テストでは、テストケースの高速な繰り返しや、複数のテストのパラレルな実行により、システムの性能的な限界をテストする。これらのテストがファズテストになっていることもある。ファズテストを高速に繰り返すと、ファジングのツールでゆっくり実行していたときには見つからなかったような問題が見つかることもある。メモリリークや性能的な問題がその例だ。

 テストケースが問題を示したら、そのテストケースを取り出して、大体のツールがもっているレコード・プレイバック機能によって性能テストツールに読み込ませることもできる。
 ゲートウェイやファイアウォールのようなプロキシ部分をテストする場合には、負荷テストのまた別のご利益がある。

 負荷生成ツールがファジングツールと並行して使われる場合、負荷に対するシステムの耐性の変化を測る助けにもなるだろう。負荷のある状態で行われるファズテストでは、テスト結果が異なることもある。稼働中のサーバが攻撃を受けた際にはそれがあたりまえのシステム負荷となるので、このような結果は現実的なものだ。

ストレステスト(Stress Testing)

 必要なリソースへのアクセスを制限することで、テスト対象システム(System under test: SUT)の実行環境を変えるのがストレステストである。変えるものの例としては以下が含まれる。

  • 利用可能なメモリのサイズ・速度
  • 利用可能なディスクのサイズ・速度
  • 利用可能なプロセッサの数や処理速度
  • 環境変数

 ストレステストは、サンドボックスやソフトウェア開発シミュレーションのような制御された環境でテスト対象システムを動かすことのできる、自動テストのフレームワークの中で実行されることが多い。

セキュリティスキャナー(Security Scanners)

 ソフトウェア開発でセキュリティスキャナーを使うことは一般的だが、顧客要件の誤解によることが大半だ。顧客が明確に要件を強調すれば、開発者がそのプロジェクトで使えるのはNessusのような脆弱性スキャナーくらいに限られるだろう。このようなケースでは、スキャンはソフトウェア開発のプラクティスの一部となる。

ユニットテスト(Unit Testing)

 利用可能なインタフェースを通じてソフトウェアの最小の部品をテストするユニットテストは、ファジングを導入する最初の箇所である。

 ユニットテストでは、実際のアプリケーションの内部で使われているモジュールがテスト対象システムとなる。機能のいろいろな部分がプロトタイプのパーツの中で実装されたり、アプリケーションの実際のロジックがバイパスされたりする。本来の実装がまだ利用できない場合や、ターゲットがたまたまコーデックやファイルパーサーである場合などで、このようなことが起こる。

 たとえばHTMLパーサーをテストする際には、必ずしもwebブラウザ全体にテストを走らせたいわけではないのであって、テストドライバーを通じてHTMLパーサーのAPIコールを使うことができる。そういう設定では、驚くほど高速なファジングテストを簡単に実現することができる。

欠陥注入(Fault Injection)

 欠陥注入は、ほぼ同義語であるファジングに関する技術の一つである。

 欠陥注入という言葉は、人為的な欠陥をプリント回路基板上に入れ込むハードウェアのテスト技法を参考にしている。たとえば、接続が短絡している、壊れている、アースされている、事前に決めた「0」「1」といった値から動かないなど。そのうえでプリント基板を使ってみて、振る舞いを観察する。その目的は、製造や製品ライフサイクルの中で起きる欠陥に対する、ハードウェアの耐性や敏感さをテストすることである。

 欠陥注入は、運用中のハードウェアの振る舞いを予測したり、欠陥に対してハードウェアをよりロバストにするための指針を示すために使われる。

シンタックステスト(Syntax Testing)

 シンタックステストは、プロトコルのような形式的なインタフェースをテストするのに用いられ、ファジングにまで拡張される。

 公共へのインタフェースをもっているシステムには堅牢性が求められ、様々な入力に対する十分な妥当性チェックを経ていなくてはならない。  人々の中には悪意あるユーザがいる。システムに対して妙なことをして喜ぶような腹立たしい人間だ。その一人のたった数時間の攻撃は、数年間の通常の使用や、偶然見つかるバグなんかよりもひどいものなのだ。

 シンタックステストの目的は、重要なインタフェースにおける入力の妥当性チェックを備えているかを検証することだ。コミュニケーションのインタフェースはどれも、悪用されたり、データ破壊に利用される可能性がある。
 優秀なソフトウェア開発者なら、インタフェースの仕様に準拠しない、あるいはただのゴミでしかないような入力であろうとも、受け入れたり耐えられたりするようにシステムを作るだろう。一方、優秀なソフトウェアテストエンジニアは、考えられるかぎりもっともクリエイティブなゴミデータをシステムに食わせるだろう。
 シンタックステストは、ランダムでなく、インタフェースの操作・構造・セマンティクスを記述した賢いファジングプロセスを自動化する。入力の内容は、内部か外部かにかかわらず、BNF(Backus-Naur記法)のようなコンテキスト非依存の言語で記述される。

 シンタックステストの設計の戦略は、入力形式やメッセージといったコンポーネントをすべて正しいものにしたうえで、一回につき一つだけ、異常値(あるいはエラー値)を与えることである。複雑なインタフェースだとこれだけでも、数万の「汚いテスト」を生み出す。二重・三重のエラーを加えると、テストケースは指数関数的に増加してしまう。

 シンタックステストで生成されるエラーのタイプは、以下のようなものである。

シンタックス系エラー

 シンタックス系エラーとは、プログラミング言語についてのいまいましいシンタックス違反のことだ。これは、トップレベル・中間レベル・フィールドレベルといった、文法の階層の様々な場所に発生しうる。
 フィールドレベルのシンタックス系エラーのもっとも簡単なものは、任意データやランダム値からなる。中間レベル・トップレベルのシンタックス系エラーとは、必要な要素が省かれている、重複している、順番を入れ替わっている、要素やそのサブ構造をネスティングしているといったものだ。

デリミターエラー

 デリミターとは、センテンス中のフィールドの区切りを表す。アスキーコードの言語でのフィールドは普通、記号や文字や空白(スペース、タブ、LF)であり、(カンマ、セミコロンなどの)他のものとその組み合わせがデリミターとなる。デリミターは、省略したり、繰り返したり、重複させたり、特に典型的な他の記号で置き換えたりすることができる。対になるタイプの中括弧のようなデリミターでは、対にならないようにすることもできる。予想できないような間違ったデリミターを、予測できないだろう場所に加えることもできる。

フィールド値エラー

 フィールド値エラーとは、センテンス中のフィールドの値が正しくないものである。フィールド値エラーでは、数値の要素でも非数値の要素でも、境界値をテストすることができる。境界そのものの値やその周囲もチェックされる。境界の1つ下、1つ上、完全に範囲外といった値も含む。
 整数値のフィールドについてのテストでは、境界値を含むべきだ。2の累乗のプラスマイナス1という値を使うことをお勧めする。2進法系が、コンピュータにおける典型的で自然な整数の表現形式であるためだ。

コンテキスト依存のエラー

 このタイプのエラーは、コンテキスト非依存のシンタックスの中では説明できない、センテンス中の特定のプロパティに関する違反である。

ステート依存のエラー

 ソフトウェアのコンポーネントにおける各ステートで、すべてのセンテンスが許されているわけではない。ステート依存のエラーとはたとえば、センテンスとしては正しいが、正しくないステートの中に現れているというものである。

ネガティブテスト(Negative Testing)

 ネガティブテストは多くの形で現れる。もっともよくあるのが、ユースケースについてネガティブテストを定義することだ。
 たとえば、認証機能を実装したのであれば、ポジティブテストには、有効なユーザ名と有効なパスワードを試すことが含まれるだろう。それ以外のものがネガティブテストで、間違ったユーザ名、間違ったパスワードや他の人のパスワードなどなどを含む。
 手動でのネガティブテストの戦術の様々な形を説明するかわりに、自動化されたネガティブテストの実行方法にフォーカスしよう。

 堅牢性テストの目的はネガティブテストを行なってみることであり、テスト対象のテストが返すレスポンスはまったく意に介さない。堅牢性テストは、ネガティブテストのモデルベースドなアプローチである。テストケースやテスト手順は、コンピュータに理解できるように記述されたユースケース(=モデル)やテンプレートに基いて生成される。モデルは、インテリジェントタグを組み込まれた様々な動的操作とともに、メッセージやそのシーケンスといった、プロトコルのビルディングブロックからなる。各メッセージは、プロトコルのフィールドの集まり、定義されたシンタックス(書式)をもつ要素、プロトコル仕様の中で定義されたセマンティクス(意味)からなる。

 堅牢性テストは、シンタックステストの技法を用いてネガティブテストを行う、自動化された手法である。
 ファジングとの大きな違いは、堅牢性テストがランダムさをまず含まないという点だ。既知の破壊的あるいは異常なデータのセットをモデルに適用することで、テストがシステマティックに生成される。テストドライバー・テストデータ・テストドキュメントや、監視ツール・テストコントローラといったテストベッドへのアクセスに必要なインタフェースからなるテストツールに、作成されたテストがビルドされる。
 堅牢性テストは、バイナリ形式のテストケースや、他のテスト自動化フレームワークで利用するための説明からなるテストスイートとして出力することもできる。最初からビルド済みの堅牢性テストは常に再実行可能で、人間の関与が最小になるような形で自動化されている。

リグレッションテスト(Regression Testing)

 ソフトウェアをリリースすればテストが終わるわけではない。リリース後も修正やアップデートが要るので、新バージョンやパッチが新しい欠陥を含んでいたり、古い欠陥をまた埋め込んだりしていないかの検証が必要である。
 リリース後のテストはリグレッションテストとして知られている。リグレッションテストは、十分に自動化され、高速でなくてはならない。また、安定していること、設定が可能であることも必要だ。リグレッションテストの修正が非常に難しいとなると、コミュニケーションインタフェースの小さなアップデートがすべてのテストを無効にする結果となりうる。

 リグレッションテストは、ソフトウェアテストに適用される以下の2つの法則に関係する。

  1. ソフトウェア開発で使用されるすべてのテスト手法や、リグレッションテストで実装したすべてのテストケースは、それらの通用しない小さなバグを残してしまう。わたしたちは常に、新しい技法とテストをプロセスに組み込まないといけない。
  2. ソフトウェアの複雑さ(それはバグの複雑さでもある)は、わたしたちの能力で管理できる限界まで成長する。「簡単な」バグを潰すことで、より細かいバグが数を増やし、重大なものになるレベルにまで複雑さを増すことを許すことになる。

 テストをすればするほど、ソフトウェアはテストに対する免疫をつけることになる。そんなソフトウェアを治療するには、ソフトウェアの違う部分を鍛えるために、新しく違ったテストを継続的に書くことだ。新しい欠陥が見つかればいつでも、個々のバグを分析し、そのバグや類似のミスを見つけるための、よりシステマティックなアプローチがないかを調べることが大切だ。
 リグレッションテストで見つかった欠陥についてのファジングを組み込む際によくある間違いは、その欠陥の派生パターンを防ぐためのテストケース群をリグレッションテストのデータベースに組み込むことがより堅牢な方法だというのに、たった一つのテストしか追加しないことだ。

 よってリグレッションテストでは、固定値や非決定性の値、マジックナンバーを避けるべきだ。セキュリティ関係の悪い例は、バッファオーバーフローのリグレッションテストを、一つの決まった長さについて行うことだろう。当初は200文字の文字列について引き起こされた欠陥が、後には201文字で起こる変種として再登場するかもしれない。
 テストの修正が、ソフトウェアの最新リリースに含まれるバグを見逃すことになるのもよくない。新しく見つかった問題も捉えられるように、リグレッションテストを定期的にアップデートすべきだ。

 リグレッションの欠陥のデータベースは、過去の失敗についての素晴らしい概観を与えてくれる。開発者にとっても他のテスト担当者にとっても、きわめて貴重な情報だ。
 リグレッションのデータベースは学習の観点から、定期的なレビューと分析が必要である。バグのデータベースは、致命的な欠陥や、それによって起こりうるセキュリティ面での結果についての価値ある情報を明らかにしてくれる。それ自身が、多彩な製品の品質に関するメトリクスなのだ。

ファジングの利点

  1. ファジングは、ソフトウェア製品に含まれる多くのセキュリティの問題を発見することのできる、非常にパワフルなテスト手法である。
  2. ファジングはコードに関する問題を数多く見つけられるという点で、監査用のあまたのコードツールよりもずっと優れている。賢く計画されたファジングと、本格的なコード監査の科学的な比較調査からも、ファジングの方を支持するという結論に至るだろう。
  3. コード監査用のツールを使っての結果の大半は、誤って陽性と報告されたものであり、事実上セキュリティ的な脅威をもたらさない。ファジングは擬陽性のような問題を指摘しない。クラッシュはクラッシュとして正しく検出され、バグはバグとして正しく検出される。
  4. ファジングで検出された問題はすべて、当然インタフェースへのファジングによって、リモートで悪用されうる。
  5. ファジングは私的なシステム、入手可能な既製のソフトウェア、クローズドソースのアプリケーションの分析を行うのにも助けになる。大半のケースでそういえる理由は、ソースコードへのアクセスが必要ないからだ。

おわりに

 テスト技法同士の関係によくわからない点があったり、ファジングの話なのか別の話なのかさまよう箇所もあるのですが、とにかくいろいろな種類のテスト技法があることはわかりました。というより、一部間違って覚えていましたw