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