1 <?php
2 /**
3 * Smarty plugin
4 *
5 * @package Smarty
6 * @subpackage Security
7 * @author Uwe Tews
8 */
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 */
19 /**
20 * This class does contain the security settings
21 */
22 class Smarty_Security {
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 * This is an array of trusted static classes.
53 *
54 * If empty access to all static classes is allowed.
55 * If set to 'none' none is allowed.
56 * @var array
57 */
58 public $static_classes = array();
59 /**
60 * This is an array of trusted PHP functions.
61 *
62 * If empty all functions are allowed.
63 * To disable all PHP functions set $php_functions = null.
64 * @var array
65 */
66 public $php_functions = array(
67 'isset', 'empty',
68 'count', 'sizeof',
69 'in_array', 'is_array',
70 'time',
71 'nl2br',
72 );
73 /**
74 * This is an array of trusted PHP modifers.
75 *
76 * If empty all modifiers are allowed.
77 * To disable all modifier set $modifiers = null.
78 * @var array
79 */
80 public $php_modifiers = array(
81 'escape',
82 'count'
83 );
84 /**
85 * This is an array of allowed tags.
86 *
87 * If empty no restriction by allowed_tags.
88 * @var array
89 */
90 public $allowed_tags = array();
91 /**
92 * This is an array of disabled tags.
93 *
94 * If empty no restriction by disabled_tags.
95 * @var array
96 */
97 public $disabled_tags = array();
98 /**
99 * This is an array of allowed modifier plugins.
100 *
101 * If empty no restriction by allowed_modifiers.
102 * @var array
103 */
104 public $allowed_modifiers = array();
105 /**
106 * This is an array of disabled modifier plugins.
107 *
108 * If empty no restriction by disabled_modifiers.
109 * @var array
110 */
111 public $disabled_modifiers = array();
112 /**
113 * This is an array of trusted streams.
114 *
115 * If empty all streams are allowed.
116 * To disable all streams set $streams = null.
117 * @var array
118 */
119 public $streams = array('file');
120 /**
121 * + flag if constants can be accessed from template
122 * @var boolean
123 */
124 public $allow_constants = true;
125 /**
126 * + flag if super globals can be accessed from template
127 * @var boolean
128 */
129 public $allow_super_globals = true;
131 /**
132 * Cache for $resource_dir lookups
133 * @var array
134 */
135 protected $_resource_dir = null;
136 /**
137 * Cache for $template_dir lookups
138 * @var array
139 */
140 protected $_template_dir = null;
141 /**
142 * Cache for $config_dir lookups
143 * @var array
144 */
145 protected $_config_dir = null;
146 /**
147 * Cache for $secure_dir lookups
148 * @var array
149 */
150 protected $_secure_dir = null;
151 /**
152 * Cache for $php_resource_dir lookups
153 * @var array
154 */
155 protected $_php_resource_dir = null;
156 /**
157 * Cache for $trusted_dir lookups
158 * @var array
159 */
160 protected $_trusted_dir = null;
163 /**
164 * @param Smarty $smarty
165 */
166 public function __construct($smarty)
167 {
168 $this->smarty = $smarty;
169 }
171 /**
172 * Check if PHP function is trusted.
173 *
174 * @param string $function_name
175 * @param object $compiler compiler object
176 * @return boolean true if function is trusted
177 * @throws SmartyCompilerException if php function is not trusted
178 */
179 public function isTrustedPhpFunction($function_name, $compiler)
180 {
181 if (isset($this->php_functions) && (empty($this->php_functions) || in_array($function_name, $this->php_functions))) {
182 return true;
183 }
185 $compiler->trigger_template_error("PHP function '{$function_name}' not allowed by security setting");
186 return false; // should not, but who knows what happens to the compiler in the future?
187 }
189 /**
190 * Check if static class is trusted.
191 *
192 * @param string $class_name
193 * @param object $compiler compiler object
194 * @return boolean true if class is trusted
195 * @throws SmartyCompilerException if static class is not trusted
196 */
197 public function isTrustedStaticClass($class_name, $compiler)
198 {
199 if (isset($this->static_classes) && (empty($this->static_classes) || in_array($class_name, $this->static_classes))) {
200 return true;
201 }
203 $compiler->trigger_template_error("access to static class '{$class_name}' not allowed by security setting");
204 return false; // should not, but who knows what happens to the compiler in the future?
205 }
207 /**
208 * Check if PHP modifier is trusted.
209 *
210 * @param string $modifier_name
211 * @param object $compiler compiler object
212 * @return boolean true if modifier is trusted
213 * @throws SmartyCompilerException if modifier is not trusted
214 */
215 public function isTrustedPhpModifier($modifier_name, $compiler)
216 {
217 if (isset($this->php_modifiers) && (empty($this->php_modifiers) || in_array($modifier_name, $this->php_modifiers))) {
218 return true;
219 }
221 $compiler->trigger_template_error("modifier '{$modifier_name}' not allowed by security setting");
222 return false; // should not, but who knows what happens to the compiler in the future?
223 }
225 /**
226 * Check if tag is trusted.
227 *
228 * @param string $tag_name
229 * @param object $compiler compiler object
230 * @return boolean true if tag is trusted
231 * @throws SmartyCompilerException if modifier is not trusted
232 */
233 public function isTrustedTag($tag_name, $compiler)
234 {
235 // check for internal always required tags
236 if (in_array($tag_name, array('assign', 'call', 'private_filter', 'private_block_plugin', 'private_function_plugin', 'private_object_block_function',
237 'private_object_function', 'private_registered_function', 'private_registered_block', 'private_special_variable', 'private_print_expression', 'private_modifier'))) {
238 return true;
239 }
240 // check security settings
241 if (empty($this->allowed_tags)) {
242 if (empty($this->disabled_tags) || !in_array($tag_name, $this->disabled_tags)) {
243 return true;
244 } else {
245 $compiler->trigger_template_error("tag '{$tag_name}' disabled by security setting", $compiler->lex->taglineno);
246 }
247 } else if (in_array($tag_name, $this->allowed_tags) && !in_array($tag_name, $this->disabled_tags)) {
248 return true;
249 } else {
250 $compiler->trigger_template_error("tag '{$tag_name}' not allowed by security setting", $compiler->lex->taglineno);
251 }
252 return false; // should not, but who knows what happens to the compiler in the future?
253 }
255 /**
256 * Check if modifier plugin is trusted.
257 *
258 * @param string $modifier_name
259 * @param object $compiler compiler object
260 * @return boolean true if tag is trusted
261 * @throws SmartyCompilerException if modifier is not trusted
262 */
263 public function isTrustedModifier($modifier_name, $compiler)
264 {
265 // check for internal always allowed modifier
266 if (in_array($modifier_name, array('default'))) {
267 return true;
268 }
269 // check security settings
270 if (empty($this->allowed_modifiers)) {
271 if (empty($this->disabled_modifiers) || !in_array($modifier_name, $this->disabled_modifiers)) {
272 return true;
273 } else {
274 $compiler->trigger_template_error("modifier '{$modifier_name}' disabled by security setting", $compiler->lex->taglineno);
275 }
276 } else if (in_array($modifier_name, $this->allowed_modifiers) && !in_array($modifier_name, $this->disabled_modifiers)) {
277 return true;
278 } else {
279 $compiler->trigger_template_error("modifier '{$modifier_name}' not allowed by security setting", $compiler->lex->taglineno);
280 }
281 return false; // should not, but who knows what happens to the compiler in the future?
282 }
284 /**
285 * Check if stream is trusted.
286 *
287 * @param string $stream_name
288 * @return boolean true if stream is trusted
289 * @throws SmartyException if stream is not trusted
290 */
291 public function isTrustedStream($stream_name)
292 {
293 if (isset($this->streams) && (empty($this->streams) || in_array($stream_name, $this->streams))) {
294 return true;
295 }
297 throw new SmartyException("stream '{$stream_name}' not allowed by security setting");
298 }
300 /**
301 * Check if directory of file resource is trusted.
302 *
303 * @param string $filepath
304 * @return boolean true if directory is trusted
305 * @throws SmartyException if directory is not trusted
306 */
307 public function isTrustedResourceDir($filepath)
308 {
309 $_template = false;
310 $_config = false;
311 $_secure = false;
313 $_template_dir = $this->smarty->getTemplateDir();
314 $_config_dir = $this->smarty->getConfigDir();
316 // check if index is outdated
317 if ((!$this->_template_dir || $this->_template_dir !== $_template_dir)
318 || (!$this->_config_dir || $this->_config_dir !== $_config_dir)
319 || (!empty($this->secure_dir) && (!$this->_secure_dir || $this->_secure_dir !== $this->secure_dir))
320 ) {
321 $this->_resource_dir = array();
322 $_template = true;
323 $_config = true;
324 $_secure = !empty($this->secure_dir);
325 }
327 // rebuild template dir index
328 if ($_template) {
329 $this->_template_dir = $_template_dir;
330 foreach ($_template_dir as $directory) {
331 $directory = realpath($directory);
332 $this->_resource_dir[$directory] = true;
333 }
334 }
336 // rebuild config dir index
337 if ($_config) {
338 $this->_config_dir = $_config_dir;
339 foreach ($_config_dir as $directory) {
340 $directory = realpath($directory);
341 $this->_resource_dir[$directory] = true;
342 }
343 }
345 // rebuild secure dir index
346 if ($_secure) {
347 $this->_secure_dir = $this->secure_dir;
348 foreach ((array) $this->secure_dir as $directory) {
349 $directory = realpath($directory);
350 $this->_resource_dir[$directory] = true;
351 }
352 }
354 $_filepath = realpath($filepath);
355 $directory = dirname($_filepath);
356 $_directory = array();
357 while (true) {
358 // remember the directory to add it to _resource_dir in case we're successful
359 $_directory[] = $directory;
360 // test if the directory is trusted
361 if (isset($this->_resource_dir[$directory])) {
362 // merge sub directories of current $directory into _resource_dir to speed up subsequent lookups
363 $this->_resource_dir = array_merge($this->_resource_dir, $_directory);
364 return true;
365 }
366 // abort if we've reached root
367 if (($pos = strrpos($directory, DS)) === false || !isset($directory[1])) {
368 break;
369 }
370 // bubble up one level
371 $directory = substr($directory, 0, $pos);
372 }
374 // give up
375 throw new SmartyException("directory '{$_filepath}' not allowed by security setting");
376 }
378 /**
379 * Check if directory of file resource is trusted.
380 *
381 * @param string $filepath
382 * @return boolean true if directory is trusted
383 * @throws SmartyException if PHP directory is not trusted
384 */
385 public function isTrustedPHPDir($filepath)
386 {
387 if (empty($this->trusted_dir)) {
388 throw new SmartyException("directory '{$filepath}' not allowed by security setting (no trusted_dir specified)");
389 }
391 // check if index is outdated
392 if (!$this->_trusted_dir || $this->_trusted_dir !== $this->trusted_dir) {
393 $this->_php_resource_dir = array();
395 $this->_trusted_dir = $this->trusted_dir;
396 foreach ((array) $this->trusted_dir as $directory) {
397 $directory = realpath($directory);
398 $this->_php_resource_dir[$directory] = true;
399 }
400 }
402 $_filepath = realpath($filepath);
403 $directory = dirname($_filepath);
404 $_directory = array();
405 while (true) {
406 // remember the directory to add it to _resource_dir in case we're successful
407 $_directory[] = $directory;
408 // test if the directory is trusted
409 if (isset($this->_php_resource_dir[$directory])) {
410 // merge sub directories of current $directory into _resource_dir to speed up subsequent lookups
411 $this->_php_resource_dir = array_merge($this->_php_resource_dir, $_directory);
412 return true;
413 }
414 // abort if we've reached root
415 if (($pos = strrpos($directory, DS)) === false || !isset($directory[2])) {
416 break;
417 }
418 // bubble up one level
419 $directory = substr($directory, 0, $pos);
420 }
422 throw new SmartyException("directory '{$_filepath}' not allowed by security setting");
423 }
425 }
427 ?>