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