HTTP requests typically originate with a client, and end
at a web server that processes the request and returns some response.
Such requests may pass through multiple proxies before they arrive at
the requested resource. If one of these proxies is configured badly (for
instance, back to a proxy that had already processed it) then the
request may be caught in a loop.
Request loops, accidental or malicious, can consume resources and degrade user's Internet performance. Such loops can even be observed at the CDN-level. Such a wide-scale attack would affect all customers of that CDN. It's been over three years
since Cloudflare acknowledged the power of such non-compliant or
malicious request loops. The proposed solution in that blog post was
quickly found to be flawed and loop protection has since been
implemented in an ad-hoc manner that is specific to each
individual provider. This lack of cohesion and co-operation has led to a
fragmented set of protection mechanisms.
We are finally happy to
report that a recent collaboration between multiple CDN providers
(including Cloudflare) has led to a new mechanism for loop protection.
This now runs at the Cloudflare edge and is compliant with other CDNs,
allowing us to provide protection against loops. The loop protection
mechanism is currently a draft item being worked on by the HTTPbis
working group. It will be published as an RFC in the standards track in
the near future.
The original problem
The original problem was summarised really nicely in the previous blog post, but I will summarise it again here (with some diagrams that are suspiciously similar to the original post, sorry Nick!).
As
you may well know, Cloudflare is a reverse proxy. When requests are
made for Cloudflare websites, the Cloudflare edge returns origin content
via a cached response or by making requests to the origin web server.
Some Cloudflare customers choose to use different CDN providers for
different facets of functionality. This means that requests go through
multiple proxy services before origin content is received from the
origin.
This is where things can sometimes get messy, either
through misconfiguration or deliberately. It's possible to configure
multiple proxy services for a given origin in a loop. For example, an origin website could configure proxy A so that proxy B is the origin, and B such that A is the origin. Then
any request sent to the origin would get caught in a loop between the
two Proxies (see above). If such a loop goes undetected, then this can
quickly eat the computing resources of the two proxies, especially if
the request requires a lot of processing at the edge. In these cases, it
is conceivable that such an attack could lead to a DoS on one or both
of the proxy services. Indeed, a research paper from NDSS 2016
showed that such an attack was practical when leveraging multiple CDN
providers (including Cloudflare) in the way method shown above.
The original solution
The
previous blog post advocated using the Via header on HTTP requests to
log the proxy services that had previously processed any previous
request. This header is specified in RFC7230
and is purpose-built for providing request loop detection. The idea was
that CDN providers would log each time a request came through their
edge architecture in the Via header. Any request that arrived at the
edge would be checked to see if it previously passed through the same
CDN using the value of the Via header. If the header indicated that it
had passed through before, then the request could be dropped before any
serious processing had taken place.
Nick’s previous post finished with a call-to-arms for all services proxying requests to be compliant with the standard.
The problem with Via
In
theory, the Via header would solve the loop protection problem. In
practice, it was quickly discovered there were issues with the
implementation of Via that meant that using the header was infeasible.
Adding the header to outbound requests from the Cloudflare edge had
grave performance consequences for a large number of Cloudflare
customers.
Such issues arose from legacy usage of the Via header
that conflicts with using it for tracking loop detection. For instance,
around 8% of Cloudflare enterprise customers experienced issues where
gzip failed to compress requests containing the Via header. This meant
that transported requests were much larger and led to wide-scale
problems for their web servers. Such performance degradation is even expected in some server implementations. For example, NGINX actively chooses not to compress proxied requests:
By
default, NGINX does not compress responses to proxied requests
(requests that come from the proxy server). The fact that a request
comes from a proxy server is determined by the presence of the Via
header field in the request.
While Cloudflare takes
security very seriously, such performance issues were unacceptable. The
difficult decision was taken to switch off loop protection based on the
contents of the Via header shortly after it was implemented.
Since
then, Cloudflare has implemented loop protection based on the
CF-Connecting-IP and X-Forwarded-For headers. In essence, when a request
is processed by the edge these headers are added to the request before
it is sent to the origin. Then, any request that is processed by the
edge including either of these headers is dropped. While this is enough
to avoid malicious loop attacks, there are some disadvantages with this
approach.
Firstly, this approach naturally means that there is no
unified way of approaching loop protection across the different CDN
providers. Without a standardised method, the possibility of mistakes in
implementations that could cause problems in the future rises.
Secondly,
there are some valid reasons that Cloudflare customers may require
requests to loop through the edge more than once.While such reasons are
usually quite esoteric, customers with such a need had to manually
modify such requests so that they did not fall foul of the loop
protection mechanism. For example, workflows that include usage of
Cloudflare Workers can send requests through the edge more than once via
subrequests for returning custom content to clients. The headers that
are currently used mean that requests are dropped as soon as a request
loops once. This can add noticeable friction to using CDN services and
it would be preferable to have a more granular solution to loop
detection.
A new solution
Collaborators at Cloudflare, Fastly and Akamai set about defining a unified solution to the loop protection problem for CDNs.
The output was the following was this draft
that has recently been accepted by the HTTPbis working group on the
Standards Track. the document has been approved by the IESG, it will
join the RFC series.
The CDN-Loop header sets out a syntax that
allows individual CDNs to mark requests as having been processed by
their edge. This header should be added to any request that passes
through the CDN architecture towards some separate endpoint. The current draft defines the syntax of the header to be the following:
This initially seems a lot to unpack. Essentially, cdn-id is a URI host ID for the destination resource, or a pseudonym related to the CDN that has processed the request. In the Cloudflare case, we might choose pseudonym = cloudflare, or use the URI host ID for the origin website that has been requested.
Then, cdn-info contains the cdn-id in addition to some optional parameters. This is denoted by *( OWS ";" OWS parameter ) where `OWS` represents optional whitespace, and parameter represents any CDN-specific information that may be informative for the specific request. If different CDN-specific cdn-info parameters are included in the same header, then these are comma-separated. For example, we may have cdn-info = cdn1; param1, cdn2; param2 for two different CDNs that have interacted with the request.
Concretely, we give some examples to describe how the CDN-Loop header may be used by a CDN to mark requests as being processed.
If a request arrives at CDN A that has no current CDN-Loop header. Then A processes the request and adds:
CDN-Loop: cdn-info(A)
to the request headers.
If a request arrives at A with the following header:
CDN-Loop: cdn-info(B)
for some different CDN B, then A either modifies the header to be:
CDN-Loop: cdn-info(B), cdn-info(A)
or adds a separate header:
CDN-Loop: cdn-info(B)
CDN-Loop: cdn-info(A)
If a request arrives at A with:
CDN-Loop: cdn-info(A)
this indicates that the request has already been processed. At this point A
detects a loop and may implement loop protection in accordance with its
own policies. This is an implementation decision that is not defined in
the specification. Options may include dropping the request or simply
re-marking it, for example:
CDN-Loop: cdn-info(A); cdn-info(A)
A CDN could also utilise the optional parameters to indicate that a request had been processed:
CDN-Loop: cdn-info(A); processed=1
The ability to use different parameters in the header allows
for much more granular loop detection and protection. For example, a CDN
could drop requests that had previously looped N>1 times, rather
than just once. In addition, the advantage of using the CDN-Loop header
is that it does not come with legacy baggage. As we experienced
previously, loop detection based on the Via header can conflict with
existing usage of the header in web server implementations that
eventually lead to compression issues and performance degradation. This
makes CDN-Loop a viable and effective solution for detecting
loop-protection attacks and applying preventions where needed.
Implementing CDN-Loop at Cloudflare
The
IETF standardisation process welcomes running code and implementation
experience in the real world. Cloudflare recently added support for the
CDN-Loop header to requests that pass through the Cloudflare edge. This
replaces the CF-Connecting-IP and X-Forwarded-For
headers as the primary means for establishing loop protection. The
structure that Cloudflare uses is similar to the examples above, where cdn-info = cloudflare. Extra parameters can be added to the header to determine how many times a request has been processed and in what manner. The
Cloudflare edge drops any requests that have been processed multiple
times to prevent malicious loop attacks. In the diagram above, requests
that have looped more times than is allowed by a given CDN (red arrows)
are dropped and an error is returned to the client. The edge can decide
to allow requests to loop more than once in certain situations, rather
than dropping immediately after the first loop.
A (second) call-to-arms
Cloudflare
previously made a call-to-arms to make use of the Via header across the
industry for preventing malicious usage of proxies for request looping.
This did not turn out as we hoped for the reasons mentioned above.
Using CDN-Loop, we believe that there is finally a way of allowing CDNs
to block loop attacks in a standardised and generic manner that fits
with other existing implementations.
CDN-Loop is actively
supported by Cloudflare and there have been none of the performance
issues that came with the usage of Via. Recently, another CDN, Fastly introduced usage of the CDN-Loop header
of the CDN-Loop header for their own edge-based loop protection. We
believe that this could be the start of a wider movement and that it
would be advantageous for all reverse proxies and CDN-like providers to
implement compliant usage of the CDN-Loop header.
While the
original solution three years ago was very different, what Nick said at
the time is still salient for all CDNs globally: Let’s work together to avoid request loops. Special
thanks to Stephen Ludin, Mark Nottingham and Nick Sullivan for their
work in drafting and improving the CDN-Loop specification. We would also
like to extend thanks to HTTPbis working group for their advice during
the standardisation process.
Preventing Request Loops Using CDN-Loop
Reviewed by HGO NET
on
08.32
Rating: 5
Post Comment
Tidak ada komentar