Rev 1 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>.namespace core;/*** Unit tests for core renderer render template exploit.** @package core* @category test* @copyright 2019 Ryan Wyllie <ryan@moodle.com>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class core_renderer_template_exploit_test extends \advanced_testcase {/*** Test cases to confirm that blacklisted helpers are stripped from the source* text by the helper before being passed to other another helper. This prevents* nested calls to helpers.*/public function get_template_testcases() {// Different helper implementations to test various combinations of nested// calls to render the templates.$norender = function($text) {return $text;};$singlerender = function($text, $helper) {return $helper->render($text);};$recursiverender = function($text, $helper) {$result = $helper->render($text);while (strpos($result, '{{') != false) {$result = $helper->render($result);}return $result;};return ['nested JS helper' => ['templates' => ['test' => '{{#testpix}} core, move, {{#js}} some nasty JS {{/js}}{{/testpix}}',],'torender' => 'test','context' => [],'helpers' => ['testpix' => $singlerender],'js' => 'some nasty JS','expected' => 'core, move,','include' => false],'other nested helper' => ['templates' => ['test' => '{{#testpix}} core, move, {{#test1}} some text {{/test1}}{{/testpix}}',],'torender' => 'test','context' => [],'helpers' => ['testpix' => $singlerender,'test1' => $norender,],'js' => 'some nasty JS','expected' => 'core, move, some text','include' => false],'double nested helper' => ['templates' => ['test' => '{{#testpix}} core, move, {{#test1}} some text {{#js}} some nasty JS {{/js}} {{/test1}}{{/testpix}}',],'torender' => 'test','context' => [],'helpers' => ['testpix' => $singlerender,'test1' => $norender,],'js' => 'some nasty JS','expected' => 'core, move, some text {{}}','include' => false],'js helper not nested' => ['templates' => ['test' => '{{#testpix}} core, move, some text {{/testpix}}{{#js}} some nasty JS {{/js}}',],'torender' => 'test','context' => [],'helpers' => ['testpix' => $singlerender],'js' => 'some nasty JS','expected' => 'core, move, some text','include' => true],'js in context not in helper' => ['templates' => ['test' => '{{#testpix}} core, move, {{/testpix}}{{hack}}',],'torender' => 'test','context' => ['hack' => '{{#js}} some nasty JS {{/js}}'],'helpers' => ['testpix' => $singlerender],'js' => 'some nasty JS','expected' => 'core, move, {{#js}} some nasty JS {{/js}}','include' => false],'js in context' => ['templates' => ['test' => '{{#testpix}} core, move, {{hack}}{{/testpix}}',],'torender' => 'test','context' => ['hack' => '{{#js}} some nasty JS {{/js}}'],'helpers' => ['testpix' => $singlerender],'js' => 'some nasty JS','expected' => 'core, move, {{}}','include' => false],'js in context double depth with single render' => ['templates' => ['test' => '{{#testpix}} core, move, {{first}}{{/testpix}}',],'torender' => 'test','context' => ['first' => '{{second}}','second' => '{{#js}} some nasty JS {{/js}}'],'helpers' => ['testpix' => $singlerender],'js' => 'some nasty JS','expected' => 'core, move, {{second}}','include' => false],'js in context double depth with recursive render' => ['templates' => ['test' => '{{#testpix}} core, move, {{first}}{{/testpix}}',],'torender' => 'test','context' => ['first' => '{{second}}','second' => '{{#js}} some nasty JS {{/js}}'],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move,','include' => false],'partial' => ['templates' => ['test' => '{{#testpix}} core, move, blah{{/testpix}}, {{> test2}}','test2' => 'some content',],'torender' => 'test','context' => [],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move, blah, some content','include' => false],'partial nested' => ['templates' => ['test' => '{{#testpix}} core, move, {{> test2}}{{/testpix}}','test2' => 'some content',],'torender' => 'test','context' => [],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move, some content','include' => false],'partial with js' => ['templates' => ['test' => '{{#testpix}} core, move, blah{{/testpix}}, {{> test2}}','test2' => '{{#js}} some nasty JS {{/js}}',],'torender' => 'test','context' => [],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move, blah,','include' => true],'partial nested with js' => ['templates' => ['test' => '{{#testpix}} core, move, {{> test2}}{{/testpix}}','test2' => '{{#js}} some nasty JS {{/js}}',],'torender' => 'test','context' => [],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move,','include' => false],'partial with js from context' => ['templates' => ['test' => '{{#testpix}} core, move, blah{{/testpix}}, {{{foo}}}','test2' => '{{#js}} some nasty JS {{/js}}',],'torender' => 'test','context' => ['foo' => '{{> test2}}'],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move, blah, {{> test2}}','include' => false],'partial nested with js from context recursive render' => ['templates' => ['test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}','test2' => '{{#js}} some nasty JS {{/js}}',],'torender' => 'test','context' => ['foo' => '{{> test2}}'],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move,','include' => false],'partial nested with js from context single render' => ['templates' => ['test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}','test2' => '{{#js}} some nasty JS {{/js}}',],'torender' => 'test','context' => ['foo' => '{{> test2}}'],'helpers' => ['testpix' => $singlerender],'js' => 'some nasty JS','expected' => 'core, move, {{> test2}}','include' => false],'partial double nested with js from context recursive render' => ['templates' => ['test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}','test2' => '{{#js}} some nasty JS {{/js}}',],'torender' => 'test','context' => ['foo' => '{{bar}}','bar' => '{{> test2}}'],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move,','include' => false],'array context depth 1' => ['templates' => ['test' => '{{#items}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/items}}'],'torender' => 'test','context' => ['items' => ['legit','{{#js}}some nasty JS{{/js}}']],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move, legit core, move,','include' => false],'array context depth 2' => ['templates' => ['test' => '{{#items}}{{#subitems}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/subitems}}{{/items}}'],'torender' => 'test','context' => ['items' => [['subitems' => ['legit','{{#js}}some nasty JS{{/js}}']],]],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move, legit core, move,','include' => false],'object context depth 1' => ['templates' => ['test' => '{{#items}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/items}}'],'torender' => 'test','context' => (object) ['items' => ['legit','{{#js}}some nasty JS{{/js}}']],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move, legit core, move,','include' => false],'object context depth 2' => ['templates' => ['test' => '{{#items}}{{#subitems}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/subitems}}{{/items}}'],'torender' => 'test','context' => (object) ['items' => [(object) ['subitems' => ['legit','{{#js}}some nasty JS{{/js}}']],]],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move, legit core, move,','include' => false],'change delimeters' => ['templates' => ['test' => '{{#testpix}} core, move, {{{foo}}}{{/testpix}}'],'torender' => 'test','context' => ['foo' => '{{=<% %>=}} <%#js%>some nasty JS,<%/js%>'],'helpers' => ['testpix' => $recursiverender],'js' => 'some nasty JS','expected' => 'core, move,','include' => false]];}/*** Test that the mustache_helper_collection class correctly strips* @dataProvider get_template_testcases()* @param array $templates The template to add* @param string $torender The name of the template to render* @param array $context The template context* @param array $helpers Mustache helpers to add* @param string $js The JS string from the template* @param string $expected The expected output of the string after stripping JS* @param bool $include If the JS should be added to the page or not*/public function test_core_mustache_engine_strips_js_helper($templates,$torender,$context,$helpers,$js,$expected,$include): void {$page = new \moodle_page();$renderer = $page->get_renderer('core');// Get the mustache engine from the renderer.$reflection = new \ReflectionMethod($renderer, 'get_mustache');$engine = $reflection->invoke($renderer);// Swap the loader out with an array loader so that we can set some// inline templates for testing.$loader = new \Mustache_Loader_ArrayLoader([]);$engine->setLoader($loader);// Add our test helpers.$helpercollection = $engine->getHelpers();foreach ($helpers as $name => $function) {$helpercollection->add($name, $function);}// Add our test template to be rendered.foreach ($templates as $name => $template) {$loader->setTemplate($name, $template);}// Confirm that the rendered template matches what we expect.$this->assertEquals($expected, trim($engine->render($torender, $context)));if ($include) {// Confirm that the JS was added to the page.$this->assertStringContainsString($js, $page->requires->get_end_code());} else {// Confirm that the JS wasn't added to the page.$this->assertStringNotContainsString($js, $page->requires->get_end_code());}}}