July 12th, 2022

Load Testing a WAF

LoadForge is typically used to load test or stress test web applications or HTTP/S-based APIs. However, it can also be used to test reverse proxies such as HTTP load balancers, and in this example, web application firewalls.

What is a WAF?

A web application firewall (aka WAF, or nowadays WAAP) is a proxy that accepts traffic for your app or API and forwards safe requests to your web servers, blocking dangerous ones.

It typically protects web applications from attacks such as cross-site forgery, cross-site-scripting (XSS), file inclusion, and SQL injection, among others. A WAF is a protocol layer 7 defense (in the OSI model), and is not designed to defend against all types of attacks. This method of attack mitigation is usually part of a suite of tools which together create a holistic defense against a range of attack vectors.

How to test a WAF?

There are two main parts to load testing a WAF:

  1. What peak RPS or SSL TPS can it handle - it must scan every request, so what is the maximum number of requests per second
  2. Does it still block traffic during the load test, or are there any failures.

You may want to test the general effectiveness of a WAF, but that's outside the scope of this which is focused on the performance of your WAF.

Example test

Below we have an example test script with some URLs you will need to replace, but it aims to achieve 4 things:

  1. Get the index page (/) of your site at a high rate (30x)
  2. Get an artificially slow version of the index page (/slow-page) at a high rate (30x)
  3. Get a static image at a medium rate (5x)
  4. Get a URL that should be blocked at a low rate (1x)

You'll notice three things that are a bit different about this test:

  1. The wait time is very low - this is to create maximum RPS.
  2. slow-page needs to have a sleep in it of around 0.1 to 0.5 seconds.
  3. We catch the response from our blocked_content request and expect a 403 forbidden.

Depending on the WAF you may need to customize some of the above, but, it does a good job of achieving the goals of our test at a very high rate.

from locust import HttpUser, task, between


class QuickstartUser(HttpUser):
    wait_time = between(0.5, 1)
    
    @task(30)
    def index_page_fast(self):
        self.client.get("/", timeout=5, name="index page")


    @task(30)
    def index_page_slow(self):
        self.client.get("/slow-page", timeout=5, name="slow page")


    @task(5)
    def single_image(self):
        self.client.get("/img/static-content.png", timeout=5, name="image")
        
    @task(1)
    def blocked_content(self):
        with self.client.get("/?test=/etc/passwd", catch_response=True, timeout=5, name="expected block") as response:
            if response.status_code == 403:
                response.success()
            else:
                response.failure("/etc/passwd was not blocked")

Scaling

This test config can run around 5,000 RPS per worker, and you can (with a standard LF account) run up to 20 workers allowing you to test 100,000 RPS with this. For higher tests (up to 4 million RPS) contact LoadForge.