“Defense in depth” is good security practice but can understandably be the first thing out the window when deadlines get tight. We’ve seen organizations large and small put in place one solid line of defense and then move on to put out the next fire. As attackers, this puts a heavy bounty on bypassing or compromising any single layer defense.

In this post, I’m going to be discussing a filter bypass technique that we refer to as request mutation. By utilizing this technique, attackers can potentially bypass security controls and filters such as WAFs, regular expressions, and sanitization.

Theory

“Request mutation” is when we submit a valid, safe request that will be modified on the server to become unsafe. When this happens after input validation is performed, it leads to a validation bypass.

As a simple example, imagine a web server that is set up behind an imaginary WAF. The WAF is in place to prevent any malicious requests from reaching the application server, such as those containing a cross-site scripting (XSS) payload by looking for the <script> tag. If the <script> tag is detected, the request will be marked as malicious and blocked. The web server performs a simple modification on the input, such as HTML decoding, and echoes the response back to the user.

In this example, request mutation can be used to bypass the WAF filtering. While sending <script> will get your request blocked, sending %lt;script%gt; will not. At the time this request is read by the WAF, it will genuinely be safe. However, the web server will perform HTML decoding before echoing the contents. Because &lt; is decoded to < and &gt; is decoded to >, the response from the server will be <script>. Suddenly, the safe request read by the WAF has become an unsafe request, and there was nothing the WAF could have done to prevent this without knowing the specific web application’s behavior. In the case of third-party WAFs, this would be infeasible.

While this example may be a bit contrived and over-simplified (why would a web server be HTML decoding and echoing user responses in the first place?), it effectively illustrates the concept. It is shockingly similar to cases where this behavior can be found in real servers, as you can see below.

Real Example

As a consultant, I first came across this technique when testing an ASP.NET application. The application utilized ASP.NET Request Validation to ensure incoming requests were safe before storing the contents within a SQL database handled by Microsoft SQL Server. This data was then retrieved and returned to the user in future responses.

Because of the request validation in place, we needed to discover what triggers the filter to reject a request. Sending an obvious payload such as <script>alert()</script> caused an error message like the one below (taken from the Microsoft documentation).

Through some trial and error, it’s simple to determine that the presence of the < and/or > characters would cause the request to be blocked. This is where request mutation comes in. If we can find a way to get safe characters to be converted into < and > on the server, we would be able to bypass the WAF and have a working exploit. However, the server did not perform any modifications to the data. It simply inserted it into a database and retrieved it later.

Thankfully for us as an attacker, Microsoft SQL Server actually can perform modifications on data for you. Specifically, it takes Unicode characters and converts them into their best-fit ASCII alternative. To test this, I created a new SQL database on a fresh installation of Microsoft SQL Server and created a database called BlogComments that simply has one column. This column is called Comment and has the type nvarchar(50). After running the command INSERT INTO BlogComments VALUES ('ŤĘŞŤ');, you can see the best-fit coercion below.

As you can see, the text ŤĘŞŤ was coerced into the ASCII text TEST. As it turns out, there are also characters that get coerced into < and >. Specifically, these are the full-width less than and greater than signs (U+FF1C and U+FF1E). After submitting another test comment of <script>alert()</script> (note the full-width characters), we can see that they do in fact get coerced as expected.

We now have all the requirement for a successful request mutation attack. We have a filter being applied early on in the request’s handling, and we have mutation being done on the request after it is being validated. By utilizing this technique during a test, we were able to successfully bypass the ASP.NET request validation and perform a stored XSS attack.

All technology is fallible and contains bugs, and as attackers we get to be creative about how we use those bugs to cause unexpected outcomes. In this case, a WAF and a webserver both behaved logically, but we were able to exploit the behavior of both in combination to cause a security impacting condition. Defense in depth can help mitigate the impact of a single bypass, making it less likely that one vulnerability allows an attacker to accomplish their goals.

Reach out if you’d like help with security architecture or software review - we love looking at systems early on and designing for secure outcomes.

This blog post has been turned into a presentation, which was presented at Bsides NoVA 2024! The slides are available here.