Archives


Master Symfony2 fundamentals

Be trained by SensioLabs experts (2 to 6 day sessions -- French or English).
trainings.sensiolabs.com

Discover the SensioLabs Support

Access to the SensioLabs Competency Center for an exclusive and tailor-made support on Symfony
sensiolabs.com

Fabien Potencier
Security release: Symfony 2.0.20 and 2.1.5 released
by Fabien Potencier – December 20, 2012 – 14 comments

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 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 with Controller (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 with Controller, 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 and Symfony\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. 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 the render 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 the render 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).
    • 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.

Comments RSS

  • Massimiliano Arione
    #1 Massimiliano Arione said on the 2012/12/20 at 18:02
    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)
  • Tobias Schultze
    #2 Tobias Schultze said on the 2012/12/20 at 18:06
    Fabien, the first setence is not really correct because only Symfony 2.1 contains *two* security fixes (2.0 only one).
  • Tobias Schultze
    #3 Tobias Schultze said on the 2012/12/20 at 18:07
    Ops, the other way round: Only Symfony 2.0 contains *two* security fixes (2.1 only one).
  • Kevin Boyd
    #4 Kevin Boyd said on the 2012/12/20 at 18:19
    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.
  • Kevin Boyd
    #5 Kevin Boyd said on the 2012/12/20 at 18:46
    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. ;-) )
  • Douglas Greenshields
    #6 Douglas Greenshields said on the 2012/12/20 at 22:21
    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.
  • Kevin Bond
    #7 Kevin Bond said on the 2012/12/20 at 22:58
    @Douglas yes! I am having the same problem and just traced it back to that as well.
  • Fabien Potencier
    #8 Fabien Potencier said on the 2012/12/20 at 23:22
    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.
  • Hugo Hamon
    #9 Hugo Hamon said on the 2012/12/21 at 10:05
    I'm gonna submit a PR to the Symfony documentation repository if it hasn't already been done yet.
  • Sebastiaan Stok
    #10 Sebastiaan Stok said on the 2012/12/21 at 11:22
    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>
  • Christof Damian
    #11 Christof Damian said on the 2012/12/21 at 20:53
    Cheers for including the CVE numbers now!
  • David López Rguez
    #12 David López Rguez said on the 2012/12/22 at 20:30
    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?
  • Kousuke Ebihara
    #13 Kousuke Ebihara said on the 2012/12/23 at 04:13
    A link to this entry is not in http://symfony.com/doc/master/contributing/code/security.html#security-advisories yet.
  • Ryan Weaver
    #14 Ryan Weaver said on the 2012/12/23 at 17:56
    @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!