プロジェクト

全般

プロフィール

Rule of functional test for 36x » 履歴 » バージョン 13

Kousuke Ebihara, 2014-01-30 20:05

1 1 Kousuke Ebihara
h1. Rule of functional test for 36x
2 2 Kousuke Ebihara
3 11 Kousuke Ebihara
{{>toc}}
4 11 Kousuke Ebihara
5 2 Kousuke Ebihara
h2. この文書について
6 2 Kousuke Ebihara
7 3 Kousuke Ebihara
OpenPNE 3.6.0 をリリースするために、 http://www.openpne.jp/archives/5532/#m-5 に記述されている、「CSRF と XSS に脆弱でないかどうか」という「最低限度のプログラマテストが機能しているような状態」に持って行くことを目的として、そのために必要な作業についての手順等を、作業者に向けて示すものです。
8 2 Kousuke Ebihara
9 3 Kousuke Ebihara
h2. 作業の目的
10 1 Kousuke Ebihara
11 3 Kousuke Ebihara
残念ながら、現時点での OpenPNE 開発チームのテスターのリソースが足りていません。テスターは、http://www.openpne.jp/archives/5532/#m-8 に記載されているような、「全機能に関する正常動作の動作テスト」を実施し、正常動作に関する安定動作を保証できるようにするのが精一杯です。
12 3 Kousuke Ebihara
13 3 Kousuke Ebihara
CSRF や XSS に脆弱でないかどうかのテストも非常に重要なのですが、少ないリソースで「全機能に対する正常動作のテスト」と「全機能の CSRF や XSS に関するテスト」を両立することはできません。
14 3 Kousuke Ebihara
15 3 Kousuke Ebihara
そこで、重要度の高い CSRF や XSS に関するテストだけでも、プログラマテストでカバーできるようにしよう、というのがこの作業の目的です。
16 3 Kousuke Ebihara
17 1 Kousuke Ebihara
h2. 事前準備
18 1 Kousuke Ebihara
19 3 Kousuke Ebihara
* 実際のコーディング作業の実施のために、 http://github.com/openpne/OpenPNE3 を fork してください
20 12 Kousuke Ebihara
* 作業の進捗管理に使用しているスプレッドシートを更新するために、招待メールを希望のメールアドレスに送ります。希望するメールアドレスを、 -ebihara@tejimaya.com- (not available) に知らせてください。
21 3 Kousuke Ebihara
22 2 Kousuke Ebihara
h2. 作業手順
23 1 Kousuke Ebihara
24 7 Kousuke Ebihara
h3. コア側の作業について
25 7 Kousuke Ebihara
26 6 Kousuke Ebihara
作業の進捗管理に使用しているスプレッドシート( https://spreadsheets.google.com/pub?key=0Ain-euBnqQDLdGxzMnoyOFhBaUlQUVJnS0Y0YXNzZlE&hl=en&output=html )を開き、 XSS / CSRF の欄が空のものに対してテストをおこないます。
27 1 Kousuke Ebihara
28 3 Kousuke Ebihara
テストを書きたいアクションに対して、 GitHub における自分のユーザ名を XSS / CSRF の欄に記入してください。
29 3 Kousuke Ebihara
30 3 Kousuke Ebihara
その後、自分の OpenPNE 3 のリポジトリの stable-3.6.x ブランチ (もしくはそこから派生させたブランチ) に対して作業をおこなってください。
31 3 Kousuke Ebihara
32 3 Kousuke Ebihara
作業が完了したら、ある程度まとまった段階で、 ebihara 等に pull request して知らせてください。
33 3 Kousuke Ebihara
34 3 Kousuke Ebihara
修正を確認し、 openpne/OpenPNE3 に取り込んだ段階で、海老原がスプレッドシートの XSS / CSRF の欄を灰色にします。
35 3 Kousuke Ebihara
36 4 Kousuke Ebihara
なお、 XSS もしくは CSRF のテストの必要がないものについては、 XSS / CSRF の欄を - に書き換えてください。その場合、報告の必要はありません。
37 1 Kousuke Ebihara
38 7 Kousuke Ebihara
h3. プラグイン側の作業について
39 1 Kousuke Ebihara
40 7 Kousuke Ebihara
プラグインでの作業でも、スプレッドシートの更新についてはコアと変わりません。 GitHub にプラグインが管理されている場合は、コアと同じように fork し、作業してください。
41 7 Kousuke Ebihara
42 13 Kousuke Ebihara
プラグインによっては、 GitHub ではないところで管理されているものがあります。その場合は修正作業の手順が異なりますので、 -ebihara@tejimaya.com- (not available) に相談してください。
43 7 Kousuke Ebihara
44 13 Kousuke Ebihara
なお、プラグイン側の作業は pull request せずに、 -ebihara@tejimaya.com- (not available) に報告してください。
45 7 Kousuke Ebihara
46 10 Kousuke Ebihara
h3. コアやプラグインのコンポーネントに対する作業について
47 10 Kousuke Ebihara
48 10 Kousuke Ebihara
作業の進捗管理に利用しているスプレッドシートのなかに、 _ ではじまるアクション名を持つ項目があります。これはコンポーネントを表しています。
49 10 Kousuke Ebihara
50 10 Kousuke Ebihara
コンポーネントをテストする場合、そのコンポーネントを使用しているアクションのどれかひとつに対して、テストを記述してください。コンポーネントが利用可能な状態になっていない場合は、有効な状態にしてください。
51 10 Kousuke Ebihara
52 7 Kousuke Ebihara
h3. 具体的な作業内容
53 7 Kousuke Ebihara
54 7 Kousuke Ebihara
h4. XSS 脆弱性に関するテスト手順
55 7 Kousuke Ebihara
56 3 Kousuke Ebihara
テストしたいアクションの全テンプレートを確認します。テンプレート中にユーザ入力値に基づいて動的に生成される箇所があれば、その出力についてテストを記述する必要があります。
57 3 Kousuke Ebihara
58 3 Kousuke Ebihara
本来はユーザ入力値に限らず、動的な値を埋め込むすべての場所に対して、 HTML 出力を意図している場合を除いて、 HTML 特殊文字のエスケープがおこなわれているかどうかを確認するべきですが、作業量や難易度等を考慮し、今回はそこまではおこなわず、あくまでユーザ入力値に限定します。
59 3 Kousuke Ebihara
60 3 Kousuke Ebihara
h4. DB 内データの出力に関するテスト
61 3 Kousuke Ebihara
62 3 Kousuke Ebihara
まず、そのテンプレート中でモデルから得られる値を埋め込んでいるすべての箇所を列挙します。
63 3 Kousuke Ebihara
64 3 Kousuke Ebihara
XSS 脆弱性テスト用のテストデータ (test/fixtures/xss_test_data.yml) を確認し、テストに必要な情報がなければ作成します。
65 3 Kousuke Ebihara
66 3 Kousuke Ebihara
テストデータは以下のような形式になっています。
67 3 Kousuke Ebihara
68 3 Kousuke Ebihara
<pre>
69 3 Kousuke Ebihara
Member:
70 3 Kousuke Ebihara
  html_member_1:
71 3 Kousuke Ebihara
    id: 1055 # it means "XSS (X-55)"
72 3 Kousuke Ebihara
    name: "<&\"'>Member.name ESCAPING HTML TEST DATA"
73 3 Kousuke Ebihara
    is_active: 1
74 3 Kousuke Ebihara
</pre>
75 3 Kousuke Ebihara
76 3 Kousuke Ebihara
文字列を受け入れるフィールドに対して、テスト用の文字列を指定する以外には普通の fixture と変わらずに記述できます。
77 3 Kousuke Ebihara
78 3 Kousuke Ebihara
テスト用の文字列は、かならず、以下のような書式でなければなりません。
79 3 Kousuke Ebihara
80 3 Kousuke Ebihara
<pre>
81 3 Kousuke Ebihara
<&"'>モデル名.カラム名 ESCAPING HTML TEST DATA
82 3 Kousuke Ebihara
</pre>
83 3 Kousuke Ebihara
84 3 Kousuke Ebihara
こうして挿入されたテストデータが表示されるアクションのためのテストにおいて、 opTesterHtmlEscape が提供するメソッド群を利用することで、意図通りのエスケープがおこなわれているかどうかを確認することができます。
85 3 Kousuke Ebihara
86 3 Kousuke Ebihara
そのアクションの出力に存在する、「Member.name」の値がすべてエスケープされているかどうかを確認するには、以下のように opTesterHtmlEscape::isAllEscapedData() を実行します。
87 3 Kousuke Ebihara
88 3 Kousuke Ebihara
<pre>
89 3 Kousuke Ebihara
$browser = new opTestFunctional(new opBrowser(), new lime_test(null, new lime_output_color()));
90 3 Kousuke Ebihara
$browser
91 3 Kousuke Ebihara
->info('member/profile')
92 3 Kousuke Ebihara
->get('member/1055')
93 3 Kousuke Ebihara
->with('html_escape')->begin()
94 3 Kousuke Ebihara
  ->isAllEscapedData('Member', 'name')
95 3 Kousuke Ebihara
->end()
96 3 Kousuke Ebihara
</pre>
97 3 Kousuke Ebihara
98 3 Kousuke Ebihara
反対に、そのアクションの出力に存在する、「Member.name」の値がすべてエスケープ*されていないかどうか*を確認するには、 opTesterHtmlEscape::isAllRawData() を実行してください。
99 3 Kousuke Ebihara
100 3 Kousuke Ebihara
<pre>
101 3 Kousuke Ebihara
$browser = new opTestFunctional(new opBrowser(), new lime_test(null, new lime_output_color()));
102 3 Kousuke Ebihara
$browser
103 3 Kousuke Ebihara
->info('member/profile')
104 3 Kousuke Ebihara
->get('member/1055')
105 3 Kousuke Ebihara
->with('html_escape')->begin()
106 3 Kousuke Ebihara
  ->isAllRawData('Member', 'name')
107 3 Kousuke Ebihara
->end()
108 3 Kousuke Ebihara
</pre>
109 3 Kousuke Ebihara
110 3 Kousuke Ebihara
また、モデルの値の出力が op_truncate() によって truncate される場合は、 opTesterHtmlEscape::countEscapedData() や opTesterHtmlEscape::countRawData() を用いてください。これは、モデル名やカラム名の他に、期待するデータの数や op_truncate() に渡されている引数も受け付けます。以下は、 op_truncate($string, $width = 36, $etc = '', $rows = 3) を使用したモデルの値の出力が 3 つ存在することをテストする場合の例です。
111 3 Kousuke Ebihara
112 3 Kousuke Ebihara
<pre>
113 3 Kousuke Ebihara
$browser = new opTestFunctional(new opBrowser(), new lime_test(null, new lime_output_color()));
114 3 Kousuke Ebihara
$browser
115 3 Kousuke Ebihara
->info('member/profile')
116 3 Kousuke Ebihara
->get('member/1055')
117 3 Kousuke Ebihara
->with('html_escape')->begin()
118 3 Kousuke Ebihara
  ->countEscapedData(3, 'Member', 'name', array(
119 3 Kousuke Ebihara
    'width' => 36,
120 3 Kousuke Ebihara
    'etc'   => '',
121 3 Kousuke Ebihara
    'rows'  => 3,
122 3 Kousuke Ebihara
  ))
123 3 Kousuke Ebihara
->end()
124 3 Kousuke Ebihara
</pre>
125 3 Kousuke Ebihara
126 3 Kousuke Ebihara
h4. それ以外のユーザ入力値に関するテスト
127 3 Kousuke Ebihara
128 3 Kousuke Ebihara
それ以外のユーザ入力値についても、変則的ではありますが、同じようにしてテストをおこなうことができます。
129 3 Kousuke Ebihara
130 3 Kousuke Ebihara
たとえば、アクションの出力中に含まれるリクエストパラメータ html の出力をテストしたい場合、以下のように記述してください。
131 3 Kousuke Ebihara
132 3 Kousuke Ebihara
133 3 Kousuke Ebihara
<pre>
134 3 Kousuke Ebihara
$browser = new opTestFunctional(new opBrowser(), new lime_test(null, new lime_output_color()));
135 3 Kousuke Ebihara
$browser
136 3 Kousuke Ebihara
->info('member/profile')
137 3 Kousuke Ebihara
->get('member/1055', array('html' => opTesterHtmlEscape::getRawTestData('request', 'html')))
138 3 Kousuke Ebihara
->with('html_escape')->begin()
139 3 Kousuke Ebihara
  ->isAllEscapedData('request', 'html')
140 3 Kousuke Ebihara
->end()
141 3 Kousuke Ebihara
</pre>
142 3 Kousuke Ebihara
143 3 Kousuke Ebihara
これにより、 <&"'>request.html ESCAPING HTML TEST DATA という値の出力をテストすることになります。
144 3 Kousuke Ebihara
145 1 Kousuke Ebihara
opTesterHtmlEscape::getRawTestData() および opTesterHtmlEscape::isAllEscapedData() の第一引数や第二引数の値は、そのアクション内でユニークなものになっていればなんでも構いません。
146 2 Kousuke Ebihara
147 7 Kousuke Ebihara
h4. CSRF 脆弱性に関するテスト手順
148 8 Masato Nagasawa
149 8 Masato Nagasawa
CSRFのチェックを行う場合には opTestFunctional::checkCSRF() を使用してください。
150 8 Masato Nagasawa
CSRFのエラー時の挙動は以下のいずれかに合わせる必要があり、それ以外はエラーとなります。
151 8 Masato Nagasawa
152 9 Masato Nagasawa
* #contents div:contains("CSRF attack detected.")
153 9 Masato Nagasawa
* #FormGlobalError td:contains("csrf token: Required.")
154 9 Masato Nagasawa
* p:contains("_csrf_token [Required.]")
155 9 Masato Nagasawa
(英文の箇所は言語によって変化します)
156 8 Masato Nagasawa
157 8 Masato Nagasawa
前者は sfWebRequest::checkCSRFProtection() した場合、後者はformにbind後renderした場合の実装となります。
158 8 Masato Nagasawa
159 8 Masato Nagasawa
実装例
160 8 Masato Nagasawa
<pre>
161 8 Masato Nagasawa
$browser = new opTestFunctional(new opBrowser(), new lime_test(null, new lime_output_color()));
162 8 Masato Nagasawa
~ログイン処理~
163 8 Masato Nagasawa
$browser->
164 8 Masato Nagasawa
->info('monitoring/deleteImage/id/1 - CSRF')
165 8 Masato Nagasawa
->post('monitoring/deleteImage/id/1', array())
166 8 Masato Nagasawa
->checkCSRF()
167 8 Masato Nagasawa
</pre>