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/>.
20 * Unit tests for core renderer render template exploit.
24 * @copyright 2019 Ryan Wyllie <ryan@moodle.com>
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 class core_renderer_template_exploit_test
extends \advanced_testcase
{
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.
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) {
39 $singlerender = function($text, $helper) {
40 return $helper->render($text);
42 $recursiverender = function($text, $helper) {
43 $result = $helper->render($text);
45 while (strpos($result, '{{') != false) {
46 $result = $helper->render($result);
53 'nested JS helper' => [
55 'test' => '{{#testpix}} core, move, {{#js}} some nasty JS {{/js}}{{/testpix}}',
60 'testpix' => $singlerender
62 'js' => 'some nasty JS',
63 'expected' => 'core, move,',
66 'other nested helper' => [
68 'test' => '{{#testpix}} core, move, {{#test1}} some text {{/test1}}{{/testpix}}',
73 'testpix' => $singlerender,
76 'js' => 'some nasty JS',
77 'expected' => 'core, move, some text',
80 'double nested helper' => [
82 'test' => '{{#testpix}} core, move, {{#test1}} some text {{#js}} some nasty JS {{/js}} {{/test1}}{{/testpix}}',
87 'testpix' => $singlerender,
90 'js' => 'some nasty JS',
91 'expected' => 'core, move, some text {{}}',
94 'js helper not nested' => [
96 'test' => '{{#testpix}} core, move, some text {{/testpix}}{{#js}} some nasty JS {{/js}}',
101 'testpix' => $singlerender
103 'js' => 'some nasty JS',
104 'expected' => 'core, move, some text',
107 'js in context not in helper' => [
109 'test' => '{{#testpix}} core, move, {{/testpix}}{{hack}}',
111 'torender' => 'test',
113 'hack' => '{{#js}} some nasty JS {{/js}}'
116 'testpix' => $singlerender
118 'js' => 'some nasty JS',
119 'expected' => 'core, move, {{#js}} some nasty JS {{/js}}',
124 'test' => '{{#testpix}} core, move, {{hack}}{{/testpix}}',
126 'torender' => 'test',
128 'hack' => '{{#js}} some nasty JS {{/js}}'
131 'testpix' => $singlerender
133 'js' => 'some nasty JS',
134 'expected' => 'core, move, {{}}',
137 'js in context double depth with single render' => [
139 'test' => '{{#testpix}} core, move, {{first}}{{/testpix}}',
141 'torender' => 'test',
143 'first' => '{{second}}',
144 'second' => '{{#js}} some nasty JS {{/js}}'
147 'testpix' => $singlerender
149 'js' => 'some nasty JS',
150 'expected' => 'core, move, {{second}}',
153 'js in context double depth with recursive render' => [
155 'test' => '{{#testpix}} core, move, {{first}}{{/testpix}}',
157 'torender' => 'test',
159 'first' => '{{second}}',
160 'second' => '{{#js}} some nasty JS {{/js}}'
163 'testpix' => $recursiverender
165 'js' => 'some nasty JS',
166 'expected' => 'core, move,',
171 'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{> test2}}',
172 'test2' => 'some content',
174 'torender' => 'test',
177 'testpix' => $recursiverender
179 'js' => 'some nasty JS',
180 'expected' => 'core, move, blah, some content',
183 'partial nested' => [
185 'test' => '{{#testpix}} core, move, {{> test2}}{{/testpix}}',
186 'test2' => 'some content',
188 'torender' => 'test',
191 'testpix' => $recursiverender
193 'js' => 'some nasty JS',
194 'expected' => 'core, move, some content',
197 'partial with js' => [
199 'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{> test2}}',
200 'test2' => '{{#js}} some nasty JS {{/js}}',
202 'torender' => 'test',
205 'testpix' => $recursiverender
207 'js' => 'some nasty JS',
208 'expected' => 'core, move, blah,',
211 'partial nested with js' => [
213 'test' => '{{#testpix}} core, move, {{> test2}}{{/testpix}}',
214 'test2' => '{{#js}} some nasty JS {{/js}}',
216 'torender' => 'test',
219 'testpix' => $recursiverender
221 'js' => 'some nasty JS',
222 'expected' => 'core, move,',
225 'partial with js from context' => [
227 'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{{foo}}}',
228 'test2' => '{{#js}} some nasty JS {{/js}}',
230 'torender' => 'test',
232 'foo' => '{{> test2}}'
235 'testpix' => $recursiverender
237 'js' => 'some nasty JS',
238 'expected' => 'core, move, blah, {{> test2}}',
241 'partial nested with js from context recursive render' => [
243 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
244 'test2' => '{{#js}} some nasty JS {{/js}}',
246 'torender' => 'test',
248 'foo' => '{{> test2}}'
251 'testpix' => $recursiverender
253 'js' => 'some nasty JS',
254 'expected' => 'core, move,',
257 'partial nested with js from context single render' => [
259 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
260 'test2' => '{{#js}} some nasty JS {{/js}}',
262 'torender' => 'test',
264 'foo' => '{{> test2}}'
267 'testpix' => $singlerender
269 'js' => 'some nasty JS',
270 'expected' => 'core, move, {{> test2}}',
273 'partial double nested with js from context recursive render' => [
275 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
276 'test2' => '{{#js}} some nasty JS {{/js}}',
278 'torender' => 'test',
281 'bar' => '{{> test2}}'
284 'testpix' => $recursiverender
286 'js' => 'some nasty JS',
287 'expected' => 'core, move,',
290 'array context depth 1' => [
292 'test' => '{{#items}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/items}}'
294 'torender' => 'test',
298 '{{#js}}some nasty JS{{/js}}'
302 'testpix' => $recursiverender
304 'js' => 'some nasty JS',
305 'expected' => 'core, move, legit core, move,',
308 'array context depth 2' => [
310 'test' => '{{#items}}{{#subitems}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/subitems}}{{/items}}'
312 'torender' => 'test',
318 '{{#js}}some nasty JS{{/js}}'
324 'testpix' => $recursiverender
326 'js' => 'some nasty JS',
327 'expected' => 'core, move, legit core, move,',
330 'object context depth 1' => [
332 'test' => '{{#items}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/items}}'
334 'torender' => 'test',
335 'context' => (object) [
338 '{{#js}}some nasty JS{{/js}}'
342 'testpix' => $recursiverender
344 'js' => 'some nasty JS',
345 'expected' => 'core, move, legit core, move,',
348 'object context depth 2' => [
350 'test' => '{{#items}}{{#subitems}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/subitems}}{{/items}}'
352 'torender' => 'test',
353 'context' => (object) [
358 '{{#js}}some nasty JS{{/js}}'
364 'testpix' => $recursiverender
366 'js' => 'some nasty JS',
367 'expected' => 'core, move, legit core, move,',
370 'change delimeters' => [
372 'test' => '{{#testpix}} core, move, {{{foo}}}{{/testpix}}'
374 'torender' => 'test',
376 'foo' => '{{=<% %>=}} <%#js%>some nasty JS,<%/js%>'
379 'testpix' => $recursiverender
381 'js' => 'some nasty JS',
382 'expected' => 'core, move,',
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
399 public function test_core_mustache_engine_strips_js_helper(
408 $page = new \
moodle_page();
409 $renderer = $page->get_renderer('core');
411 // Get the mustache engine from the renderer.
412 $reflection = new \
ReflectionMethod($renderer, 'get_mustache');
413 $engine = $reflection->invoke($renderer);
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);
420 // Add our test helpers.
421 $helpercollection = $engine->getHelpers();
422 foreach ($helpers as $name => $function) {
423 $helpercollection->add($name, $function);
426 // Add our test template to be rendered.
427 foreach ($templates as $name => $template) {
428 $loader->setTemplate($name, $template);
431 // Confirm that the rendered template matches what we expect.
432 $this->assertEquals($expected, trim($engine->render($torender, $context)));
435 // Confirm that the JS was added to the page.
436 $this->assertStringContainsString($js, $page->requires
->get_end_code());
438 // Confirm that the JS wasn't added to the page.
439 $this->assertStringNotContainsString($js, $page->requires
->get_end_code());