Commit 06407917 by seldaek

Added support for instance and static method calls white-listing in…

Added support for instance and static method calls white-listing in Dwoo_Security_Policy (see allowMethod()), this is hardly efficient though for instance calls since it has to do runtime checks so use it with caution git-svn-id: http://svn.dwoo.org/trunk@345 0598d79b-80c4-4d41-97ba-ac86fbbd088b
parent ad60ed1a
...@@ -10,6 +10,10 @@ ...@@ -10,6 +10,10 @@
+ Improved parsing of array() to support real php array syntax as well + Improved parsing of array() to support real php array syntax as well
as variables as array keys, thanks to acecream for the help as variables as array keys, thanks to acecream for the help
+ Improved parsing of named parameters that can now be quoted + Improved parsing of named parameters that can now be quoted
+ Added support for instance and static method calls white-listing in
Dwoo_Security_Policy (see allowMethod()), this is hardly efficient
though for instance calls since it has to do runtime checks so use
it with caution
* Added $this->viewParam support to ZendFramework adapter through a * Added $this->viewParam support to ZendFramework adapter through a
Dwoo_Adapters_ZendFramework_Dwoo class that extends Dwoo, you should use Dwoo_Adapters_ZendFramework_Dwoo class that extends Dwoo, you should use
this if you called setEngine() on the ZF view this if you called setEngine() on the ZF view
......
...@@ -1706,7 +1706,11 @@ class Dwoo_Compiler implements Dwoo_ICompiler ...@@ -1706,7 +1706,11 @@ class Dwoo_Compiler implements Dwoo_ICompiler
} }
if ($curBlock === 'method' || $func === 'do' || strstr($func, '::') !== false) { if ($curBlock === 'method' || $func === 'do' || strstr($func, '::') !== false) {
$pluginType = Dwoo_Core::NATIVE_PLUGIN; // handle static method calls with security policy
if (strstr($func, '::') !== false && $this->securityPolicy !== null && $this->securityPolicy->isMethodAllowed(explode('::', strtolower($func))) !== true) {
throw new Dwoo_Security_Exception('Call to a disallowed php function : '.$func);
}
$pluginType = Dwoo::NATIVE_PLUGIN;
} else { } else {
$pluginType = $this->getPluginType($func); $pluginType = $this->getPluginType($func);
} }
...@@ -2291,15 +2295,26 @@ class Dwoo_Compiler implements Dwoo_ICompiler ...@@ -2291,15 +2295,26 @@ class Dwoo_Compiler implements Dwoo_ICompiler
} else { } else {
// method // method
if (substr($methMatch[2], 0, 2) === '()') { if (substr($methMatch[2], 0, 2) === '()') {
$parsedCall = '->'.$methMatch[1].'()'; $parsedCall = $methMatch[1].'()';
$ptr += strlen($methMatch[1]) + 2; $ptr += strlen($methMatch[1]) + 2;
} else { } else {
$parsedCall = '->'.$this->parseFunction($methodCall, $ptr, strlen($methodCall), false, 'method', $ptr); $parsedCall = $this->parseFunction($methodCall, $ptr, strlen($methodCall), false, 'method', $ptr);
} }
if ($curBlock === 'root') { if ($this->securityPolicy !== null) {
$output .= $parsedCall; $argPos = strpos($parsedCall, '(');
$method = strtolower(substr($parsedCall, 0, $argPos));
$args = substr($parsedCall, $argPos);
if ($curBlock === 'root') {
$output = '$this->getSecurityPolicy()->callMethod($this, '.$output.', '.var_export($method, true).', array'.$args.')';
} else {
$output = '(($tmp = '.$output.') ? $this->getSecurityPolicy()->callMethod($this, $tmp, '.var_export($method, true).', array'.$args.') : null)';
}
} else { } else {
$output = '(($tmp = '.$output.') ? $tmp'.$parsedCall.' : null)'; if ($curBlock === 'root') {
$output .= '->'.$parsedCall;
} else {
$output = '(($tmp = '.$output.') ? $tmp->'.$parsedCall.' : null)';
}
} }
} }
} }
...@@ -2921,8 +2936,10 @@ class Dwoo_Compiler implements Dwoo_ICompiler ...@@ -2921,8 +2936,10 @@ class Dwoo_Compiler implements Dwoo_ICompiler
$pluginType = -1; $pluginType = -1;
if (($this->securityPolicy === null && (function_exists($name) || strtolower($name) === 'isset' || strtolower($name) === 'empty')) || if (($this->securityPolicy === null && (function_exists($name) || strtolower($name) === 'isset' || strtolower($name) === 'empty')) ||
($this->securityPolicy !== null && in_array(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) !== false)) { ($this->securityPolicy !== null && array_key_exists(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) !== false)) {
$phpFunc = true; $phpFunc = true;
} elseif ($this->securityPolicy !== null && function_exists($name) && array_key_exists(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) === false) {
throw new Dwoo_Security_Exception('Call to a disallowed php function : '.$name);
} }
while ($pluginType <= 0) { while ($pluginType <= 0) {
......
...@@ -47,11 +47,28 @@ class Dwoo_Security_Policy ...@@ -47,11 +47,28 @@ class Dwoo_Security_Policy
*/ */
protected $allowedPhpFunctions = array protected $allowedPhpFunctions = array
( (
'str_repeat', 'number_format', 'htmlentities', 'htmlspecialchars', 'str_repeat' => true,
'long2ip', 'strlen', 'list', 'empty', 'count', 'sizeof', 'in_array', 'is_array', 'number_format' => true,
'htmlentities' => true,
'htmlspecialchars' => true,
'long2ip' => true,
'strlen' => true,
'list' => true,
'empty' => true,
'count' => true,
'sizeof' => true,
'in_array' => true,
'is_array' => true,
); );
/** /**
* methods that are allowed to be used within the template
*
* @var array
*/
protected $allowedMethods = array();
/**
* paths that are safe to use with include or other file-access plugins * paths that are safe to use with include or other file-access plugins
* *
* @var array * @var array
...@@ -116,6 +133,49 @@ class Dwoo_Security_Policy ...@@ -116,6 +133,49 @@ class Dwoo_Security_Policy
} }
/** /**
* adds a class method to the allowed list, this must be used for
* both static and non static method by providing the class name
* and method name to use
*
* @param mixed $class class name or array of array('class', 'method') couples
* @param string $method method name
*/
public function allowMethod($class, $method = null)
{
if (is_array($class))
foreach ($class as $elem)
$this->allowedMethods[strtolower($elem[0])][strtolower($elem[1])] = true;
else
$this->allowedMethods[strtolower($class)][strtolower($method)] = true;
}
/**
* removes a class method from the allowed list
*
* @param mixed $class class name or array of array('class', 'method') couples
* @param string $method method name
*/
public function disallowMethod($class, $method = null)
{
if (is_array($class))
foreach ($class as $elem)
unset($this->allowedMethods[strtolower($elem[0])][strtolower($elem[1])]);
else
unset($this->allowedMethods[strtolower($class)][strtolower($method)]);
}
/**
* returns the list of class methods allowed to run, note that the class names
* and method names are stored in the array keys and not values
*
* @return array
*/
public function getAllowedMethods()
{
return $this->allowedMethods;
}
/**
* adds a directory to the safelist for includes and other file-access plugins * adds a directory to the safelist for includes and other file-access plugins
* *
* note that all the includePath directories you provide to the Dwoo_Template_File class * note that all the includePath directories you provide to the Dwoo_Template_File class
...@@ -196,4 +256,49 @@ class Dwoo_Security_Policy ...@@ -196,4 +256,49 @@ class Dwoo_Security_Policy
{ {
return $this->constHandling; return $this->constHandling;
} }
/**
* this is used at run time to check whether method calls are allowed or not
*
* @param Dwoo_Core $dwoo dwoo instance that calls this
* @param object $obj any object on which the method must be called
* @param string $method lowercased method name
* @param array $args arguments array
* @return mixed result of method call or unll + E_USER_NOTICE if not allowed
*/
public function callMethod(Dwoo_Core $dwoo, $obj, $method, $args)
{
foreach ($this->allowedMethods as $class => $methods) {
if (!isset($methods[$method])) {
continue;
}
if ($obj instanceof $class) {
return call_user_func_array(array($obj, $method), $args);
}
}
$dwoo->triggerError('The current security policy prevents you from calling '.get_class($obj).'::'.$method.'()');
return null;
}
/**
* this is used at compile time to check whether static method calls are allowed or not
*
* @param mixed $class lowercased class name or array('class', 'method') couple
* @param string $method lowercased method name
* @return bool
*/
public function isMethodAllowed($class, $method = null) {
if (is_array($class)) {
list($class, $method) = $class;
}
foreach ($this->allowedMethods as $allowedClass => $methods) {
if (!isset($methods[$method])) {
continue;
}
if ($class === $allowedClass || is_subclass_of($class, $allowedClass)) {
return true;
}
}
return false;
}
} }
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
require_once DWOO_DIRECTORY . 'Dwoo/Compiler.php'; require_once DWOO_DIRECTORY . 'Dwoo/Compiler.php';
function testphpfunc($input) { return $input.'OK'; }
class SecurityTests extends PHPUnit_Framework_TestCase class SecurityTests extends PHPUnit_Framework_TestCase
{ {
protected $compiler; protected $compiler;
...@@ -67,19 +65,78 @@ class SecurityTests extends PHPUnit_Framework_TestCase ...@@ -67,19 +65,78 @@ class SecurityTests extends PHPUnit_Framework_TestCase
$tpl->forceCompilation(); $tpl->forceCompilation();
$this->assertEquals("fooOK", $this->dwoo->get($tpl, array(), $this->compiler)); $this->assertEquals("fooOK", $this->dwoo->get($tpl, array(), $this->compiler));
$this->policy->disallowPhpFunction('testphpfunc');
} }
/** /**
* @expectedException Dwoo_Exception * @expectedException Dwoo_Security_Exception
*/ */
public function testNotAllowedPhpFunction() public function testNotAllowedPhpFunction()
{ {
$tpl = new Dwoo_Template_String('{strtotime("2000-01-01")}'); $tpl = new Dwoo_Template_String('{testphpfunc("foo")}');
$tpl->forceCompilation();
$this->dwoo->get($tpl, array(), $this->compiler);
}
public function testAllowMethod()
{
$this->policy->allowMethod('testSecurityClass','testOK');
$tpl = new Dwoo_Template_String('{$obj->testOK("foo")}');
$tpl->forceCompilation();
$this->assertEquals("fooOK", $this->dwoo->get($tpl, array('obj' => new testSecurityClass), $this->compiler));
$this->policy->disallowMethod('testSecurityClass','test');
}
/**
* @expectedException PHPUnit_Framework_Error
*/
public function testNotAllowedMethod()
{
$tpl = new Dwoo_Template_String('{$obj->testOK("foo")}');
$tpl->forceCompilation();
$this->dwoo->get($tpl, array('obj' => new testSecurityClass), $this->compiler);
}
public function testAllowStaticMethod()
{
$this->policy->allowMethod('testSecurityClass','testStatic');
$tpl = new Dwoo_Template_String('{testSecurityClass::testStatic("foo")}');
$tpl->forceCompilation();
$this->assertEquals("fooOK", $this->dwoo->get($tpl, array(), $this->compiler));
$this->policy->disallowMethod('testSecurityClass','testStatic');
}
/**
* @expectedException Dwoo_Security_Exception
*/
public function testNotAllowedStaticMethod()
{
$tpl = new Dwoo_Template_String('{testSecurityClass::testStatic("foo")}');
$tpl->forceCompilation(); $tpl->forceCompilation();
$this->dwoo->get($tpl, array(), $this->compiler); $this->dwoo->get($tpl, array(), $this->compiler);
} }
/**
* @expectedException Dwoo_Security_Exception
*/
public function testNotAllowedSubExecution()
{
$tpl = new Dwoo_Template_String('{$obj->test(preg_replace_callback("{.}", "mail", "f"))}');
$tpl->forceCompilation();
$this->dwoo->get($tpl, array('obj' => new testSecurityClass), $this->compiler);
}
public function testAllowDirectoryGetSet() public function testAllowDirectoryGetSet()
{ {
$old = $this->policy->getAllowedDirectories(); $old = $this->policy->getAllowedDirectories();
...@@ -104,3 +161,19 @@ class SecurityTests extends PHPUnit_Framework_TestCase ...@@ -104,3 +161,19 @@ class SecurityTests extends PHPUnit_Framework_TestCase
$this->assertEquals($old, $this->policy->getAllowedPhpFunctions()); $this->assertEquals($old, $this->policy->getAllowedPhpFunctions());
} }
} }
function testphpfunc($input) { return $input.'OK'; }
class testSecurityClass {
public static function testStatic($input) {
return $input.'OK';
}
public function testOK($input) {
return $input.'OK';
}
public function test($input) {
throw new Exception('can not call');
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment