A journey back in time: The analysis of the first version of my WAF

A journey back in time: The analysis of the first version of my WAF

As you might already know, back in 2014, I’ve developed a custom Web Application Firewall. You’ll see one adorning BreachDirectory and indeed, you can see one on this blog too. The primary reason I’ve started making it is that I wanted to have one file that I could incorporate into a website to improve its security.

Back then, the firewall was pretty simple – it only blocked basic attack vectors, but hey, it did work as it was supposed to, so I was happy. As time passed, the firewall evolved – I updated its threat detection rules, incorporated new attack vectors that could be blocked, added HTTP security headers.

But its not all roses with a WAF. Today, I want to reflect on just how much my firewall has evolved and changed over the last couple of years, share what I did wrong, what I did right and perhaps what I could have done better.

What I did wrong

Let me first outline a couple of things I did wrong and then I’ll talk about each of them in detail:

  • The firewall only blocked 2 attack vectors.
  • Threat detection rules weren’t very good.
  • The firewall itself was vulnerable.
  • The incidents could have been logged better.

The attack vectors

The first version of my Web Application Firewall only blocked 2 attack vectors:

  1. SQL Injection (SQLi)
  2. Cross-Site Scripting (XSS)

Blocking two attack vectors is far from sufficient security-wise. What about Cross-Site Request Forgery or File Inclusion attacks? Clearly, I didn’t think about the importance of blocking them back then..

Weak policies

The key to a good Web Application Firewall is effectively managing its policies, but the old version of my WAF did not have good rules for blocking attacks. Take Cross-Site Scripting for an example: When blocking such an attack, the firewall would only look for a “script” keyword in the request. What’s more, the filter could have been easily bypassed by capitalizing some of the letters because the firewall would only block requests containing the keyword in all lowercase letters..

Vulnerabilities in the firewall

When the firewall detects an attack it reacts like so:

This is a good thing – the firewall detected a basic XSS, logged the attempt and blocked it, but due to a simple thing I had overlooked, the firewall was vulnerable itself..

Here’s how the firewall works:

  1. If the firewall detects an attack based on a pre-defined ruleset, the attack would be logged and blocked.
  2. According to the attack that is blocked, a message is shown.
  3. The message can contain an IP address, a user agent that is being used at the time, the URL that caused a rule to be triggered, a reason for the block and time.

However, I failed to sanitize the user agent that was being shown – that means that even if an attempted XSS would be blocked, the attack could still be triggered. How? Well..

To make it worse, the XSS payload would execute even if SQL injection would be blocked because the user agent is displayed in all messages!

Fortunately, this version of my Web Application Firewall never saw daylight and only ran locally – had it been public, the consequences could have been more severe: if any user would have had their PC compromised, user agent modified to an XSS payload and then triggered the WAF, they could have been redirected to a phishing website or had their cookies stolen.

Logging violations

The whole point of having a WAF is to block malicious attacks directed at a web application. Blocking attacks is great, but not seeing what was blocked isn’t very effective. This is why logging rule violations is an essential part of a firewall. The older version of my firewall didn’t log violations well – only the attempted attack and the IP of the attacker have been logged. The approach has since been improved.

What has been done right

Now while there are some things that I have done wrong, there are some that have been done decently. Let’s have a look at them too.

Protect all the things!

Instead of filtering specific parameters, the firewall protected both GET and POST parameters by default. This saved me from enormous amount of hassle because I didn’t have to define which parameter needs protection from which attacks.

Custom modes and messages

Ahh, customization.. My favorite.

The firewall had 2 modes that could have been put to use – a “silent” and normal mode. When in silent mode, the firewall would block the attack, but wouldn’t alert the user. In normal mode the firewall behaves differently – it blocks the attack and shows the attacker their IP, browser, URL, provides a reason for the block and shows the current time.

The messages could have been extensively customized, but I kept them clean and simple because I like it that way.

Both of these features exist to this day.

Where to now?

Even though I started developing the firewall in 2014, it is still being actively maintained and to this day, the firewall is one of my favorite projects. I love the fact that the firewall is based on a “drag-and-drop” principle and that the firewall, though small in size, protects websites from multiple types of attacks.

The firewall will continue to evolve. I don’t have plans to cease the development at the current time – I’m still updating its core, policies and implementing new features which I’ll hopefully write about on this blog soon.