Twelve Days of ZAPmas - Day 4 - Fuzzing for Injection

Twelve Days of ZAPmas - Day 4 - Fuzzing for Injection
Mic Whitehorn
Author: Mic Whitehorn

I briefly introduced fuzzing earlier in the series, citing it as the second primitive upon which application testing techniques are built. OWASP has a more in-depth definition available here. We also have a video on fuzzing with Burp Suite here.

Fuzzing in ZAP

Much like tampering, you can start by locating the request we want to fuzz in the History tab, and right-click it to bring up the context menu.

zap4 4

From there, go into the Attack submenu and select Fuzz…

This will open up a dialog showing the request, but unlike the Request Editor, this has an empty list on the right side indicating Fuzz Locations.

zap4 3

My example request is an authentication payload with a username and password. I could select multiple fuzzing locations, but let’s limit it to just the password field for now. I can select the text, the word samurai in my example, and use the Add… button to the right of the Fuzz Locations list.

zap4 2

This will open the Payloads dialog, which indicates the location and current value, and has an Add… button.

zap4 1

I’ll click Add…, which will open up yet another dialog to a payload.

zap4 7

There are many types of payloads, for example it’s not uncommon that I want it to iterate over a range of numbers. But in this case, I’ve left it as Strings and pasted in a list of values just to demonstrate it. I then clicked the Add button at the bottom of this dialog to finish adding my list of strings as a payload. This is now reflected in the Payloads dialog, as shown in the following image.

zap4 6

And if I click OK on the payloads dialog, I’ll see the Fuzz Location’s highlighting is matched to selection in the request body. I also have a count in the # of Payloads column of 11, which matches the number of strings I supplied.

zap4 5

Finally, I can click the Start Fuzzer dialog to run the fuzzing operation.

zap4 9

With the default settings for only 11 requests, this appeared instantaneous for me, and populated a Fuzzer tab in the bottom pane of ZAP’s main window, as pictured.

Main ZAP window showing the Fuzzer tab has now appeared in the same pane as the History tab is presented. Most of the requests resulted in a 401 unauthorized, but the one with the correct password shows a 200 OK, and one request with a double-quote character shows a 400 Bad Request with a Reflected badge in the State column.

I rearranged my columns into an order that made more sense. The top request (Task ID 0) being the original valid login. Task ID 6 with the payload of samurai was the valid password I threw into the list, and you can see it has the 200 status code and the same size request and response as the original. The majority of the others led to a 401 Unauthorized response, all with the same sizes as each other. If I select one of these, it will open in the Response pane.

A 401 Unauthorized response in ZAP, with an error stating "Invalid credentials".

It’s exactly what you might expect from an invalid login. Referring back to the list of them, the one interesting response was Task ID 10, which returned a 400 Bad Request, had a payload of a double-quote character (“), and was the only one with an entry in the State column indicating it was Reflected. In other cases this could be an indicator of something interesting. However, in my case, it’s far more benign than that. If I switch to the Request tab, it’s pretty obvious. Because I put that quote character into a JSON structure without escaping it, I’ve malformed the JSON structure, as shown.

A JSON body in a request, with a malformed section due to an extra set of quotes in the password parameter.

If I switch back to the Response tab, you can see that the response was HTML formatted, and just happened to use the same quote characters, so ZAP misinterpreted it as Reflected.

A response with a 400 Bad Request status, highlighting that ZAP indicated a quote character that is a standard part of the first HTML attribute in the document.

With other payloads this certainly might be interesting, like if I was fuzzing for cross-site scripting flaws. But in this case, it’s a false positive.

Applying this to XSS

Let’s look at a simple example of a classic reflected input. The example request below submits a username and password.

An HTTP POST request with a form-urlencoded body.

When the response comes back, I can see the username is still populated.

A login form with the value foo populated in the username field.

