gluon-web-remote

Web remote Administration for big size of gluon routers
git clone git://archive.git.mtrnord.blog/MTRNord/gluon-web-remote.git
Log | Files | Refs | README

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 }