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?
20 * This class does contain the security settings
25 * This determines how Smarty handles "<?php ... ?>" tags in templates.
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>
36 public $php_handling = Smarty
::PHP_PASSTHRU
;
38 * This is the list of template directories that are considered secure.
39 * $template_dir is in this list implicitly.
43 public $secure_dir = array();
45 * This is an array of directories where trusted php scripts reside.
46 * {@link $security} is disabled during their inclusion/execution.
50 public $trusted_dir = array();
52 * List of regular expressions (PCRE) that include trusted URIs
56 public $trusted_uri = array();
58 * List of trusted constants names
62 public $trusted_constants = array();
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.
70 public $static_classes = array();
73 * This is an nested array of trusted classes and static methods.
74 * If empty access to all static classes and methods is allowed.
77 * 'class_1' => array('method_1', 'method_2'), // allowed methods listed
78 * 'class_2' => array(), // all methods of class allowed
80 * If set to null none is allowed.
84 public $trusted_static_methods = array();
87 * This is an array of trusted static properties.
88 * If empty access to all static classes and properties is allowed.
91 * 'class_1' => array('prop_1', 'prop_2'), // allowed properties listed
92 * 'class_2' => array(), // all properties of class allowed
94 * If set to null none is allowed.
98 public $trusted_static_properties = array();
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.
106 public $php_functions = array(
109 'in_array', 'is_array',
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.
119 public $php_modifiers = array(
125 * This is an array of allowed tags.
126 * If empty no restriction by allowed_tags.
130 public $allowed_tags = array();
132 * This is an array of disabled tags.
133 * If empty no restriction by disabled_tags.
137 public $disabled_tags = array();
139 * This is an array of allowed modifier plugins.
140 * If empty no restriction by allowed_modifiers.
144 public $allowed_modifiers = array();
146 * This is an array of disabled modifier plugins.
147 * If empty no restriction by disabled_modifiers.
151 public $disabled_modifiers = array();
153 * This is an array of disabled special $smarty variables.
157 public $disabled_special_smarty_vars = array();
159 * This is an array of trusted streams.
160 * If empty all streams are allowed.
161 * To disable all streams set $streams = null.
165 public $streams = array('file');
167 * + flag if constants can be accessed from template
171 public $allow_constants = true;
173 * + flag if super globals can be accessed from template
177 public $allow_super_globals = true;
179 * max template nesting level
183 public $max_template_nesting = 0;
185 * current template nesting level
189 private $_current_template_nesting = 0;
191 * Cache for $resource_dir lookup
195 protected $_resource_dir = null;
197 * Cache for $template_dir lookup
201 protected $_template_dir = null;
203 * Cache for $config_dir lookup
207 protected $_config_dir = null;
209 * Cache for $secure_dir lookup
213 protected $_secure_dir = null;
215 * Cache for $php_resource_dir lookup
219 protected $_php_resource_dir = null;
221 * Cache for $trusted_dir lookup
225 protected $_trusted_dir = null;
228 * @param Smarty $smarty
230 public function __construct($smarty)
232 $this->smarty
= $smarty;
236 * Check if PHP function is trusted.
238 * @param string $function_name
239 * @param object $compiler compiler object
241 * @return boolean true if function is trusted
242 * @throws SmartyCompilerException if php function is not trusted
244 public function isTrustedPhpFunction($function_name, $compiler)
246 if (isset($this->php_functions
) && (empty($this->php_functions
) ||
in_array($function_name, $this->php_functions
))) {
250 $compiler->trigger_template_error("PHP function '{$function_name}' not allowed by security setting");
252 return false; // should not, but who knows what happens to the compiler in the future?
256 * Check if static class is trusted.
258 * @param string $class_name
259 * @param object $compiler compiler object
261 * @return boolean true if class is trusted
262 * @throws SmartyCompilerException if static class is not trusted
264 public function isTrustedStaticClass($class_name, $compiler)
266 if (isset($this->static_classes
) && (empty($this->static_classes
) ||
in_array($class_name, $this->static_classes
))) {
270 $compiler->trigger_template_error("access to static class '{$class_name}' not allowed by security setting");
272 return false; // should not, but who knows what happens to the compiler in the future?
276 * Check if static class method/property is trusted.
278 * @param string $class_name
279 * @param string $params
280 * @param object $compiler compiler object
282 * @return boolean true if class method is trusted
283 * @throws SmartyCompilerException if static class method is not trusted
285 public function isTrustedStaticClassAccess($class_name, $params, $compiler)
287 if (!isset($params[2])) {
289 return $this->isTrustedStaticClass($class_name, $compiler);
291 if ($params[2] == 'method') {
292 $allowed = $this->trusted_static_methods
;
293 $name = substr($params[0], 0, strpos($params[0], '('));
295 $allowed = $this->trusted_static_properties
;
297 $name = substr($params[0], 1);
299 if (isset($allowed)) {
300 if (empty($allowed)) {
302 return $this->isTrustedStaticClass($class_name, $compiler);
304 if (isset($allowed[$class_name])
305 && (empty($allowed[$class_name])
306 ||
in_array($name, $allowed[$class_name]))
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?
316 * Check if PHP modifier is trusted.
318 * @param string $modifier_name
319 * @param object $compiler compiler object
321 * @return boolean true if modifier is trusted
322 * @throws SmartyCompilerException if modifier is not trusted
324 public function isTrustedPhpModifier($modifier_name, $compiler)
326 if (isset($this->php_modifiers
) && (empty($this->php_modifiers
) ||
in_array($modifier_name, $this->php_modifiers
))) {
330 $compiler->trigger_template_error("modifier '{$modifier_name}' not allowed by security setting");
332 return false; // should not, but who knows what happens to the compiler in the future?
336 * Check if tag is trusted.
338 * @param string $tag_name
339 * @param object $compiler compiler object
341 * @return boolean true if tag is trusted
342 * @throws SmartyCompilerException if modifier is not trusted
344 public function isTrustedTag($tag_name, $compiler)
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'))
352 // check security settings
353 if (empty($this->allowed_tags
)) {
354 if (empty($this->disabled_tags
) ||
!in_array($tag_name, $this->disabled_tags
)) {
357 $compiler->trigger_template_error("tag '{$tag_name}' disabled by security setting", $compiler->lex
->taglineno
);
359 } elseif (in_array($tag_name, $this->allowed_tags
) && !in_array($tag_name, $this->disabled_tags
)) {
362 $compiler->trigger_template_error("tag '{$tag_name}' not allowed by security setting", $compiler->lex
->taglineno
);
365 return false; // should not, but who knows what happens to the compiler in the future?
369 * Check if special $smarty variable is trusted.
371 * @param string $var_name
372 * @param object $compiler compiler object
374 * @return boolean true if tag is trusted
375 * @throws SmartyCompilerException if modifier is not trusted
377 public function isTrustedSpecialSmartyVar($var_name, $compiler)
379 if (!in_array($var_name, $this->disabled_special_smarty_vars
)) {
382 $compiler->trigger_template_error("special variable '\$smarty.{$var_name}' not allowed by security setting", $compiler->lex
->taglineno
);
385 return false; // should not, but who knows what happens to the compiler in the future?
389 * Check if modifier plugin is trusted.
391 * @param string $modifier_name
392 * @param object $compiler compiler object
394 * @return boolean true if tag is trusted
395 * @throws SmartyCompilerException if modifier is not trusted
397 public function isTrustedModifier($modifier_name, $compiler)
399 // check for internal always allowed modifier
400 if (in_array($modifier_name, array('default'))) {
403 // check security settings
404 if (empty($this->allowed_modifiers
)) {
405 if (empty($this->disabled_modifiers
) ||
!in_array($modifier_name, $this->disabled_modifiers
)) {
408 $compiler->trigger_template_error("modifier '{$modifier_name}' disabled by security setting", $compiler->lex
->taglineno
);
410 } elseif (in_array($modifier_name, $this->allowed_modifiers
) && !in_array($modifier_name, $this->disabled_modifiers
)) {
413 $compiler->trigger_template_error("modifier '{$modifier_name}' not allowed by security setting", $compiler->lex
->taglineno
);
416 return false; // should not, but who knows what happens to the compiler in the future?
420 * Check if constants are enabled or trusted
422 * @param string $const contant name
423 * @param object $compiler compiler object
427 public function isTrustedConstant($const, $compiler)
429 if (in_array($const, array('true', 'false', 'null'))) {
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");
439 if ($this->allow_constants
) {
442 $compiler->trigger_template_error("Security: access to constants not permitted");
447 * Check if stream is trusted.
449 * @param string $stream_name
451 * @return boolean true if stream is trusted
452 * @throws SmartyException if stream is not trusted
454 public function isTrustedStream($stream_name)
456 if (isset($this->streams
) && (empty($this->streams
) ||
in_array($stream_name, $this->streams
))) {
460 throw new SmartyException("stream '{$stream_name}' not allowed by security setting");
464 * Check if directory of file resource is trusted.
466 * @param string $filepath
468 * @return boolean true if directory is trusted
469 * @throws SmartyException if directory is not trusted
471 public function isTrustedResourceDir($filepath)
477 $_template_dir = $this->smarty
->getTemplateDir();
478 $_config_dir = $this->smarty
->getConfigDir();
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
))
485 $this->_resource_dir
= array();
488 $_secure = !empty($this->secure_dir
);
491 // rebuild template dir index
493 $this->_template_dir
= $_template_dir;
494 foreach ($_template_dir as $directory) {
495 $directory = realpath($directory);
496 $this->_resource_dir
[$directory] = true;
500 // rebuild config dir index
502 $this->_config_dir
= $_config_dir;
503 foreach ($_config_dir as $directory) {
504 $directory = realpath($directory);
505 $this->_resource_dir
[$directory] = true;
509 // rebuild secure dir index
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;
518 $_filepath = realpath($filepath);
519 $directory = dirname($_filepath);
520 $_directory = array();
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);
531 // abort if we've reached root
532 if (($pos = strrpos($directory, DS
)) === false ||
!isset($directory[1])) {
535 // bubble up one level
536 $directory = substr($directory, 0, $pos);
540 throw new SmartyException("directory '{$_filepath}' not allowed by security setting");
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}.
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
555 public function isTrustedUri($uri)
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)) {
567 throw new SmartyException("URI '{$uri}' not allowed by security setting");
571 * Check if directory of file resource is trusted.
573 * @param string $filepath
575 * @return boolean true if directory is trusted
576 * @throws SmartyException if PHP directory is not trusted
578 public function isTrustedPHPDir($filepath)
580 if (empty($this->trusted_dir
)) {
581 throw new SmartyException("directory '{$filepath}' not allowed by security setting (no trusted_dir specified)");
584 // check if index is outdated
585 if (!$this->_trusted_dir ||
$this->_trusted_dir
!== $this->trusted_dir
) {
586 $this->_php_resource_dir
= array();
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;
595 $_filepath = realpath($filepath);
596 $directory = dirname($_filepath);
597 $_directory = array();
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);
608 // abort if we've reached root
609 if (($pos = strrpos($directory, DS
)) === false ||
!isset($directory[2])) {
612 // bubble up one level
613 $directory = substr($directory, 0, $pos);
616 throw new SmartyException("directory '{$_filepath}' not allowed by security setting");
620 * Start template processing
624 * @throws SmartyException
626 public function startTemplate($template)
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}'");
634 * Exit template processing
638 public function exitTemplate($template)
640 if ($this->max_template_nesting
> 0) {
641 $this->_current_template_nesting
--;