プロジェクト

全般

プロフィール

Rule of functional test for 36x » 履歴 » リビジョン 3

リビジョン 2 (Kousuke Ebihara, 2010-10-28 15:33) → リビジョン 3/13 (Kousuke Ebihara, 2010-10-28 16:20)

h1. Rule of functional test for 36x 

 h2. この文書について 

 OpenPNE 3.6.0 をリリースするために、 http://www.openpne.jp/archives/5532/#m-5 に記述されている、「CSRF と XSS に脆弱でないかどうか」という「最低限度のプログラマテストが機能しているような状態」に持って行くことを目的として、そのために必要な作業についての手順等を、作業者に向けて示すものです。 h2. 目的 

 h2. 作業の目的 対象 

 残念ながら、現時点での OpenPNE 開発チームのテスターのリソースが足りていません。テスターは、http://www.openpne.jp/archives/5532/#m-8 に記載されているような、「全機能に関する正常動作の動作テスト」を実施し、正常動作に関する安定動作を保証できるようにするのが精一杯です。 

 CSRF や XSS に脆弱でないかどうかのテストも非常に重要なのですが、少ないリソースで「全機能に対する正常動作のテスト」と「全機能の CSRF や XSS に関するテスト」を両立することはできません。 

 そこで、重要度の高い CSRF や XSS に関するテストだけでも、プログラマテストでカバーできるようにしよう、というのがこの作業の目的です。 

 h2. 事前準備 

 * 実際のコーディング作業の実施のために、 http://github.com/openpne/OpenPNE3 を fork してください 
 * 作業の進捗管理に使用しているスプレッドシートを更新するために、招待メールを希望のメールアドレスに送ります。希望するメールアドレスを、 ebihara@tejimaya.com に知らせてください。 

 h2. 作業手順 

 作業の進捗管理に使用しているスプレッドシート(https://spreadsheets.google.com/pub?key=0Ain-euBnqQDLdGxzMnoyOFhBaUlQUVJnS0Y0YXNzZlE&hl=en&output=html)を開き、 XSS / CSRF の欄が空のものに対してテストをおこないます。 h3. すべてのテストに共通の手順 

 テストを書きたいアクションに対して、 GitHub における自分のユーザ名を XSS / CSRF の欄に記入してください。 

 その後、自分の OpenPNE 3 のリポジトリの stable-3.6.x ブランチ (もしくはそこから派生させたブランチ) に対して作業をおこなってください。 

 作業が完了したら、ある程度まとまった段階で、 ebihara 等に pull request して知らせてください。 

 修正を確認し、 openpne/OpenPNE3 に取り込んだ段階で、海老原がスプレッドシートの XSS / CSRF の欄を灰色にします。 

 h3. XSS 脆弱性に関するテスト手順 

 テストしたいアクションの全テンプレートを確認します。テンプレート中にユーザ入力値に基づいて動的に生成される箇所があれば、その出力についてテストを記述する必要があります。 

 本来はユーザ入力値に限らず、動的な値を埋め込むすべての場所に対して、 HTML 出力を意図している場合を除いて、 HTML 特殊文字のエスケープがおこなわれているかどうかを確認するべきですが、作業量や難易度等を考慮し、今回はそこまではおこなわず、あくまでユーザ入力値に限定します。 

 h4. DB 内データの出力に関するテスト 

 まず、そのテンプレート中でモデルから得られる値を埋め込んでいるすべての箇所を列挙します。 

 XSS 脆弱性テスト用のテストデータ (test/fixtures/xss_test_data.yml) を確認し、テストに必要な情報がなければ作成します。 

 テストデータは以下のような形式になっています。 

 <pre> 
 Member: 
   html_member_1: 
     id: 1055 # it means "XSS (X-55)" 
     name: "<&\"'>Member.name ESCAPING HTML TEST DATA" 
     is_active: 1 
 </pre> 

 文字列を受け入れるフィールドに対して、テスト用の文字列を指定する以外には普通の fixture と変わらずに記述できます。 

 テスト用の文字列は、かならず、以下のような書式でなければなりません。 

 <pre> 
 <&"'>モデル名.カラム名 ESCAPING HTML TEST DATA 
 </pre> 

 こうして挿入されたテストデータが表示されるアクションのためのテストにおいて、 opTesterHtmlEscape が提供するメソッド群を利用することで、意図通りのエスケープがおこなわれているかどうかを確認することができます。 

 そのアクションの出力に存在する、「Member.name」の値がすべてエスケープされているかどうかを確認するには、以下のように opTesterHtmlEscape::isAllEscapedData() を実行します。 

 <pre> 
 $browser = new opTestFunctional(new opBrowser(), new lime_test(null, new lime_output_color())); 
 $browser 
 ->info('member/profile') 
 ->get('member/1055') 
 ->with('html_escape')->begin() 
   ->isAllEscapedData('Member', 'name') 
 ->end() 
 </pre> 

 反対に、そのアクションの出力に存在する、「Member.name」の値がすべてエスケープ*されていないかどうか*を確認するには、 opTesterHtmlEscape::isAllRawData() を実行してください。 

 <pre> 
 $browser = new opTestFunctional(new opBrowser(), new lime_test(null, new lime_output_color())); 
 $browser 
 ->info('member/profile') 
 ->get('member/1055') 
 ->with('html_escape')->begin() 
   ->isAllRawData('Member', 'name') 
 ->end() 
 </pre> 

 また、モデルの値の出力が op_truncate() によって truncate される場合は、 opTesterHtmlEscape::countEscapedData() や opTesterHtmlEscape::countRawData() を用いてください。これは、モデル名やカラム名の他に、期待するデータの数や op_truncate() に渡されている引数も受け付けます。以下は、 op_truncate($string, $width = 36, $etc = '', $rows = 3) を使用したモデルの値の出力が 3 つ存在することをテストする場合の例です。 

 <pre> 
 $browser = new opTestFunctional(new opBrowser(), new lime_test(null, new lime_output_color())); 
 $browser 
 ->info('member/profile') 
 ->get('member/1055') 
 ->with('html_escape')->begin() 
   ->countEscapedData(3, 'Member', 'name', array( 
     'width' => 36, 
     'etc'     => '', 
     'rows'    => 3, 
   )) 
 ->end() 
 </pre> 

 h4. それ以外のユーザ入力値に関するテスト 

 それ以外のユーザ入力値についても、変則的ではありますが、同じようにしてテストをおこなうことができます。 

 たとえば、アクションの出力中に含まれるリクエストパラメータ html の出力をテストしたい場合、以下のように記述してください。 


 <pre> 
 $browser = new opTestFunctional(new opBrowser(), new lime_test(null, new lime_output_color())); 
 $browser 
 ->info('member/profile') 
 ->get('member/1055', array('html' => opTesterHtmlEscape::getRawTestData('request', 'html'))) 
 ->with('html_escape')->begin() 
   ->isAllEscapedData('request', 'html') 
 ->end() 
 </pre> 

 これにより、 <&"'>request.html ESCAPING HTML TEST DATA という値の出力をテストすることになります。 

 opTesterHtmlEscape::getRawTestData() および opTesterHtmlEscape::isAllEscapedData() の第一引数や第二引数の値は、そのアクション内でユニークなものになっていればなんでも構いません。 

 h3. CSRF 脆弱性に関するテスト手順