Fuzzing and Bypassing the AWS WAF

The Sysdig Threat Research Team discovered techniques that allowed the AWS WAF to be bypassed using a specialized DOM event. Web Application Firewalls (WAFs) serve as the first line of defense for your web applications, acting as a filter between your application and incoming web traffic to protect against unauthorized or malicious activity.

In this blog post, we will analyze one of the most commonly used Web Application Firewalls, the AWS WAF, and explain ways that allowed it to be bypassed.

Timeline

10-25-2023: Vulnerability reported to AWS security team

10-25-2023: Acknowledgement from the AWS team

12-13-2023: Fix deployed

01-09-2024: Information disclosed and blog post published

AWS WAF 101: The ABC of not getting hacked

Setting up the WAF on the AWS Console is straightforward; in our tests, we built a test environment composed of an EC2 instance that hosts the actual web application, a load balancer with built-in support for AWS WAF, and a set of predefined rules, specifically:

  • AWS-AWSManagedRulesCommonRuleSet
  • AWS-AWSManagedRulesKnownBadInputsRuleSet
  • AWS-AWSManagedRulesSQLiRuleSet

As the names suggest, these rule sets are responsible for detecting and blocking a wide range of attacks; the common rule set takes care of XSS attacks, while the identification of bad inputs helps prevent the exploitation of established vulnerabilities, such as Command Execution in a web application, by filtering recognized paths and strings (such as /bin/bash, /etc/passwd, etc.). Finally, the SQLi rule set is responsible for blocking SQL Injection (another bypass was recently found there, check it out).

While AWS offers managed rules for plug-and-play security measures, it’s important to remember that these rules are not a silver bullet. Usually, a fine-graded tuning is needed to make them strong enough to prevent attacks that abuse specific encoding mechanisms.

Deep dive into WAF: Manual tests, tags, and events

Our approach was initially based on identifying all the inputs that led to WAF blocking our requests. This involved conducting a series of manual tests using various techniques, incorporating a mix of different tags and attributes. This initial exploration allowed us to gain insights into the specific behavior and limitations of the WAF, setting the stage for more advanced testing.

Thankfully, AWS WAF provides us with metrics that help us understand what exactly was matched and which rules fired an event.

Sadly, due to the vast amount of tags and events, it turned out to be almost impossible to test every single permutation and combination of payloads to find edge cases not covered by the WAF. As a result, we decided to change our methodology and start building our own WAF fuzzer (Wafer).

Automating the chaos: Fuzzing WAF with Selenium

Our fuzzer was heavily based on the beautiful PortSwigger XSS reference, which contains primarily up-to-date payloads known to work on the most recent browsers.

This tool was designed to automate finding unfiltered tags and attributes, and combine them to form working payloads. The tool initially iterates by tags and attributes, and after realizing which words cannot be present, it generates permutations using the keywords that the firewall does not block in search of working payloads.

Having generated a payload, however, we must ensure it is executed within the browser and triggered. To do this, we used Selenium (an open-source framework for automating web browsers) both to verify that the alert was called, and to simulate the interaction of a user clicking on elements such as an input field or a button.

As said before, we had to overcome a few challenges during our development, such as finding a way to catch alerts and check whether it was possible to trigger the XSS from user interaction.

To overcome those issues, we created a simple hook to catch every call to alertconfirm, and prompt like the following:

window.alert_trigger = false;

window.alert = function() {

    window.alert_trigger = true;

};

window.confirm = window.alert;

window.prompt = window.alert;

Code language: JavaScript (javascript)

The second issue (triggering user interaction on every element in the page) was solved by injecting another JS script, which is responsible for firing common events like blurfocusclickdrag & drop, and so on.

var ids = [IDs of the created elements]

for (var i = 0; i < ids.length; i++) {

    var element = document.getElementById(ids[i]);

    if(!element) continue;

    // Trigger all possible events click, mouseover, etc.

    var events = ['click', 'mouseover', 'mousedown', 'mouseup', 'mousemove', 'mouseout', 'mouseenter', 'mouseleave', 'dblclick', 'contextmenu', 'wheel', 'select', 'pointerdown', 'pointerup', 'pointermove', 'pointerover', 'pointerout', 'pointerenter', 'pointerleave', 'gotpointercapture', 'lostpointercapture'];

  try {

      for (var j = 0; j < events.length; j++) {{

              var event = new MouseEvent(events[j], {{bubbles: true}});

              element.dispatchEvent(event);

      }}

      element.focus();

      element.blur();

      element.dispatchEvent(new KeyboardEvent('keydown', {{ctrlKey: true, altKey: true, key: 'x'}}));

  } catch (e) {}

}Code language: JavaScript (javascript)

Apart from that, our fuzzer implements different strategies to try to bypass regex-based rules, like splitting tags using spaces and newlines, or adding random Unicode characters which are later converted to their ASCII representation. We have released the fuzzer, which can be found here.

With the above knowledge, we started the fuzzer and forgot about it.

Mission Possible: The AWS WAF bypass PoC

When we came back to check the results after a few days, we found a message from our fuzzer stating that he could identify a payload that could potentially trigger JS execution.

After briefly reviewing it, we found out it used the onbeforetoggle event, which, as the fuzzer found, wasn’t caught by the WAF!.

This payload managed to successfully evade WAF’s defenses while also executing an alert box, thus proving our proof of concept. It relies on the experimental onbeforetoggle event to link a standard HTML button to a popover element, chaining their functionalities to execute arbitrary JavaScript code. For example, the following payload would bypass the existing WAF protections.

<strong><button popovertarget=x>click me</button><test onbeforetoggle=alert(document.domain) popover id=x>aaa</aaa></strong>Code language: JavaScript (javascript)

If exploited in a real-world scenario, this vulnerability could allow attackers to inject malicious scripts into a vulnerable application, potentially compromising sensitive user data and system integrity.

In dissecting AWS WAF’s capabilities and limitations, we’ve demonstrated that while AWS WAF serves as an excellent first line of defense, our experiments showed that no security solution can be considered foolproof. Organizations should never solely rely on out-of-the-box solutions and must constantly adapt and test their security controls to stay ahead of evolving threats.

Daniele Linguaglossa