Bypassing An Industry-Leading WAF and Exploiting SQLi

Since we're still going through the disclosure process, the final payload won't be shown here. However, the goal of this blog post is to educate readers on methodologies and how you can manually bypass WAFs too.

During a recent engagement, I was tasked with assessing the security of a customer's web application. After looking around the application and getting a feel for it I found a few issues. I then started looking through my Burp logs and began playing with some parameters.

WAF Detection

I tried the old tried and true trick of adding ' to many parameters and endpoints to see how the app responded. The following screenshot shows something that caught my eye - mostly because in the UI, this parameter was called in drop-down option, not via direct user input so I thought there was a good chance the developers didn't protect this parameter.

This is a common theme I often see: Parameters that are passed in requests that the UI shows as a pre-defined set of values are often left unprotected by the developer.

Exploitation

After adding another ' to close the query and getting a 200 response, I dove into basic exploitation. Let's try ' OR '1'='1-- - in the vulnerable parameter:

Immediately I was taken aback.

We always request that our customers turn off their WAF when we conduct application security assessments. However, sometimes, such as in this instance, they believed they did, but it didn't happen.

Either way, at this point, the assessment was underway. I gambled and decided to dive deeper here, and if I could bypass the WAF, I could demonstrate both the vulnerability and the importance of not relying on WAFs for protection to the client - which would be two big wins.

I started working backward from the payload’s end, starting with the comment. I saw it was triggering it, so I started trying known techniques such as double and triple URL encoding, but this didn't work. I tried taking the comment portion out (dash dash)...

This didn't work. Now I'm thinking maybe it's also detecting the 1=1. So what happens if we just do 1=?

It bypassed the WAF. Maybe we're onto something? What happens if we do 1=2?

Well that didn't work. As you can see here, I even tried removing the '. A lot of signature detection also has to do with the = character, but what about the less commonly used != comparison operator?

It worked! So let's reassess. We know that = doesn't work, but != works. We can't comment out the query using two - characters or ;# because both will trigger the WAF.

We know the SQL server database will execute the query and since this is error-based, it's still attempting to execute the query and return verbose information.

After doing a few hours of research on this well-known WAF, we know it relies heavily on signature-based detection. Looking at logs, it looks like it's updated pretty regularly. The idea here is that we want to use as many possible combinations of different methods in order to bypass detection.

Hours of trial and error later...

I threw it all out and decided to try a different approach. From my own experience, blind payloads can have a higher level of success. If there's error-based SQLi then there should also be a way to infer data returned.

In addition to adding further obfuscation in the payload like using /**/ instead of + or %20 for spaces, let's throw in a blind payload. We know there's a WAF in place, so throwing in NULL's or other commonly used words such as ORDER BY will likely be detected.

So we know a regular blind payload won’t work. Let’s try working our way back (again). This time, let’s just try to delete the “A” in DELAY and see what happens.

I know what you're thinking.

Ok so a few things happened here. After trying a bunch of different blind payloads, I tried this blind payload removing the A and got an error back. So we’re back to error-based SQLi  (sort of). We’re in the middle of the two but we’ll take whatever works.

We know that WAITFOR isn’t being filtered – everything here works but DELAY is getting picked up.

After a few hours of trying different things, I wanted to see if we could end the query like we did before. So I added the dash dash again at the end but this time in this payload:

It worked! What the heck? So we know that works in this case, but not sure if it will be useful or not. At this point, I tried doing other things like adding unicode characters in place of regular characters as sometimes SQL Server will translate them on the back end. I also tried using BENCHMARK, breaking up null bytes, and using other payloads including CONCAT and HEX to obfuscate the payload, but they kept getting caught.

Hours of research later, I found this neat blog:

https://swarm.ptsecurity.com/advanced-mssql-injection-tricks/

Way down towards the bottom of this page there's a little tid-bit on non-standard whitespace characters. This is from the blog:

So let's give that a try with %C2%A0:

Here, it sort of worked. The whitespace characters were another evasive technique that worked and avoided signature detection BUT it was breaking up the query in a way we didn't want it to. What happens if we move the whitespace character somewhere else... like maybe before the D in DELAY?

IT WORKED! Now we have confirmed Blind SQLi bypassing this WAF. If you notice here, the payload has a WAITFOR DELAY for 12 seconds and the response took 12,105 milliseconds.

I tried other things such as pulling the value of LEN(DB_NAME()) = 4 which worked by confirming that the database name was 4 characters long with a 4 second delay.

So now let's try pulling other metadata to demonstrate to our client why this is an issue. I tried pulling data using traditional methods such as @@version and using other functions such as ASCII and SUBSTRING but none of them were passing the WAF. I even tried encoding random parts of the word SUBSTRING but that didn't work:

Eventually I thought this isn't ideal... but at least I was able to prove SQLi to some degree.

At this point I had spent WAY too much time on this for a "regular" assessment. I thought well we can show the customer that we were able to extract the LEN value of specific metadata. That's cool right?

Nope.

So I pushed on. After trying many other things, I thought "What if I added whitespace in there but using /**/ to obfuscate and break up the payload?

As you can see here, it didn't work. What if I added junk in between the /* */ to make it seem like a comment?

Nope. Hours go by again and then I figured what if I try adding other characters of a unique nature in there as well?

I know this part is frustrating for you to read, but as we go through the disclosure process, we can't share the final payload yet.

Naturally, at this point, this is me:

Recap

  • Detection was fairly straightforward, with ' and ''
  • We then turned it into a blind exploitation attempt because we're dealing with this WAF, and there's a lot of signature detection going on, and blind payloads just generally have better luck
  • Since we saw that it was largely signature-based, we always retried certain things that didn't work (ex. commenting out the query with the dash dash ended up working after modifying the rest of the payload)
  • We then went back to the query and tried to turn it back into error-based because although we could have achieved this fully through BLIND-SQLi, the WAF was blocking so many characters, and these tests are time-boxed