Fuzzing JSON Web Services

Fuzzing JSON services, especially those implemented with dynamic scripting languages like Python, Ruby and JavaScript, is often a fruitful exercise. Yet, my observations are that during testing, this is rarely done well enough. In this blog post, I would like to give you a rundown on how I tend to do it. Needless to say, I use my own toolbox because it is specially designed for this but you can come up with your own tools if that is what you are into. Let's get started.

JSON web services are simple web services that take JSON documents as input. They are widely available from your favourite cloud infrastructures like AWS, Azure and Google Cloud to your phone, watch, TV and your fridge. Almost everything communicates with JSON. The reason for this abundance is because unlike other document formats, such as XML, YAML, INI and so on, JSON is relatively simple, compact, and not prone to weird errors. JSON is also natively supported in all modern programming languages and environments for its properties and ease of use.

In many programming languages such as JavaScript and to some extend Ruby and Python, JSON is a first class citizen. This means that JSON documents are not just foreign object models but naturally looking objects and as such, they can be accessed like any other normal object. For example, consider the following request:

POST /path/to/service HTTP/1.1
Host: service
Content-Length: xxxx
Content-Type: application/json

{
    "profile": {
        "name": "Bob",
        "age": 40
    }
}

In order to access field "name" from the property "profile", the developer is likely to do something like this in JavaScript:

// this is what you will normally see in ES5

updateProfile(body.profile.name, body.profile.age)

// ...or in modern ES6 may even look like this which looks a bit safer

const { profile = {} } = body
const { name, age } = profile

updateProfile(name, age)

Notice that both "name" and "age" fields are extracted directly from "profile". Also noticed that we are not validating the types of these two fields nor we are normalizing them. Although this is well too convenient in this example, it the vast majority of public code out there this is not too unlikely.

Exploiting this largely varies on the service and what "updateProfile" function actually does. Hence, there is no particular exploitation style we can follow. We cannot stick some known bad characters hoping to see quick results. Instead, we need to analyse the application behaviour and deduce what could be happening and if there is an opportunity for exploitation. This is where we need to roll up the sleeves and start to fuzz. Fuzz what and how?

Obviously, we need to try different values for both name and age props. Moreover, we need to try some unexpected container values such as arrays and objects. For example, the updateProfile function may be anticipating an object such as {first, last} instead of a string. These checks are perhaps implemented irrespective of how the "updateProfile" function is used with data deserialised from the JSON body and as a result, it contributes to a branch in the code which may contain bugs which do not appear when the name field is simply a string. These are the things we need to try automatically through fuzzing.

I will use AppBandit for this exercise but you can also use Fuzzer or even your own tool if you want to write your own permutator. I've already done this for both AppBandit and HTTPView so I don't want to waste my cycle hence why I am using the tools.

Let's launch AppBandit and open a new Fuzzer tab. Inside we need to set up the base request like the screenshot below. In this case, we are not doing anything fancy. This is just a guide to prepare for the next stage.

Now we need to configure the JSON document so that we can permutate over different combinations. For this, we will use the JSON Fuzz generator. Navigate to Body and select JSON Fuzz from the drop-down. In the first field, we need to setup our document. In the second filed we will set up your payload.

If we just enter a simple string in the payload field, it will be used as is. For example, if the payload is "test" then the JSON Fuzz generator will generate two documents like this:

{
    "profile": {
        "name": "test",
        "age": 40
    }
}

and

{
    "profile": {
        "name": "Bob",
        "age": "test"
    }
}

However, this is hardly going to help us find any vulnerabilities so we will use some other generators in order to extract more value. Let's use a dictionary generator which we can select from the drop-down. Select FuzzDB from the dropdown list of dictionaries and then select "attack/json/JSON_Fuzzing.txt". Keep in mind that AppBandit does not embed any of these dictionaries by default. They will be downloaded on the spot when you need them.

Now that we have the list loaded, we can also edit it so that we can include our own payloads. Feel free to add anything you think could be useful. The more payloads we have the better the fuzz will be. That being said, you can preview how the payloads will be generated with the help of the arrow buttons. Notice that at the moment we are properly quoting the payloads in JSON format which means that they will be interpreted as strings.

This is not what we want. What we want is to use the raw JSON and to do that we need to select the "Parse payload" and "Ignore payload parsing errors" options. Now it is properly generated as you can see from the screenshot below.

And we are done with this. Exit the generator popup and cycle through the sampled fuzzer payloads using the arrow buttons. Notice how everything is generated properly.

Before we hit the play button, I like to configure a few more options. Set the request timeout to something more convenient and also increase the maximum connections to 60 so that we can do the job a lot quicker.

Sometimes the application may misbehave for some reason. Don't despair. If that happens click the Fork button to create an exact copy of the current configuration and execute another test. It is really easy.

Now we are done. The test is completed in no time. The next step is to analyse the results and see if something weird is happening with the service. There is no science behind this. You simply need to explore the result set to find out misbehaviour and if you do repeat it with AppBandit Resend tool so that you can confirm it and explore/exploit further.

I know I did not delve further into what to do once an issue is identified. This is an open-ended question if you think about it. Unlike SQL Injection or Cross-site Scripting and Local File Includes, this is not a predefined vulnerability. In other words, there are no funny characters that we can inject in order to achieve some sort of code execution. However, using the right JSON payload we can achieve control over the code which could lead us to a vulnerability. While I cannot disclose any real-world scenarios at the moment, we have certainly seen many situations where this approach leads to interesting and exploitable results.