Release 2.46 (#5757)
This commit is contained in:
@@ -16,10 +16,6 @@ final class ChainPolicy implements SecurityPolicyInterface
|
||||
/** @var array<SecurityPolicyInterface> */
|
||||
private array $policies = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function addPolicy(SecurityPolicyInterface $policy): void
|
||||
{
|
||||
$this->policies[] = $policy;
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
|
||||
namespace App\Twig\SecurityPolicy;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Pdf\PdfContext;
|
||||
use Symfony\Bridge\Twig\AppVariable;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\ServerBag;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Twig\Sandbox\SecurityNotAllowedMethodError;
|
||||
use Twig\Sandbox\SecurityPolicyInterface;
|
||||
|
||||
/**
|
||||
@@ -22,6 +29,49 @@ final class DefaultPolicy implements SecurityPolicyInterface
|
||||
|
||||
public function checkMethodAllowed($obj, $method): void
|
||||
{
|
||||
if ($obj instanceof ServerBag) {
|
||||
throw new SecurityNotAllowedMethodError('Tried to access server environment', ServerBag::class, $method);
|
||||
}
|
||||
|
||||
if ($obj instanceof SessionInterface) {
|
||||
throw new SecurityNotAllowedMethodError('Tried to access session', SessionInterface::class, $method);
|
||||
}
|
||||
|
||||
$lcm = strtolower($method);
|
||||
|
||||
if ($obj instanceof PdfContext) {
|
||||
if ($lcm !== 'setoption') {
|
||||
throw new SecurityNotAllowedMethodError('Tried to access forbidden method on PdfContext', PdfContext::class, $method);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!str_starts_with($lcm, 'has') && !str_starts_with($lcm, 'is') && !str_starts_with($lcm, 'get') && $lcm !== '__tostring') {
|
||||
throw new SecurityNotAllowedMethodError('Tried to access non-read method', $obj::class, $method);
|
||||
}
|
||||
|
||||
if ($obj instanceof Request) {
|
||||
if (!str_starts_with($lcm, 'get')) {
|
||||
throw new SecurityNotAllowedMethodError('Tried to call setter() of app variable', AppVariable::class, $method);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($obj instanceof AppVariable) {
|
||||
if (!\in_array($lcm, ['getrequest', 'getuser', 'getlocale'], true)) {
|
||||
throw new SecurityNotAllowedMethodError('Tried to access forbidden app variable method', User::class, $method);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($obj instanceof User) {
|
||||
if (\in_array($lcm, ['getpassword', 'gettotpsecret', 'getplainpassword', 'getconfirmationtoken', 'gettotpauthenticationconfiguration'], true)) {
|
||||
throw new SecurityNotAllowedMethodError('Tried to access user secrets', User::class, $method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function checkPropertyAllowed($obj, $property): void
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Twig\SecurityPolicy;
|
||||
|
||||
use Twig\Markup;
|
||||
use Twig\Sandbox\SecurityNotAllowedFilterError;
|
||||
use Twig\Sandbox\SecurityNotAllowedFunctionError;
|
||||
use Twig\Sandbox\SecurityNotAllowedMethodError;
|
||||
use Twig\Sandbox\SecurityNotAllowedPropertyError;
|
||||
use Twig\Sandbox\SecurityNotAllowedTagError;
|
||||
use Twig\Sandbox\SecurityPolicyInterface;
|
||||
use Twig\Template;
|
||||
|
||||
/**
|
||||
* A blocking approach for Twig templates.
|
||||
*/
|
||||
final class ForbiddenPolicy implements SecurityPolicyInterface
|
||||
{
|
||||
/** @var array<string, array<string>> */
|
||||
private array $forbiddenMethods = [];
|
||||
|
||||
/**
|
||||
* @param array<string> $forbiddenTags
|
||||
* @param array<string> $forbiddenFilters
|
||||
* @param array<string, array<string>> $forbiddenMethods
|
||||
* @param array<string, array<string>> $forbiddenProperties
|
||||
* @param array<string> $forbiddenFunctions
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $forbiddenTags = [],
|
||||
private readonly array $forbiddenFilters = [],
|
||||
array $forbiddenMethods = [],
|
||||
private readonly array $forbiddenProperties = [],
|
||||
private readonly array $forbiddenFunctions = []
|
||||
)
|
||||
{
|
||||
$this->forbiddenMethods = [];
|
||||
foreach ($forbiddenMethods as $class => $m) {
|
||||
$this->forbiddenMethods[$class] = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, \is_array($m) ? $m : [$m]);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkSecurity($tags, $filters, $functions): void
|
||||
{
|
||||
foreach ($tags as $tag) {
|
||||
if (\in_array($tag, $this->forbiddenTags)) {
|
||||
throw new SecurityNotAllowedTagError(\sprintf('Tag "%s" is not allowed.', $tag), $tag);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($filters as $filter) {
|
||||
if (\in_array($filter, $this->forbiddenFilters)) {
|
||||
throw new SecurityNotAllowedFilterError(\sprintf('Filter "%s" is not allowed.', $filter), $filter);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($functions as $function) {
|
||||
if (\in_array($function, $this->forbiddenFunctions)) {
|
||||
throw new SecurityNotAllowedFunctionError(\sprintf('Function "%s" is not allowed.', $function), $function);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function checkMethodAllowed($obj, $method): void
|
||||
{
|
||||
if ($obj instanceof Template || $obj instanceof Markup) { // @phpstan-ignore instanceof.internalClass
|
||||
return;
|
||||
}
|
||||
|
||||
$forbidden = false;
|
||||
$method = strtr($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
|
||||
foreach ($this->forbiddenMethods as $class => $methods) {
|
||||
if ($obj instanceof $class) {
|
||||
$forbidden = \in_array($method, $methods);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($forbidden) {
|
||||
$class = \get_class($obj);
|
||||
throw new SecurityNotAllowedMethodError(\sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkPropertyAllowed($obj, $property): void
|
||||
{
|
||||
$forbidden = false;
|
||||
foreach ($this->forbiddenProperties as $class => $properties) {
|
||||
if ($obj instanceof $class) {
|
||||
$forbidden = \in_array($property, \is_array($properties) ? $properties : [$properties]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($forbidden) {
|
||||
$class = \get_class($obj);
|
||||
throw new SecurityNotAllowedPropertyError(\sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,13 @@ use Twig\Template;
|
||||
*/
|
||||
final class InvoicePolicy implements SecurityPolicyInterface
|
||||
{
|
||||
private ChainPolicy $policy;
|
||||
private SecurityPolicyInterface $default;
|
||||
private SecurityPolicyInterface $security;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->policy = new ChainPolicy();
|
||||
$this->policy->addPolicy(new DefaultPolicy());
|
||||
$this->policy->addPolicy(new SecurityPolicy(
|
||||
$this->default = new DefaultPolicy();
|
||||
$this->security = new SecurityPolicy(
|
||||
['block', 'if', 'for', 'set', 'extends', 'import'],
|
||||
[
|
||||
// =================================================================
|
||||
@@ -194,12 +194,13 @@ final class InvoicePolicy implements SecurityPolicyInterface
|
||||
'month_names',
|
||||
'locale_format',
|
||||
]
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
public function checkSecurity($tags, $filters, $functions): void
|
||||
{
|
||||
$this->policy->checkSecurity($tags, $filters, $functions);
|
||||
$this->default->checkSecurity($tags, $filters, $functions);
|
||||
$this->security->checkSecurity($tags, $filters, $functions);
|
||||
}
|
||||
|
||||
public function checkMethodAllowed($obj, $method): void
|
||||
@@ -208,21 +209,20 @@ final class InvoicePolicy implements SecurityPolicyInterface
|
||||
return;
|
||||
}
|
||||
|
||||
$this->default->checkMethodAllowed($obj, $method);
|
||||
|
||||
$lm = strtolower($method);
|
||||
|
||||
if (str_starts_with($lm, 'get') || str_starts_with($lm, 'is') || str_starts_with($lm, 'has')) {
|
||||
if (str_starts_with($lm, 'get') || str_starts_with($lm, 'is') || str_starts_with($lm, 'has') || $lm === '__tostring') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($lm === '__tostring') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->policy->checkMethodAllowed($obj, $method);
|
||||
$this->security->checkMethodAllowed($obj, $method);
|
||||
}
|
||||
|
||||
public function checkPropertyAllowed($obj, $property): void
|
||||
{
|
||||
$this->policy->checkPropertyAllowed($obj, $property);
|
||||
$this->default->checkPropertyAllowed($obj, $property);
|
||||
$this->security->checkPropertyAllowed($obj, $property);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user