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\Component\Routing\Matcher\UrlMatcher
) 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 thesecurity.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 andTobias 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 (see1f8c501 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\Bundle\FrameworkBundle\Controller\InternalController
files have been removed as we don't need to generate internal routes anymore;
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. Youmust 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 #}{%renderurl("path_to_controller_router")%}{# the with argument is needed but ignored #}{%renderurl("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.