2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Unit tests for core renderer render template exploit.
20 * @copyright 2019 Ryan Wyllie <ryan@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 defined('MOODLE_INTERNAL') ||
die();
27 * Unit tests for core renderer render template exploit.
29 class core_renderer_template_exploit_testcase
extends advanced_testcase
{
31 * Test cases to confirm that blacklisted helpers are stripped from the source
32 * text by the helper before being passed to other another helper. This prevents
33 * nested calls to helpers.
35 public function get_template_testcases() {
36 // Different helper implementations to test various combinations of nested
37 // calls to render the templates.
38 $norender = function($text) {
41 $singlerender = function($text, $helper) {
42 return $helper->render($text);
44 $recursiverender = function($text, $helper) {
45 $result = $helper->render($text);
47 while (strpos($result, '{{') != false) {
48 $result = $helper->render($result);
55 'nested JS helper' => [
57 'test' => '{{#testpix}} core, move, {{#js}} some nasty JS {{/js}}{{/testpix}}',
62 'testpix' => $singlerender
64 'js' => 'some nasty JS',
65 'expected' => 'core, move,',
68 'other nested helper' => [
70 'test' => '{{#testpix}} core, move, {{#test1}} some text {{/test1}}{{/testpix}}',
75 'testpix' => $singlerender,
78 'js' => 'some nasty JS',
79 'expected' => 'core, move, some text',
82 'double nested helper' => [
84 'test' => '{{#testpix}} core, move, {{#test1}} some text {{#js}} some nasty JS {{/js}} {{/test1}}{{/testpix}}',
89 'testpix' => $singlerender,
92 'js' => 'some nasty JS',
93 'expected' => 'core, move, some text',
96 'js helper not nested' => [
98 'test' => '{{#testpix}} core, move, some text {{/testpix}}{{#js}} some nasty JS {{/js}}',
100 'torender' => 'test',
103 'testpix' => $singlerender
105 'js' => 'some nasty JS',
106 'expected' => 'core, move, some text',
109 'js in context not in helper' => [
111 'test' => '{{#testpix}} core, move, {{/testpix}}{{hack}}',
113 'torender' => 'test',
115 'hack' => '{{#js}} some nasty JS {{/js}}'
118 'testpix' => $singlerender
120 'js' => 'some nasty JS',
121 'expected' => 'core, move, {{#js}} some nasty JS {{/js}}',
126 'test' => '{{#testpix}} core, move, {{hack}}{{/testpix}}',
128 'torender' => 'test',
130 'hack' => '{{#js}} some nasty JS {{/js}}'
133 'testpix' => $singlerender
135 'js' => 'some nasty JS',
136 'expected' => 'core, move,',
139 'js in context double depth with single render' => [
141 'test' => '{{#testpix}} core, move, {{first}}{{/testpix}}',
143 'torender' => 'test',
145 'first' => '{{second}}',
146 'second' => '{{#js}} some nasty JS {{/js}}'
149 'testpix' => $singlerender
151 'js' => 'some nasty JS',
152 'expected' => 'core, move, {{#js}} some nasty JS {{/js}}',
155 'js in context double depth with recursive render' => [
157 'test' => '{{#testpix}} core, move, {{first}}{{/testpix}}',
159 'torender' => 'test',
161 'first' => '{{second}}',
162 'second' => '{{#js}} some nasty JS {{/js}}'
165 'testpix' => $recursiverender
167 'js' => 'some nasty JS',
168 'expected' => 'core, move,',
173 'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{> test2}}',
174 'test2' => 'some content',
176 'torender' => 'test',
179 'testpix' => $recursiverender
181 'js' => 'some nasty JS',
182 'expected' => 'core, move, blah, some content',
185 'partial nested' => [
187 'test' => '{{#testpix}} core, move, {{> test2}}{{/testpix}}',
188 'test2' => 'some content',
190 'torender' => 'test',
193 'testpix' => $recursiverender
195 'js' => 'some nasty JS',
196 'expected' => 'core, move, some content',
199 'partial with js' => [
201 'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{> test2}}',
202 'test2' => '{{#js}} some nasty JS {{/js}}',
204 'torender' => 'test',
207 'testpix' => $recursiverender
209 'js' => 'some nasty JS',
210 'expected' => 'core, move, blah,',
213 'partial nested with js' => [
215 'test' => '{{#testpix}} core, move, {{> test2}}{{/testpix}}',
216 'test2' => '{{#js}} some nasty JS {{/js}}',
218 'torender' => 'test',
221 'testpix' => $recursiverender
223 'js' => 'some nasty JS',
224 'expected' => 'core, move,',
227 'partial with js from context' => [
229 'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{{foo}}}',
230 'test2' => '{{#js}} some nasty JS {{/js}}',
232 'torender' => 'test',
234 'foo' => '{{> test2}}'
237 'testpix' => $recursiverender
239 'js' => 'some nasty JS',
240 'expected' => 'core, move, blah, {{> test2}}',
243 'partial nested with js from context recursive render' => [
245 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
246 'test2' => '{{#js}} some nasty JS {{/js}}',
248 'torender' => 'test',
250 'foo' => '{{> test2}}'
253 'testpix' => $recursiverender
255 'js' => 'some nasty JS',
256 'expected' => 'core, move,',
259 'partial nested with js from context single render' => [
261 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
262 'test2' => '{{#js}} some nasty JS {{/js}}',
264 'torender' => 'test',
266 'foo' => '{{> test2}}'
269 'testpix' => $singlerender
271 'js' => 'some nasty JS',
272 'expected' => 'core, move,',
275 'partial double nested with js from context single render' => [
277 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
278 'test2' => '{{#js}} some nasty JS {{/js}}',
280 'torender' => 'test',
282 'foo' => '{{{bar}}}',
283 'bar' => '{{> test2}}'
286 'testpix' => $singlerender
288 'js' => 'some nasty JS',
289 'expected' => 'core, move, {{> test2}}',
292 'partial double nested with js from context recursive render' => [
294 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
295 'test2' => '{{#js}} some nasty JS {{/js}}',
297 'torender' => 'test',
300 'bar' => '{{> test2}}'
303 'testpix' => $recursiverender
305 'js' => 'some nasty JS',
306 'expected' => 'core, move,',
309 'array context depth 1' => [
311 'test' => '{{#items}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/items}}'
313 'torender' => 'test',
317 '{{#js}}some nasty JS{{/js}}'
321 'testpix' => $recursiverender
323 'js' => 'some nasty JS',
324 'expected' => 'core, move, legit core, move,',
327 'array context depth 2' => [
329 'test' => '{{#items}}{{#subitems}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/subitems}}{{/items}}'
331 'torender' => 'test',
337 '{{#js}}some nasty JS{{/js}}'
343 'testpix' => $recursiverender
345 'js' => 'some nasty JS',
346 'expected' => 'core, move, legit core, move,',
349 'object context depth 1' => [
351 'test' => '{{#items}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/items}}'
353 'torender' => 'test',
354 'context' => (object) [
357 '{{#js}}some nasty JS{{/js}}'
361 'testpix' => $recursiverender
363 'js' => 'some nasty JS',
364 'expected' => 'core, move, legit core, move,',
367 'object context depth 2' => [
369 'test' => '{{#items}}{{#subitems}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/subitems}}{{/items}}'
371 'torender' => 'test',
372 'context' => (object) [
377 '{{#js}}some nasty JS{{/js}}'
383 'testpix' => $recursiverender
385 'js' => 'some nasty JS',
386 'expected' => 'core, move, legit core, move,',
389 'change delimeters' => [
391 'test' => '{{#testpix}} core, move, {{{foo}}}{{/testpix}}'
393 'torender' => 'test',
395 'foo' => '{{=<% %>=}} <%#js%>some nasty JS,<%/js%>'
398 'testpix' => $recursiverender
400 'js' => 'some nasty JS',
401 'expected' => 'core, move,',
408 * Test that the mustache_helper_collection class correctly strips
409 * @dataProvider get_template_testcases()
410 * @param string $templates The template to add
411 * @param string $torender The name of the template to render
412 * @param array $context The template context
413 * @param array $helpers Mustache helpers to add
414 * @param string $js The JS string from the template
415 * @param string $expected The expected output of the string after stripping JS
416 * @param bool $include If the JS should be added to the page or not
418 public function test_core_mustache_engine_strips_js_helper(
427 $page = new \
moodle_page();
428 $renderer = $page->get_renderer('core');
430 // Get the mustache engine from the renderer.
431 $reflection = new \
ReflectionMethod($renderer, 'get_mustache');
432 $reflection->setAccessible(true);
433 $engine = $reflection->invoke($renderer);
435 // Swap the loader out with an array loader so that we can set some
436 // inline templates for testing.
437 $loader = new \
Mustache_Loader_ArrayLoader([]);
438 $engine->setLoader($loader);
440 // Add our test helpers.
441 $helpercollection = $engine->getHelpers();
442 foreach ($helpers as $name => $function) {
443 $helpercollection->add($name, $function);
446 // Add our test template to be rendered.
447 foreach ($templates as $name => $template) {
448 $loader->setTemplate($name, $template);
451 // Confirm that the rendered template matches what we expect.
452 $this->assertEquals($expected, trim($engine->render($torender, $context)));
455 // Confirm that the JS was added to the page.
456 $this->assertContains($js, $page->requires
->get_end_code());
458 // Confirm that the JS wasn't added to the page.
459 $this->assertNotContains($js, $page->requires
->get_end_code());