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