Symfony 2.0.20 and Symfony 2.1.5 have just been released and they both contain two security fixes.
CVE-2012-6431: Routes behind a firewall are accessible even when not logged in
Affected versions
All versions from 2.0.0 to 2.0.19 are affected. Symfony 2.1.0 and later are not affected by this security issue as it was fixed in 55014a6.
Description
On the Symfony 2.0.x version, there's a security issue that allows access to routes protected by a firewall even when the user is not logged in.
Both the Routing component and the Security component uses the path returned
by getPathInfo()
to match a Request. The getPathInfo()
returns a
decoded path, but the Routing component
(Symfony
) decodes the path a second
time; whereas the Security component,
Symfony\Component\HttpFoundation\RequestMatcher
, does not.
This difference causes Symfony 2.0 to be vulnerable to double encoding attacks.
Exploits
When you create a route that matches /foo
and if you secure it in the
security.yml
configuration file, requesting /foo
from a browser will
redirect you to the login page if you are not already logged in. If you encode
the f
in the URL, /%66oo
will still be secured.
But if you double-encoded the f
, like in /%2566oo
, then /%2566oo
does not match anymore the Security rule but /%2566oo
still matches the
Routing route (as UrlMatcher
double-decodes the path). So, when that
happens, Symfony will render the /foo
path even if the path is secured.
Resolution
To avoid any other regressions in the code, we have decided to re-encode the path just before matching a request to prevent the double-decoding issue.
If you are using an affected version of Symfony (any 2.0 version), you must upgrade as soon as possible by applying the following patch: 8b2c17f. If possible, we also recommend you to upgrade to Symfony 2.0.20.
Credits
I would like to thank Manuele Menozzi for reporting this security issue which came with great explanations about the issue and how to reproduce it and Tobias Schultze for providing the patch.
CVE-2012-6432: Code execution vulnerability via the "internal" routes
Affected versions
All versions of Symfony2 (2.0.X, 2.1.X, and 2.2-dev) are affected by this issue.
Your application is vulnerable if the
@FrameworkBundle/Resources/config/routing/internal.xml
routing file is
mounted in your routing configuration and if you have not secured its routes
properly (read below).
You can check if you are using the internal routes easily by running the following command:
1 2
$ php ./app/console cache:clear
$ php ./app/console router:debug _internal
If you have an error that says The route "_internal" does not exist.
, you
are not vulnerable.
Description and Exploits
For handling ESIs (via the render
tag), Symfony uses a special route named
_internal
, defined in
@FrameworkBundle/Resources/config/routing/internal.xml
.
As of Symfony 2.1, the internal routing file defines an additional route,
_internal_public
, to be able to manage HIncludes (also via the render
tag).
As the _internal
route must only be used to route URLs between your PHP
application and a reverse proxy, it must be secured to avoid any access from a
browser. But the _internal_public
route must always be available from a
browser as it should be reachable by your frontend JavaScript (of course only
if you are using HIncludes in your application).
These two routes execute the same FrameworkBundle:Internal:index
controller which in turn executes the controller passed as an argument in the
URL. If these routes are reachable by a browser, an attacker could call them
to execute protected controllers or any other service (as a controller can
also be defined as a service).
Resolution
For Symfony 2.0 and 2.1, we made two changes:
- The
FrameworkBundle:Internal:index
controller now only executes services for which the class name ends withController
(it throws an exception otherwise). That limits drastically the number of classes that can be called by an attacker (if some of your controllers do not end withController
, keep reading to learn how to fix them). You must understand that this change do not freed you from doing what is described in the next section (see 1f8c501 for the actual changes in the Symfony code). - The
render
tag now allows the usage of absolute URLs as a first argument (see the section below to learn how it helps when solving the problem in your code).
For the upcoming Symfony 2.2 version, we have taken several drastic actions to remove this problem altogether:
- The
render
tag now only takes a URI as an argument and does not support controller names anymore (this means that all the fragments rendered for ESIs and HIncludes must have associated routes); - The
@FrameworkBundle/Resources/config/routing/internal.xml
andSymfony
files have been removed as we don't need to generate internal routes anymore;\Bundle \FrameworkBundle \Controller \InternalController
Have a look at the changes we made in 64d43c8 for a more detailed explanation of what we changed for 2.2.
For the Symfony Standard Edition, we have tweaked the comments to warn developers about this issue.
Protecting your Code
Upgrading to the latest version of Symfony won't fix the issue. You must act according to the actions described below.
Depending on what you are using in your application, you have several options to resolve this issue :
Do not use the internal routes: If you are using Symfony 2.0 or Symfony 2.1 without HIncludes, and if you are not using the
standalone
option for therender
tag, you can just remove the internal routes configuration from your routing files to protect yourself. You can check that you've done the right thing by executing the following commands (the second command must throw an error):1 2
$ php ./app/console cache:clear $ php ./app/console router:debug _internal
Restrict access to the internal routes: If you are using Symfony 2.0 or Symfony 2.1 without HIncludes, and if you are using the
standalone
option for therender
tag, you must restrict access to the internal routes via your reverse proxy. Here is an example for Varnish:1 2 3 4 5 6 7
import urlcode; vcl_recv { if (urlcode.decode(req.url) ~ "^/_internal") { error 403 "No Access"; } }
And another one for Nginx:
1 2 3
location ^~ /_internal { return 403; }
Updade your code: In all other cases, you must update your code by doing the following steps:
- Step 1: Upgrade to Symfony 2.0.20 or 2.1.5 (or the most current commit if you are using Symfony 2.2-dev) depending on which version of Symfony you are using (upgrading greatly mitigates the vulnerability and should be done as soon as possible -- the remaining steps removes the vulnerability for good);
- Step 2: Remove the internal routes configuration from your routing files;
Step 3: Check that the internal routes are not configured anymore by running the following commands (the second command must throw an error):
1 2
$ php ./app/console cache:clear $ php ./app/console router:debug _internal
Step 4: Find all calls to the
render
tag in your templates. As an example, here is the command you can use on a Unix-like system:1
$ find src/ -name "*.twig" -exec grep "{% render" {} \; -print
Step 5: Create a route for each controller used in a
render
tag. The public URL for the new route can be anything you like but here are some recommendations:- Add a special prefix/suffix to the pattern to better identify all routes
that point to controllers that should only be used to generate
"fragments" of content (
/fragment/XXX
or/XXX/YYY_fragment
); - If the controller for the sub-request renders a content that will be
included in a page that is secured, apply the same security
configuration (for instance, if the main page is secured by a Symfony
firewall because its path starts with
/secure
, use the same/secure
prefix for the sub-request controller route path).
- Add a special prefix/suffix to the pattern to better identify all routes
that point to controllers that should only be used to generate
"fragments" of content (
Step 6: Replace each
render
call as follows:Before:
1 2 3 4
{% render "SomeBundle:Controller:action" %} {# render call with some arguments #} {% render "SomeBundle:Controller:action" with { 'param': 1 }, { 'standalone': true } %}
After:
1 2 3 4 5
{# the route should have been created at step 5 #} {% render url("path_to_controller_router") %} {# the with argument is needed but ignored #} {% render url("path_to_controller_router", { 'param': 1 }) with {}, { 'standalone': true } %}
- Step 7: Check that your application still works fine and deploy.
Credits
I would like to thank Victor Berchet for reporting this security issue.
Just upgraded, and got this exception:
[Symfony\Component\DependencyInjection\Exception\InvalidArgumentException] The parameter "kernel.trusted_proxies" must be defined.
after cache:clear --env=prod --no-debug
(clearing cache without --no-debug is fine)
Fabien, the first setence is not really correct because only Symfony 2.1 contains two security fixes (2.0 only one).
Ops, the other way round: Only Symfony 2.0 contains two security fixes (2.1 only one).
Massimiliano,
I noticed this a few days ago. If you go into your config.yml and add "trusted_proxies: ~" under the "framework:" section, it should fix it.
However, you might want to investigate more before blindly including that setting. Pretty sure it enables "trust all proxies", which could result in users tampering with their reported IP address.
Deps Files for Symfony 2.0.20:
https://raw.github.com/symfony/symfony-standard/v2.0.20/deps https://raw.github.com/symfony/symfony-standard/v2.0.20/deps.lock
(I always find it annoying that if I try to "wget" these, I have to use the "--no-check-certificate" option. I blame Github. ;-) )
The above instructions (removing the _internal route from routing files and modifying use of the render tag in Twig templates) seems to completely break if you are using 2.1.5 with ESI switched on, because the call to getInternalUri() is still made within the HttpKernel object, which still references the _internal route.
@Douglas yes! I am having the same problem and just traced it back to that as well.
Unfortunately, as Kevin and Douglas mentioned, if you are using ESI and want to disable the _internal route, it won't work as is. This has been fixed now in all Symfony versions. I will probably release a new version of Symfony 2.0 and 2.1 tomorrow. In the meantime, keeping the _internal routes and protecting them via Nginx or Varnish work.
I'm gonna submit a PR to the Symfony documentation repository if it hasn't already been done yet.
Apache users can either use this rewrite rule RewriteRule "^_internal" - [F,L]
Or add this to there server configuration (does not work with .httaccess)
<Location "/_internal"> deny from all </Location>
Cheers for including the CVE numbers now!
Hello, I have updated to version 2.1.5 and 2.1.6 and the last two I have stopped working properly the method 'bind' forms. If I go back to version 2.1.4 again bindear data correctly. Have you made any changes in this regard?
A link to this entry is not in http://symfony.com/doc/master/contributing/code/security.html#security-advisories yet.
@Kousuke Fabien has made a pull request to the security document with the needed details and I've just merged it in - it should render on the site shortly at http://symfony.com/doc/current/contributing/code/security.html#security-advisories.
Hugo has also done a PR to the documentation so we can update any language needed - see https://github.com/symfony/symfony-docs/pull/2057 - I'll work on that, merge it shortly, and we'll make any other changes needed going forward.
Thanks!