Something that makes this even more significant, is that this isn’t a case where an API call is made and 401 response makes the client show an error message. This response contains an actual HTML fragment containing that value.

A section of HTML with a highlighted portion showing a text input with a value attribute equal to foo.

Because of the context of this input, my goal as an attacker is to somehow escape the context of the value attribute, normally by closing the quotes. Another option that might work if I can get multiple inputs to reflect would be escaping the existing closing quote. In either case, let’s say I want to fuzz this to check how it behaves with a bunch of different values. So I’ll send this login request to the Fuzzer tool, just like before. I’ve manually written out a nice short-list of payloads to try. Normally I would use something like one of the lists in SecLists, but in this case I’ll keep it very simple.

ZAP's Add Payload dialog, with various payloads based on the value foo followed by quote and backslash characters.

After I run the Fuzzer, I can see that only one of these was marked as reflected, but they’re all 200 OK. In a JSON payload, I might think that my trailing backslash has escaped the closing quote, and might result in something to pursue. In HTML, it doesn’t fulfill that function, so it has not effect.

Fuzzing results in ZAP, with a highlighted entry showing the payload foo followed by a trailing backslash (or escape) character was marked as reflected.

I would also like to determine why specifically my double-quotes didn’t break context, so I’ll look at one of those first, and find that quotes are being HTML entity encoded.

An HTML input element with the value foo followed by an HTML entity encoded quote character.

For comparison, I’ll run a few payloads against a wide-open unsafe reflection in a different demo app. This time, the input reflection is into a different context. It’s a text node within a div tag.

A section of an HTML document with a text node holding a value of foo inside a div with an ID of payload.

So my payloads will look to create HTML elements. Now, if ZAP sees my payloads as reflected, there’s almost certainly a cross-site scripting flaw here. Some filtering libraries like DomPurify will remove malicious attributes without fully removing HTML, so my benign <h1>Test</h1> payload could reflect and not indicate a vulnerability. But the only way to know is to try it.

A set of 3 values in ZAP's payload dialog, each creating a different HTML element. Two of these have script payloads as well, one in an a tag's href attribute, the other in an img onerror event handler.

As you can see below, all of them were reflected. And if I take, for example, the last payload (the img tag) and submit it through the browser, let’s see what happens.

 ZAP fuzzing results showing all 3 payloads are marked as reflected.

As you can see, the pop-up popped up, proving that script execution was obtained.

The test form rendered within the browser, with the img onerror payload from the fuzzer visible in the input box. An alert pop-up is visible, indicating that the onerror handler executed.

What about SQL injection or command injection?

The same principle applies to server-side injection flaws, but we don’t generally have the same visibility into how our input is being treated. Still, consider the following fuzz result, also against a login form:

A set of fuzzing results in ZAP, most being regular alphabetic names. One entry is o'doyle, with a single-quote (or apostrophe) character.

I can see that every entry responded the same way except one. That one in question produced a 500 server error (this is usually not a gracefully handled error). It’s different from the other payloads in that it includes a single-quote (‘) which is how strings are wrapped in common SQL dialects. It also has a space in it, and the others don’t. And if I look at the actual response, I’ll see a stack trace related to a SQL query.

A section of an error response showing a SQL error, with the malformed query visible highlighting how o'doyle's apostrophe caused the string to be closed early, malforming the query.

This is where an intentionally vulnerable learning app (OWASP Juice Shop in this case) is different from most production systems. A SQL error could be returned, but often it will be a generic 500 error, and we’d have to do additional detective work to either demonstrate an actual exploit or at least establish better evidence that it’s a real flaw.

In Summary…

Fuzzing is a great discovery technique, but there’s also plenty more that can be done with the tool, including using multiple locations, multiple payload sets, and extra payload processing rules for stuff like encoding.

Take a look at day 5, looking at using Scope and Contexts within ZAP to make it easier to see what you’re doing and keep your attacks on target.

Join the professionally evil newsletter