Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core;
18
 
19
/**
20
 * Unit tests for core renderer render template exploit.
21
 *
22
 * @package core
23
 * @category test
24
 * @copyright 2019 Ryan Wyllie <ryan@moodle.com>
25
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26
 */
27
class core_renderer_template_exploit_test extends \advanced_testcase {
28
    /**
29
     * Test cases to confirm that blacklisted helpers are stripped from the source
30
     * text by the helper before being passed to other another helper. This prevents
31
     * nested calls to helpers.
32
     */
33
    public function get_template_testcases() {
34
        // Different helper implementations to test various combinations of nested
35
        // calls to render the templates.
36
        $norender = function($text) {
37
            return $text;
38
        };
39
        $singlerender = function($text, $helper) {
40
            return $helper->render($text);
41
        };
42
        $recursiverender = function($text, $helper) {
43
            $result = $helper->render($text);
44
 
45
            while (strpos($result, '{{') != false) {
46
                $result = $helper->render($result);
47
            }
48
 
49
            return $result;
50
        };
51
 
52
        return [
53
            'nested JS helper' => [
54
                'templates' => [
55
                    'test' => '{{#testpix}} core, move, {{#js}} some nasty JS {{/js}}{{/testpix}}',
56
                ],
57
                'torender' => 'test',
58
                'context' => [],
59
                'helpers' => [
60
                    'testpix' => $singlerender
61
                ],
62
                'js' => 'some nasty JS',
63
                'expected' => 'core, move,',
64
                'include' => false
65
            ],
66
            'other nested helper' => [
67
                'templates' => [
68
                    'test' => '{{#testpix}} core, move, {{#test1}} some text {{/test1}}{{/testpix}}',
69
                ],
70
                'torender' => 'test',
71
                'context' => [],
72
                'helpers' => [
73
                    'testpix' => $singlerender,
74
                    'test1' => $norender,
75
                ],
76
                'js' => 'some nasty JS',
77
                'expected' => 'core, move,  some text',
78
                'include' => false
79
            ],
80
            'double nested helper' => [
81
                'templates' => [
82
                    'test' => '{{#testpix}} core, move, {{#test1}} some text {{#js}} some nasty JS {{/js}} {{/test1}}{{/testpix}}',
83
                ],
84
                'torender' => 'test',
85
                'context' => [],
86
                'helpers' => [
87
                    'testpix' => $singlerender,
88
                    'test1' => $norender,
89
                ],
90
                'js' => 'some nasty JS',
91
                'expected' => 'core, move,  some text {{}}',
92
                'include' => false
93
            ],
94
            'js helper not nested' => [
95
                'templates' => [
96
                    'test' => '{{#testpix}} core, move, some text {{/testpix}}{{#js}} some nasty JS {{/js}}',
97
                ],
98
                'torender' => 'test',
99
                'context' => [],
100
                'helpers' => [
101
                    'testpix' => $singlerender
102
                ],
103
                'js' => 'some nasty JS',
104
                'expected' => 'core, move, some text',
105
                'include' => true
106
            ],
107
            'js in context not in helper' => [
108
                'templates' => [
109
                    'test' => '{{#testpix}} core, move, {{/testpix}}{{hack}}',
110
                ],
111
                'torender' => 'test',
112
                'context' => [
113
                    'hack' => '{{#js}} some nasty JS {{/js}}'
114
                ],
115
                'helpers' => [
116
                    'testpix' => $singlerender
117
                ],
118
                'js' => 'some nasty JS',
119
                'expected' => 'core, move, {{#js}} some nasty JS {{/js}}',
120
                'include' => false
121
            ],
122
            'js in context' => [
123
                'templates' => [
124
                    'test' => '{{#testpix}} core, move, {{hack}}{{/testpix}}',
125
                ],
126
                'torender' => 'test',
127
                'context' => [
128
                    'hack' => '{{#js}} some nasty JS {{/js}}'
129
                ],
130
                'helpers' => [
131
                    'testpix' => $singlerender
132
                ],
133
                'js' => 'some nasty JS',
134
                'expected' => 'core, move, {{}}',
135
                'include' => false
136
            ],
137
            'js in context double depth with single render' => [
138
                'templates' => [
139
                    'test' => '{{#testpix}} core, move, {{first}}{{/testpix}}',
140
                ],
141
                'torender' => 'test',
142
                'context' => [
143
                    'first' => '{{second}}',
144
                    'second' => '{{#js}} some nasty JS {{/js}}'
145
                ],
146
                'helpers' => [
147
                    'testpix' => $singlerender
148
                ],
149
                'js' => 'some nasty JS',
150
                'expected' => 'core, move, {{second}}',
151
                'include' => false
152
            ],
153
            'js in context double depth with recursive render' => [
154
                'templates' => [
155
                    'test' => '{{#testpix}} core, move, {{first}}{{/testpix}}',
156
                ],
157
                'torender' => 'test',
158
                'context' => [
159
                    'first' => '{{second}}',
160
                    'second' => '{{#js}} some nasty JS {{/js}}'
161
                ],
162
                'helpers' => [
163
                    'testpix' => $recursiverender
164
                ],
165
                'js' => 'some nasty JS',
166
                'expected' => 'core, move,',
167
                'include' => false
168
            ],
169
            'partial' => [
170
                'templates' => [
171
                    'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{> test2}}',
172
                    'test2' => 'some content',
173
                ],
174
                'torender' => 'test',
175
                'context' => [],
176
                'helpers' => [
177
                    'testpix' => $recursiverender
178
                ],
179
                'js' => 'some nasty JS',
180
                'expected' => 'core, move, blah, some content',
181
                'include' => false
182
            ],
183
            'partial nested' => [
184
                'templates' => [
185
                    'test' => '{{#testpix}} core, move, {{> test2}}{{/testpix}}',
186
                    'test2' => 'some content',
187
                ],
188
                'torender' => 'test',
189
                'context' => [],
190
                'helpers' => [
191
                    'testpix' => $recursiverender
192
                ],
193
                'js' => 'some nasty JS',
194
                'expected' => 'core, move, some content',
195
                'include' => false
196
            ],
197
            'partial with js' => [
198
                'templates' => [
199
                    'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{> test2}}',
200
                    'test2' => '{{#js}} some nasty JS {{/js}}',
201
                ],
202
                'torender' => 'test',
203
                'context' => [],
204
                'helpers' => [
205
                    'testpix' => $recursiverender
206
                ],
207
                'js' => 'some nasty JS',
208
                'expected' => 'core, move, blah,',
209
                'include' => true
210
            ],
211
            'partial nested with js' => [
212
                'templates' => [
213
                    'test' => '{{#testpix}} core, move, {{> test2}}{{/testpix}}',
214
                    'test2' => '{{#js}} some nasty JS {{/js}}',
215
                ],
216
                'torender' => 'test',
217
                'context' => [],
218
                'helpers' => [
219
                    'testpix' => $recursiverender
220
                ],
221
                'js' => 'some nasty JS',
222
                'expected' => 'core, move,',
223
                'include' => false
224
            ],
225
            'partial with js from context' => [
226
                'templates' => [
227
                    'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{{foo}}}',
228
                    'test2' => '{{#js}} some nasty JS {{/js}}',
229
                ],
230
                'torender' => 'test',
231
                'context' => [
232
                    'foo' => '{{> test2}}'
233
                ],
234
                'helpers' => [
235
                    'testpix' => $recursiverender
236
                ],
237
                'js' => 'some nasty JS',
238
                'expected' => 'core, move, blah, {{> test2}}',
239
                'include' => false
240
            ],
241
            'partial nested with js from context recursive render' => [
242
                'templates' => [
243
                    'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
244
                    'test2' => '{{#js}} some nasty JS {{/js}}',
245
                ],
246
                'torender' => 'test',
247
                'context' => [
248
                    'foo' => '{{> test2}}'
249
                ],
250
                'helpers' => [
251
                    'testpix' => $recursiverender
252
                ],
253
                'js' => 'some nasty JS',
254
                'expected' => 'core, move,',
255
                'include' => false
256
            ],
257
            'partial nested with js from context single render' => [
258
                'templates' => [
259
                    'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
260
                    'test2' => '{{#js}} some nasty JS {{/js}}',
261
                ],
262
                'torender' => 'test',
263
                'context' => [
264
                    'foo' => '{{> test2}}'
265
                ],
266
                'helpers' => [
267
                    'testpix' => $singlerender
268
                ],
269
                'js' => 'some nasty JS',
270
                'expected' => 'core, move, {{&gt; test2}}',
271
                'include' => false
272
            ],
273
            'partial double nested with js from context recursive render' => [
274
                'templates' => [
275
                    'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
276
                    'test2' => '{{#js}} some nasty JS {{/js}}',
277
                ],
278
                'torender' => 'test',
279
                'context' => [
280
                    'foo' => '{{bar}}',
281
                    'bar' => '{{> test2}}'
282
                ],
283
                'helpers' => [
284
                    'testpix' => $recursiverender
285
                ],
286
                'js' => 'some nasty JS',
287
                'expected' => 'core, move,',
288
                'include' => false
289
            ],
290
            'array context depth 1' => [
291
                'templates' => [
292
                    'test' => '{{#items}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/items}}'
293
                ],
294
                'torender' => 'test',
295
                'context' => [
296
                    'items' => [
297
                        'legit',
298
                        '{{#js}}some nasty JS{{/js}}'
299
                    ]
300
                ],
301
                'helpers' => [
302
                    'testpix' => $recursiverender
303
                ],
304
                'js' => 'some nasty JS',
305
                'expected' => 'core, move, legit core, move,',
306
                'include' => false
307
            ],
308
            'array context depth 2' => [
309
                'templates' => [
310
                    'test' => '{{#items}}{{#subitems}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/subitems}}{{/items}}'
311
                ],
312
                'torender' => 'test',
313
                'context' => [
314
                    'items' => [
315
                        [
316
                            'subitems' => [
317
                                'legit',
318
                                '{{#js}}some nasty JS{{/js}}'
319
                            ]
320
                        ],
321
                    ]
322
                ],
323
                'helpers' => [
324
                    'testpix' => $recursiverender
325
                ],
326
                'js' => 'some nasty JS',
327
                'expected' => 'core, move, legit core, move,',
328
                'include' => false
329
            ],
330
            'object context depth 1' => [
331
                'templates' => [
332
                    'test' => '{{#items}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/items}}'
333
                ],
334
                'torender' => 'test',
335
                'context' => (object) [
336
                    'items' => [
337
                        'legit',
338
                        '{{#js}}some nasty JS{{/js}}'
339
                    ]
340
                ],
341
                'helpers' => [
342
                    'testpix' => $recursiverender
343
                ],
344
                'js' => 'some nasty JS',
345
                'expected' => 'core, move, legit core, move,',
346
                'include' => false
347
            ],
348
            'object context depth 2' => [
349
                'templates' => [
350
                    'test' => '{{#items}}{{#subitems}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/subitems}}{{/items}}'
351
                ],
352
                'torender' => 'test',
353
                'context' => (object) [
354
                    'items' => [
355
                        (object) [
356
                            'subitems' => [
357
                                'legit',
358
                                '{{#js}}some nasty JS{{/js}}'
359
                            ]
360
                        ],
361
                    ]
362
                ],
363
                'helpers' => [
364
                    'testpix' => $recursiverender
365
                ],
366
                'js' => 'some nasty JS',
367
                'expected' => 'core, move, legit core, move,',
368
                'include' => false
369
            ],
370
            'change delimeters' => [
371
                'templates' => [
372
                    'test' => '{{#testpix}} core, move, {{{foo}}}{{/testpix}}'
373
                ],
374
                'torender' => 'test',
375
                'context' => [
376
                    'foo' => '{{=<% %>=}} <%#js%>some nasty JS,<%/js%>'
377
                ],
378
                'helpers' => [
379
                    'testpix' => $recursiverender
380
                ],
381
                'js' => 'some nasty JS',
382
                'expected' => 'core, move,',
383
                'include' => false
384
            ]
385
        ];
386
    }
387
 
388
    /**
389
     * Test that the mustache_helper_collection class correctly strips
390
     * @dataProvider get_template_testcases()
391
     * @param array $templates The template to add
392
     * @param string $torender The name of the template to render
393
     * @param array $context The template context
394
     * @param array $helpers Mustache helpers to add
395
     * @param string $js The JS string from the template
396
     * @param string $expected The expected output of the string after stripping JS
397
     * @param bool $include If the JS should be added to the page or not
398
     */
399
    public function test_core_mustache_engine_strips_js_helper(
400
        $templates,
401
        $torender,
402
        $context,
403
        $helpers,
404
        $js,
405
        $expected,
406
        $include
11 efrain 407
    ): void {
1 efrain 408
        $page = new \moodle_page();
409
        $renderer = $page->get_renderer('core');
410
 
411
        // Get the mustache engine from the renderer.
412
        $reflection = new \ReflectionMethod($renderer, 'get_mustache');
413
        $engine = $reflection->invoke($renderer);
414
 
415
        // Swap the loader out with an array loader so that we can set some
416
        // inline templates for testing.
417
        $loader = new \Mustache_Loader_ArrayLoader([]);
418
        $engine->setLoader($loader);
419
 
420
        // Add our test helpers.
421
        $helpercollection = $engine->getHelpers();
422
        foreach ($helpers as $name => $function) {
423
            $helpercollection->add($name, $function);
424
        }
425
 
426
        // Add our test template to be rendered.
427
        foreach ($templates as $name => $template) {
428
            $loader->setTemplate($name, $template);
429
        }
430
 
431
        // Confirm that the rendered template matches what we expect.
432
        $this->assertEquals($expected, trim($engine->render($torender, $context)));
433
 
434
        if ($include) {
435
            // Confirm that the JS was added to the page.
436
            $this->assertStringContainsString($js, $page->requires->get_end_code());
437
        } else {
438
            // Confirm that the JS wasn't added to the page.
439
            $this->assertStringNotContainsString($js, $page->requires->get_end_code());
440
        }
441
    }
442
}