update to smarty v3.1.24
[GitHub/Stricted/Domain-Control-Panel.git] / lib / api / smarty / sysplugins / smarty_security.php
CommitLineData
2aa91ff2
S
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 */
22class 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();
ccd27f54
S
57 /**
58 * List of trusted constants names
59 *
60 * @var array
61 */
62 public $trusted_constants = array();
2aa91ff2
S
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();
ccd27f54
S
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();
2aa91ff2
S
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',
2aa91ff2
S
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',
cd8826ea
S
121 'count',
122 'nl2br',
2aa91ff2
S
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();
ccd27f54
S
152 /**
153 * This is an array of disabled special $smarty variables.
154 *
155 * @var array
156 */
157 public $disabled_special_smarty_vars = array();
2aa91ff2
S
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;
ccd27f54
S
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;
2aa91ff2
S
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
ccd27f54
S
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
2aa91ff2
S
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
ccd27f54
S
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
2aa91ff2
S
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
ccd27f54
S
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
2aa91ff2
S
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 }
ccd27f54
S
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 }
2aa91ff2 644}