Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]
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 /** 18 * Unit tests for core renderer render template exploit. 19 * 20 * @copyright 2019 Ryan Wyllie <ryan@moodle.com> 21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 */ 23 24 defined('MOODLE_INTERNAL') || die(); 25 26 /** 27 * Unit tests for core renderer render template exploit. 28 */ 29 class core_renderer_template_exploit_testcase extends advanced_testcase { 30 /** 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. 34 */ 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) { 39 return $text; 40 }; 41 $singlerender = function($text, $helper) { 42 return $helper->render($text); 43 }; 44 $recursiverender = function($text, $helper) { 45 $result = $helper->render($text); 46 47 while (strpos($result, '{{') != false) { 48 $result = $helper->render($result); 49 } 50 51 return $result; 52 }; 53 54 return [ 55 'nested JS helper' => [ 56 'templates' => [ 57 'test' => '{{#testpix}} core, move, {{#js}} some nasty JS {{/js}}{{/testpix}}', 58 ], 59 'torender' => 'test', 60 'context' => [], 61 'helpers' => [ 62 'testpix' => $singlerender 63 ], 64 'js' => 'some nasty JS', 65 'expected' => 'core, move,', 66 'include' => false 67 ], 68 'other nested helper' => [ 69 'templates' => [ 70 'test' => '{{#testpix}} core, move, {{#test1}} some text {{/test1}}{{/testpix}}', 71 ], 72 'torender' => 'test', 73 'context' => [], 74 'helpers' => [ 75 'testpix' => $singlerender, 76 'test1' => $norender, 77 ], 78 'js' => 'some nasty JS', 79 'expected' => 'core, move, some text', 80 'include' => false 81 ], 82 'double nested helper' => [ 83 'templates' => [ 84 'test' => '{{#testpix}} core, move, {{#test1}} some text {{#js}} some nasty JS {{/js}} {{/test1}}{{/testpix}}', 85 ], 86 'torender' => 'test', 87 'context' => [], 88 'helpers' => [ 89 'testpix' => $singlerender, 90 'test1' => $norender, 91 ], 92 'js' => 'some nasty JS', 93 'expected' => 'core, move, some text', 94 'include' => false 95 ], 96 'js helper not nested' => [ 97 'templates' => [ 98 'test' => '{{#testpix}} core, move, some text {{/testpix}}{{#js}} some nasty JS {{/js}}', 99 ], 100 'torender' => 'test', 101 'context' => [], 102 'helpers' => [ 103 'testpix' => $singlerender 104 ], 105 'js' => 'some nasty JS', 106 'expected' => 'core, move, some text', 107 'include' => true 108 ], 109 'js in context not in helper' => [ 110 'templates' => [ 111 'test' => '{{#testpix}} core, move, {{/testpix}}{{hack}}', 112 ], 113 'torender' => 'test', 114 'context' => [ 115 'hack' => '{{#js}} some nasty JS {{/js}}' 116 ], 117 'helpers' => [ 118 'testpix' => $singlerender 119 ], 120 'js' => 'some nasty JS', 121 'expected' => 'core, move, {{#js}} some nasty JS {{/js}}', 122 'include' => false 123 ], 124 'js in context' => [ 125 'templates' => [ 126 'test' => '{{#testpix}} core, move, {{hack}}{{/testpix}}', 127 ], 128 'torender' => 'test', 129 'context' => [ 130 'hack' => '{{#js}} some nasty JS {{/js}}' 131 ], 132 'helpers' => [ 133 'testpix' => $singlerender 134 ], 135 'js' => 'some nasty JS', 136 'expected' => 'core, move,', 137 'include' => false 138 ], 139 'js in context double depth with single render' => [ 140 'templates' => [ 141 'test' => '{{#testpix}} core, move, {{first}}{{/testpix}}', 142 ], 143 'torender' => 'test', 144 'context' => [ 145 'first' => '{{second}}', 146 'second' => '{{#js}} some nasty JS {{/js}}' 147 ], 148 'helpers' => [ 149 'testpix' => $singlerender 150 ], 151 'js' => 'some nasty JS', 152 'expected' => 'core, move, {{#js}} some nasty JS {{/js}}', 153 'include' => false 154 ], 155 'js in context double depth with recursive render' => [ 156 'templates' => [ 157 'test' => '{{#testpix}} core, move, {{first}}{{/testpix}}', 158 ], 159 'torender' => 'test', 160 'context' => [ 161 'first' => '{{second}}', 162 'second' => '{{#js}} some nasty JS {{/js}}' 163 ], 164 'helpers' => [ 165 'testpix' => $recursiverender 166 ], 167 'js' => 'some nasty JS', 168 'expected' => 'core, move,', 169 'include' => false 170 ], 171 'partial' => [ 172 'templates' => [ 173 'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{> test2}}', 174 'test2' => 'some content', 175 ], 176 'torender' => 'test', 177 'context' => [], 178 'helpers' => [ 179 'testpix' => $recursiverender 180 ], 181 'js' => 'some nasty JS', 182 'expected' => 'core, move, blah, some content', 183 'include' => false 184 ], 185 'partial nested' => [ 186 'templates' => [ 187 'test' => '{{#testpix}} core, move, {{> test2}}{{/testpix}}', 188 'test2' => 'some content', 189 ], 190 'torender' => 'test', 191 'context' => [], 192 'helpers' => [ 193 'testpix' => $recursiverender 194 ], 195 'js' => 'some nasty JS', 196 'expected' => 'core, move, some content', 197 'include' => false 198 ], 199 'partial with js' => [ 200 'templates' => [ 201 'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{> test2}}', 202 'test2' => '{{#js}} some nasty JS {{/js}}', 203 ], 204 'torender' => 'test', 205 'context' => [], 206 'helpers' => [ 207 'testpix' => $recursiverender 208 ], 209 'js' => 'some nasty JS', 210 'expected' => 'core, move, blah,', 211 'include' => true 212 ], 213 'partial nested with js' => [ 214 'templates' => [ 215 'test' => '{{#testpix}} core, move, {{> test2}}{{/testpix}}', 216 'test2' => '{{#js}} some nasty JS {{/js}}', 217 ], 218 'torender' => 'test', 219 'context' => [], 220 'helpers' => [ 221 'testpix' => $recursiverender 222 ], 223 'js' => 'some nasty JS', 224 'expected' => 'core, move,', 225 'include' => false 226 ], 227 'partial with js from context' => [ 228 'templates' => [ 229 'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{{foo}}}', 230 'test2' => '{{#js}} some nasty JS {{/js}}', 231 ], 232 'torender' => 'test', 233 'context' => [ 234 'foo' => '{{> test2}}' 235 ], 236 'helpers' => [ 237 'testpix' => $recursiverender 238 ], 239 'js' => 'some nasty JS', 240 'expected' => 'core, move, blah, {{> test2}}', 241 'include' => false 242 ], 243 'partial nested with js from context recursive render' => [ 244 'templates' => [ 245 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}', 246 'test2' => '{{#js}} some nasty JS {{/js}}', 247 ], 248 'torender' => 'test', 249 'context' => [ 250 'foo' => '{{> test2}}' 251 ], 252 'helpers' => [ 253 'testpix' => $recursiverender 254 ], 255 'js' => 'some nasty JS', 256 'expected' => 'core, move,', 257 'include' => false 258 ], 259 'partial nested with js from context single render' => [ 260 'templates' => [ 261 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}', 262 'test2' => '{{#js}} some nasty JS {{/js}}', 263 ], 264 'torender' => 'test', 265 'context' => [ 266 'foo' => '{{> test2}}' 267 ], 268 'helpers' => [ 269 'testpix' => $singlerender 270 ], 271 'js' => 'some nasty JS', 272 'expected' => 'core, move,', 273 'include' => false 274 ], 275 'partial double nested with js from context single render' => [ 276 'templates' => [ 277 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}', 278 'test2' => '{{#js}} some nasty JS {{/js}}', 279 ], 280 'torender' => 'test', 281 'context' => [ 282 'foo' => '{{{bar}}}', 283 'bar' => '{{> test2}}' 284 ], 285 'helpers' => [ 286 'testpix' => $singlerender 287 ], 288 'js' => 'some nasty JS', 289 'expected' => 'core, move, {{> test2}}', 290 'include' => false 291 ], 292 'partial double nested with js from context recursive render' => [ 293 'templates' => [ 294 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}', 295 'test2' => '{{#js}} some nasty JS {{/js}}', 296 ], 297 'torender' => 'test', 298 'context' => [ 299 'foo' => '{{bar}}', 300 'bar' => '{{> test2}}' 301 ], 302 'helpers' => [ 303 'testpix' => $recursiverender 304 ], 305 'js' => 'some nasty JS', 306 'expected' => 'core, move,', 307 'include' => false 308 ], 309 'array context depth 1' => [ 310 'templates' => [ 311 'test' => '{{#items}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/items}}' 312 ], 313 'torender' => 'test', 314 'context' => [ 315 'items' => [ 316 'legit', 317 '{{#js}}some nasty JS{{/js}}' 318 ] 319 ], 320 'helpers' => [ 321 'testpix' => $recursiverender 322 ], 323 'js' => 'some nasty JS', 324 'expected' => 'core, move, legit core, move,', 325 'include' => false 326 ], 327 'array context depth 2' => [ 328 'templates' => [ 329 'test' => '{{#items}}{{#subitems}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/subitems}}{{/items}}' 330 ], 331 'torender' => 'test', 332 'context' => [ 333 'items' => [ 334 [ 335 'subitems' => [ 336 'legit', 337 '{{#js}}some nasty JS{{/js}}' 338 ] 339 ], 340 ] 341 ], 342 'helpers' => [ 343 'testpix' => $recursiverender 344 ], 345 'js' => 'some nasty JS', 346 'expected' => 'core, move, legit core, move,', 347 'include' => false 348 ], 349 'object context depth 1' => [ 350 'templates' => [ 351 'test' => '{{#items}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/items}}' 352 ], 353 'torender' => 'test', 354 'context' => (object) [ 355 'items' => [ 356 'legit', 357 '{{#js}}some nasty JS{{/js}}' 358 ] 359 ], 360 'helpers' => [ 361 'testpix' => $recursiverender 362 ], 363 'js' => 'some nasty JS', 364 'expected' => 'core, move, legit core, move,', 365 'include' => false 366 ], 367 'object context depth 2' => [ 368 'templates' => [ 369 'test' => '{{#items}}{{#subitems}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/subitems}}{{/items}}' 370 ], 371 'torender' => 'test', 372 'context' => (object) [ 373 'items' => [ 374 (object) [ 375 'subitems' => [ 376 'legit', 377 '{{#js}}some nasty JS{{/js}}' 378 ] 379 ], 380 ] 381 ], 382 'helpers' => [ 383 'testpix' => $recursiverender 384 ], 385 'js' => 'some nasty JS', 386 'expected' => 'core, move, legit core, move,', 387 'include' => false 388 ], 389 'change delimeters' => [ 390 'templates' => [ 391 'test' => '{{#testpix}} core, move, {{{foo}}}{{/testpix}}' 392 ], 393 'torender' => 'test', 394 'context' => [ 395 'foo' => '{{=<% %>=}} <%#js%>some nasty JS,<%/js%>' 396 ], 397 'helpers' => [ 398 'testpix' => $recursiverender 399 ], 400 'js' => 'some nasty JS', 401 'expected' => 'core, move,', 402 'include' => false 403 ] 404 ]; 405 } 406 407 /** 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 417 */ 418 public function test_core_mustache_engine_strips_js_helper( 419 $templates, 420 $torender, 421 $context, 422 $helpers, 423 $js, 424 $expected, 425 $include 426 ) { 427 $page = new \moodle_page(); 428 $renderer = $page->get_renderer('core'); 429 430 // Get the mustache engine from the renderer. 431 $reflection = new \ReflectionMethod($renderer, 'get_mustache'); 432 $reflection->setAccessible(true); 433 $engine = $reflection->invoke($renderer); 434 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); 439 440 // Add our test helpers. 441 $helpercollection = $engine->getHelpers(); 442 foreach ($helpers as $name => $function) { 443 $helpercollection->add($name, $function); 444 } 445 446 // Add our test template to be rendered. 447 foreach ($templates as $name => $template) { 448 $loader->setTemplate($name, $template); 449 } 450 451 // Confirm that the rendered template matches what we expect. 452 $this->assertEquals($expected, trim($engine->render($torender, $context))); 453 454 if ($include) { 455 // Confirm that the JS was added to the page. 456 $this->assertStringContainsString($js, $page->requires->get_end_code()); 457 } else { 458 // Confirm that the JS wasn't added to the page. 459 $this->assertStringNotContainsString($js, $page->requires->get_end_code()); 460 } 461 } 462 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body