smarty_security.php (21431B)
1 <?php 2 /** 3 * Smarty plugin 4 * 5 * @package Smarty 6 * @subpackage Security 7 * @author Uwe Tews 8 */ 9 10 /* 11 * FIXME: Smarty_Security API 12 * - getter and setter instead of public properties would allow cultivating an internal cache properly 13 * - current implementation of isTrustedResourceDir() assumes that Smarty::$template_dir and Smarty::$config_dir are immutable 14 * the cache is killed every time either of the variables change. That means that two distinct Smarty objects with differing 15 * $template_dir or $config_dir should NOT share the same Smarty_Security instance, 16 * as this would lead to (severe) performance penalty! how should this be handled? 17 */ 18 19 /** 20 * This class does contain the security settings 21 */ 22 class Smarty_Security 23 { 24 /** 25 * This determines how Smarty handles "<?php ... ?>" tags in templates. 26 * possible values: 27 * <ul> 28 * <li>Smarty::PHP_PASSTHRU -> echo PHP tags as they are</li> 29 * <li>Smarty::PHP_QUOTE -> escape tags as entities</li> 30 * <li>Smarty::PHP_REMOVE -> remove php tags</li> 31 * <li>Smarty::PHP_ALLOW -> execute php tags</li> 32 * </ul> 33 * 34 * @var integer 35 */ 36 public $php_handling = Smarty::PHP_PASSTHRU; 37 /** 38 * This is the list of template directories that are considered secure. 39 * $template_dir is in this list implicitly. 40 * 41 * @var array 42 */ 43 public $secure_dir = array(); 44 /** 45 * This is an array of directories where trusted php scripts reside. 46 * {@link $security} is disabled during their inclusion/execution. 47 * 48 * @var array 49 */ 50 public $trusted_dir = array(); 51 /** 52 * List of regular expressions (PCRE) that include trusted URIs 53 * 54 * @var array 55 */ 56 public $trusted_uri = array(); 57 /** 58 * List of trusted constants names 59 * 60 * @var array 61 */ 62 public $trusted_constants = array(); 63 /** 64 * This is an array of trusted static classes. 65 * If empty access to all static classes is allowed. 66 * If set to 'none' none is allowed. 67 * 68 * @var array 69 */ 70 public $static_classes = array(); 71 72 /** 73 * This is an nested array of trusted classes and static methods. 74 * If empty access to all static classes and methods is allowed. 75 * Format: 76 * array ( 77 * 'class_1' => array('method_1', 'method_2'), // allowed methods listed 78 * 'class_2' => array(), // all methods of class allowed 79 * ) 80 * If set to null none is allowed. 81 * 82 * @var array 83 */ 84 public $trusted_static_methods = array(); 85 86 /** 87 * This is an array of trusted static properties. 88 * If empty access to all static classes and properties is allowed. 89 * Format: 90 * array ( 91 * 'class_1' => array('prop_1', 'prop_2'), // allowed properties listed 92 * 'class_2' => array(), // all properties of class allowed 93 * ) 94 * If set to null none is allowed. 95 * 96 * @var array 97 */ 98 public $trusted_static_properties = array(); 99 /** 100 * This is an array of trusted PHP functions. 101 * If empty all functions are allowed. 102 * To disable all PHP functions set $php_functions = null. 103 * 104 * @var array 105 */ 106 public $php_functions = array( 107 'isset', 'empty', 108 'count', 'sizeof', 109 'in_array', 'is_array', 110 'time', 111 ); 112 /** 113 * This is an array of trusted PHP modifiers. 114 * If empty all modifiers are allowed. 115 * To disable all modifier set $php_modifiers = null. 116 * 117 * @var array 118 */ 119 public $php_modifiers = array( 120 'escape', 121 'count', 122 'nl2br', 123 ); 124 /** 125 * This is an array of allowed tags. 126 * If empty no restriction by allowed_tags. 127 * 128 * @var array 129 */ 130 public $allowed_tags = array(); 131 /** 132 * This is an array of disabled tags. 133 * If empty no restriction by disabled_tags. 134 * 135 * @var array 136 */ 137 public $disabled_tags = array(); 138 /** 139 * This is an array of allowed modifier plugins. 140 * If empty no restriction by allowed_modifiers. 141 * 142 * @var array 143 */ 144 public $allowed_modifiers = array(); 145 /** 146 * This is an array of disabled modifier plugins. 147 * If empty no restriction by disabled_modifiers. 148 * 149 * @var array 150 */ 151 public $disabled_modifiers = array(); 152 /** 153 * This is an array of disabled special $smarty variables. 154 * 155 * @var array 156 */ 157 public $disabled_special_smarty_vars = array(); 158 /** 159 * This is an array of trusted streams. 160 * If empty all streams are allowed. 161 * To disable all streams set $streams = null. 162 * 163 * @var array 164 */ 165 public $streams = array('file'); 166 /** 167 * + flag if constants can be accessed from template 168 * 169 * @var boolean 170 */ 171 public $allow_constants = true; 172 /** 173 * + flag if super globals can be accessed from template 174 * 175 * @var boolean 176 */ 177 public $allow_super_globals = true; 178 /** 179 * max template nesting level 180 * 181 * @var int 182 */ 183 public $max_template_nesting = 0; 184 /** 185 * current template nesting level 186 * 187 * @var int 188 */ 189 private $_current_template_nesting = 0; 190 /** 191 * Cache for $resource_dir lookup 192 * 193 * @var array 194 */ 195 protected $_resource_dir = null; 196 /** 197 * Cache for $template_dir lookup 198 * 199 * @var array 200 */ 201 protected $_template_dir = null; 202 /** 203 * Cache for $config_dir lookup 204 * 205 * @var array 206 */ 207 protected $_config_dir = null; 208 /** 209 * Cache for $secure_dir lookup 210 * 211 * @var array 212 */ 213 protected $_secure_dir = null; 214 /** 215 * Cache for $php_resource_dir lookup 216 * 217 * @var array 218 */ 219 protected $_php_resource_dir = null; 220 /** 221 * Cache for $trusted_dir lookup 222 * 223 * @var array 224 */ 225 protected $_trusted_dir = null; 226 227 /** 228 * @param Smarty $smarty 229 */ 230 public function __construct($smarty) 231 { 232 $this->smarty = $smarty; 233 } 234 235 /** 236 * Check if PHP function is trusted. 237 * 238 * @param string $function_name 239 * @param object $compiler compiler object 240 * 241 * @return boolean true if function is trusted 242 * @throws SmartyCompilerException if php function is not trusted 243 */ 244 public function isTrustedPhpFunction($function_name, $compiler) 245 { 246 if (isset($this->php_functions) && (empty($this->php_functions) || in_array($function_name, $this->php_functions))) { 247 return true; 248 } 249 250 $compiler->trigger_template_error("PHP function '{$function_name}' not allowed by security setting"); 251 252 return false; // should not, but who knows what happens to the compiler in the future? 253 } 254 255 /** 256 * Check if static class is trusted. 257 * 258 * @param string $class_name 259 * @param object $compiler compiler object 260 * 261 * @return boolean true if class is trusted 262 * @throws SmartyCompilerException if static class is not trusted 263 */ 264 public function isTrustedStaticClass($class_name, $compiler) 265 { 266 if (isset($this->static_classes) && (empty($this->static_classes) || in_array($class_name, $this->static_classes))) { 267 return true; 268 } 269 270 $compiler->trigger_template_error("access to static class '{$class_name}' not allowed by security setting"); 271 272 return false; // should not, but who knows what happens to the compiler in the future? 273 } 274 275 /** 276 * Check if static class method/property is trusted. 277 * 278 * @param string $class_name 279 * @param string $params 280 * @param object $compiler compiler object 281 * 282 * @return boolean true if class method is trusted 283 * @throws SmartyCompilerException if static class method is not trusted 284 */ 285 public function isTrustedStaticClassAccess($class_name, $params, $compiler) 286 { 287 if (!isset($params[2])) { 288 // fall back 289 return $this->isTrustedStaticClass($class_name, $compiler); 290 } 291 if ($params[2] == 'method') { 292 $allowed = $this->trusted_static_methods; 293 $name = substr($params[0], 0, strpos($params[0], '(')); 294 } else { 295 $allowed = $this->trusted_static_properties; 296 // strip '$' 297 $name = substr($params[0], 1); 298 } 299 if (isset($allowed)) { 300 if (empty($allowed)) { 301 // fall back 302 return $this->isTrustedStaticClass($class_name, $compiler); 303 } 304 if (isset($allowed[$class_name]) 305 && (empty($allowed[$class_name]) 306 || in_array($name, $allowed[$class_name])) 307 ) { 308 return true; 309 } 310 } 311 $compiler->trigger_template_error("access to static class '{$class_name}' {$params[2]} '{$name}' not allowed by security setting"); 312 return false; // should not, but who knows what happens to the compiler in the future? 313 } 314 315 /** 316 * Check if PHP modifier is trusted. 317 * 318 * @param string $modifier_name 319 * @param object $compiler compiler object 320 * 321 * @return boolean true if modifier is trusted 322 * @throws SmartyCompilerException if modifier is not trusted 323 */ 324 public function isTrustedPhpModifier($modifier_name, $compiler) 325 { 326 if (isset($this->php_modifiers) && (empty($this->php_modifiers) || in_array($modifier_name, $this->php_modifiers))) { 327 return true; 328 } 329 330 $compiler->trigger_template_error("modifier '{$modifier_name}' not allowed by security setting"); 331 332 return false; // should not, but who knows what happens to the compiler in the future? 333 } 334 335 /** 336 * Check if tag is trusted. 337 * 338 * @param string $tag_name 339 * @param object $compiler compiler object 340 * 341 * @return boolean true if tag is trusted 342 * @throws SmartyCompilerException if modifier is not trusted 343 */ 344 public function isTrustedTag($tag_name, $compiler) 345 { 346 // check for internal always required tags 347 if (in_array($tag_name, array('assign', 'call', 'private_filter', 'private_block_plugin', 'private_function_plugin', 'private_object_block_function', 348 'private_object_function', 'private_registered_function', 'private_registered_block', 'private_special_variable', 'private_print_expression', 'private_modifier')) 349 ) { 350 return true; 351 } 352 // check security settings 353 if (empty($this->allowed_tags)) { 354 if (empty($this->disabled_tags) || !in_array($tag_name, $this->disabled_tags)) { 355 return true; 356 } else { 357 $compiler->trigger_template_error("tag '{$tag_name}' disabled by security setting", $compiler->lex->taglineno); 358 } 359 } elseif (in_array($tag_name, $this->allowed_tags) && !in_array($tag_name, $this->disabled_tags)) { 360 return true; 361 } else { 362 $compiler->trigger_template_error("tag '{$tag_name}' not allowed by security setting", $compiler->lex->taglineno); 363 } 364 365 return false; // should not, but who knows what happens to the compiler in the future? 366 } 367 368 /** 369 * Check if special $smarty variable is trusted. 370 * 371 * @param string $var_name 372 * @param object $compiler compiler object 373 * 374 * @return boolean true if tag is trusted 375 * @throws SmartyCompilerException if modifier is not trusted 376 */ 377 public function isTrustedSpecialSmartyVar($var_name, $compiler) 378 { 379 if (!in_array($var_name, $this->disabled_special_smarty_vars)) { 380 return true; 381 } else { 382 $compiler->trigger_template_error("special variable '\$smarty.{$var_name}' not allowed by security setting", $compiler->lex->taglineno); 383 } 384 385 return false; // should not, but who knows what happens to the compiler in the future? 386 } 387 388 /** 389 * Check if modifier plugin is trusted. 390 * 391 * @param string $modifier_name 392 * @param object $compiler compiler object 393 * 394 * @return boolean true if tag is trusted 395 * @throws SmartyCompilerException if modifier is not trusted 396 */ 397 public function isTrustedModifier($modifier_name, $compiler) 398 { 399 // check for internal always allowed modifier 400 if (in_array($modifier_name, array('default'))) { 401 return true; 402 } 403 // check security settings 404 if (empty($this->allowed_modifiers)) { 405 if (empty($this->disabled_modifiers) || !in_array($modifier_name, $this->disabled_modifiers)) { 406 return true; 407 } else { 408 $compiler->trigger_template_error("modifier '{$modifier_name}' disabled by security setting", $compiler->lex->taglineno); 409 } 410 } elseif (in_array($modifier_name, $this->allowed_modifiers) && !in_array($modifier_name, $this->disabled_modifiers)) { 411 return true; 412 } else { 413 $compiler->trigger_template_error("modifier '{$modifier_name}' not allowed by security setting", $compiler->lex->taglineno); 414 } 415 416 return false; // should not, but who knows what happens to the compiler in the future? 417 } 418 419 /** 420 * Check if constants are enabled or trusted 421 * 422 * @param string $const contant name 423 * @param object $compiler compiler object 424 * 425 * @return bool 426 */ 427 public function isTrustedConstant($const, $compiler) 428 { 429 if (in_array($const, array('true', 'false', 'null'))) { 430 return true; 431 } 432 if (!empty($this->trusted_constants)) { 433 if (!in_array($const, $this->trusted_constants)) { 434 $compiler->trigger_template_error("Security: access to constant '{$const}' not permitted"); 435 return false; 436 } 437 return true; 438 } 439 if ($this->allow_constants) { 440 return true; 441 } 442 $compiler->trigger_template_error("Security: access to constants not permitted"); 443 return false; 444 } 445 446 /** 447 * Check if stream is trusted. 448 * 449 * @param string $stream_name 450 * 451 * @return boolean true if stream is trusted 452 * @throws SmartyException if stream is not trusted 453 */ 454 public function isTrustedStream($stream_name) 455 { 456 if (isset($this->streams) && (empty($this->streams) || in_array($stream_name, $this->streams))) { 457 return true; 458 } 459 460 throw new SmartyException("stream '{$stream_name}' not allowed by security setting"); 461 } 462 463 /** 464 * Check if directory of file resource is trusted. 465 * 466 * @param string $filepath 467 * 468 * @return boolean true if directory is trusted 469 * @throws SmartyException if directory is not trusted 470 */ 471 public function isTrustedResourceDir($filepath) 472 { 473 $_template = false; 474 $_config = false; 475 $_secure = false; 476 477 $_template_dir = $this->smarty->getTemplateDir(); 478 $_config_dir = $this->smarty->getConfigDir(); 479 480 // check if index is outdated 481 if ((!$this->_template_dir || $this->_template_dir !== $_template_dir) 482 || (!$this->_config_dir || $this->_config_dir !== $_config_dir) 483 || (!empty($this->secure_dir) && (!$this->_secure_dir || $this->_secure_dir !== $this->secure_dir)) 484 ) { 485 $this->_resource_dir = array(); 486 $_template = true; 487 $_config = true; 488 $_secure = !empty($this->secure_dir); 489 } 490 491 // rebuild template dir index 492 if ($_template) { 493 $this->_template_dir = $_template_dir; 494 foreach ($_template_dir as $directory) { 495 $directory = realpath($directory); 496 $this->_resource_dir[$directory] = true; 497 } 498 } 499 500 // rebuild config dir index 501 if ($_config) { 502 $this->_config_dir = $_config_dir; 503 foreach ($_config_dir as $directory) { 504 $directory = realpath($directory); 505 $this->_resource_dir[$directory] = true; 506 } 507 } 508 509 // rebuild secure dir index 510 if ($_secure) { 511 $this->_secure_dir = $this->secure_dir; 512 foreach ((array) $this->secure_dir as $directory) { 513 $directory = realpath($directory); 514 $this->_resource_dir[$directory] = true; 515 } 516 } 517 518 $_filepath = realpath($filepath); 519 $directory = dirname($_filepath); 520 $_directory = array(); 521 while (true) { 522 // remember the directory to add it to _resource_dir in case we're successful 523 $_directory[$directory] = true; 524 // test if the directory is trusted 525 if (isset($this->_resource_dir[$directory])) { 526 // merge sub directories of current $directory into _resource_dir to speed up subsequent lookup 527 $this->_resource_dir = array_merge($this->_resource_dir, $_directory); 528 529 return true; 530 } 531 // abort if we've reached root 532 if (($pos = strrpos($directory, DS)) === false || !isset($directory[1])) { 533 break; 534 } 535 // bubble up one level 536 $directory = substr($directory, 0, $pos); 537 } 538 539 // give up 540 throw new SmartyException("directory '{$_filepath}' not allowed by security setting"); 541 } 542 543 /** 544 * Check if URI (e.g. {fetch} or {html_image}) is trusted 545 * To simplify things, isTrustedUri() resolves all input to "{$PROTOCOL}://{$HOSTNAME}". 546 * So "http://username:password@hello.world.example.org:8080/some-path?some=query-string" 547 * is reduced to "http://hello.world.example.org" prior to applying the patters from {@link $trusted_uri}. 548 * 549 * @param string $uri 550 * 551 * @return boolean true if URI is trusted 552 * @throws SmartyException if URI is not trusted 553 * @uses $trusted_uri for list of patterns to match against $uri 554 */ 555 public function isTrustedUri($uri) 556 { 557 $_uri = parse_url($uri); 558 if (!empty($_uri['scheme']) && !empty($_uri['host'])) { 559 $_uri = $_uri['scheme'] . '://' . $_uri['host']; 560 foreach ($this->trusted_uri as $pattern) { 561 if (preg_match($pattern, $_uri)) { 562 return true; 563 } 564 } 565 } 566 567 throw new SmartyException("URI '{$uri}' not allowed by security setting"); 568 } 569 570 /** 571 * Check if directory of file resource is trusted. 572 * 573 * @param string $filepath 574 * 575 * @return boolean true if directory is trusted 576 * @throws SmartyException if PHP directory is not trusted 577 */ 578 public function isTrustedPHPDir($filepath) 579 { 580 if (empty($this->trusted_dir)) { 581 throw new SmartyException("directory '{$filepath}' not allowed by security setting (no trusted_dir specified)"); 582 } 583 584 // check if index is outdated 585 if (!$this->_trusted_dir || $this->_trusted_dir !== $this->trusted_dir) { 586 $this->_php_resource_dir = array(); 587 588 $this->_trusted_dir = $this->trusted_dir; 589 foreach ((array) $this->trusted_dir as $directory) { 590 $directory = realpath($directory); 591 $this->_php_resource_dir[$directory] = true; 592 } 593 } 594 595 $_filepath = realpath($filepath); 596 $directory = dirname($_filepath); 597 $_directory = array(); 598 while (true) { 599 // remember the directory to add it to _resource_dir in case we're successful 600 $_directory[] = $directory; 601 // test if the directory is trusted 602 if (isset($this->_php_resource_dir[$directory])) { 603 // merge sub directories of current $directory into _resource_dir to speed up subsequent lookup 604 $this->_php_resource_dir = array_merge($this->_php_resource_dir, $_directory); 605 606 return true; 607 } 608 // abort if we've reached root 609 if (($pos = strrpos($directory, DS)) === false || !isset($directory[2])) { 610 break; 611 } 612 // bubble up one level 613 $directory = substr($directory, 0, $pos); 614 } 615 616 throw new SmartyException("directory '{$_filepath}' not allowed by security setting"); 617 } 618 619 /** 620 * Start template processing 621 * 622 * @param $template 623 * 624 * @throws SmartyException 625 */ 626 public function startTemplate($template) 627 { 628 if ($this->max_template_nesting > 0 && $this->_current_template_nesting ++ >= $this->max_template_nesting) { 629 throw new SmartyException("maximum template nesting level of '{$this->max_template_nesting}' exceeded when calling '{$template->template_resource}'"); 630 } 631 } 632 633 /** 634 * Exit template processing 635 * 636 * @param $template 637 */ 638 public function exitTemplate($template) 639 { 640 if ($this->max_template_nesting > 0) { 641 $this->_current_template_nesting --; 642 } 643 } 644 }