Skip to content

[RFC] Prevent reverse proxy cache flooding #36320

Closed
@Toflar

Description

@Toflar

We currently do not have any built-in means to protect Symfony apps from flooding gateway caches such as Varnish and the like. Basically, given I have configured a route for /about-us and I send caching headers such as Cache-Control: public, max-age=86400, one can easily flood any cache by just adding random query parameters to the URL. So a simple script that calls /about-us?<random-key>=<random-value> floods the cache with nonsense entries.

The only way to prevent that is to deny such requests by sending e.g. a 400 Bad Request (well, any uncacheable status code but 400 would be appropriate here imho).
Now, of course you can do that manually today by having e.g. a kernel.request listener that checks for valid query parameters. You could even configure it on proxy level.
But those "solutions" are all cumbersome for many reasons:

  • Configuring it at proxy level requires you to specify a whitelist of allowed query parameters. Either a global one or even different parameters per URL/route. As soon as you deploy a new version of your application, you might also need to add new parameters to your whitelist. It's cumbersome and error-prone. Moreover, once you switch to a different cache proxy, you have to start all over again.

  • Building your own solution on app level using e.g. the kernel.request event is still cumbersome and error-prone because again, you have to manage a whitelist and there's no standard way of doing things which means that in application A one might have implemented it with a global whitelist and in application B someone implemented it as a route attribute. It's a mess.

I was thinking about this issue and I was wondering if we can come up with a general solution. A solution that protects all Symfony applications by default and in best case, doesn't even require any developer to think about a whitelist at all.

So here's an idea I came up with and I would like to know if that's something people would love to see being integrated into Symfony or if there's even better ideas:

Symfony already tracks certain things during a typical request flow. The best example is security/session access. Symfony automatically turns any responses into private responses if any of the code has accessed the current user and thus prevents responses to be cached and potentially leak private information to other visitors.
We could do something similar with query parameters. We could extend our ParameterBag to track calls to the get() and has() methods and introduce a method which would return all the query parameters that haven't been accessed (or the other way around - implementation detail).
Then we use a kernel.response listener with a fairly low priority that checks if there were any query parameters on the master request that were never accessed during this request in which case it would send a 400 Bad Request.
That means, you don't have to configure anything at all. It all works out of the box and you will not even notice this logic if you behave normally and do not send random query parameters 😎

Things to consider:

  • It may require changes in bundles. If a bundle uses $request->query->all() instead of specific calls, you may suddenly get 400 responses. Thus, for BC reasons, this listener should be disabled by default in Symfony 5. It may be enabled by default in Symfony 6.

  • We may think about having a special QueryParameterBag that does not provide an all() method because with this concept it never makes sense to access all parameters that were submitted. We may introduce a multiple(array $keys) though for DX.

  • The response listener doesn't need to do anything, if there's no caching information present on the response.

What do you think?

/cc'ing people that I know worked a lot with caching in Symfony in e.g FOSHttpCache or API Platform: @dbu @andrerom @dunglas @alexander-schranz

Metadata

Metadata

Assignees

No one assigned

    Labels

    RFCRFC = Request For Comments (proposals about features that you want to be discussed)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions