In this article, we will discuss the Burp Suite extension Turbo Intruder developed by James Kettle. We will give examples of how you can create your own Python code templates to customise Turbo Intruder’s fuzzing process.
Some of the features offered by Turbo Intruder:
- HTTP stack hand-coded
- Attacks are configured using Python code
- Multi-day/host attacks
- Support for headless environments
- Complex filters
Turbo Intruder offers a wide range of customisation options and takes advantage of templates written in Python code that are then managed by Turbo Intruder’s engine.
Some of the Python templates built into Turbo Intruder are Race condition, Brute force with complex conditions, Multi-Host Fuzzing, Rate Limit and/or perform recursive HTTP requests in combination with tokens (e.g. csrf tokens).
To start using Turbo Intruder, we need to forward a request to Turbo Intruder that has been previously intercepted by Burp Suite.
To forward an intercepted request, we can right-click on it. A new small window appears and in the “Extension” section we select “Turbo Intruder/Send to Turbo Intruder“. The Turbo Intruder window will open, showing your HTTP request at the top and the default Python code at the bottom.
To add an insertion point in Turbo Intruder, add a %s mark to the section where you want the payload(s) to be placed in the HTTP request.
GET /%s HTTP/1.1 Host: exemple.net Cookie: TrackingID=%s Connection: close
In the example above, we insert two
%s marks. The first insertion point is placed in the path. This can be useful, for example, if you want to find assets and/or endpoints. The second insertion point in the
TrackingID cookie parameter is used to maintain a dynamic
TrackingID to prevent you from being rate limited. It is also possible to use a similar technique within a user session to find assets belonging to different user privileges.
Configuration and structure
The Turbo Intruder window is open and is divided into two sections. In the first section at the top, you can edit the HTTP request. In this section, you can also add insertion points for your payloads.
The second section at the bottom allows you to filter the responses and take action based on the responses returned from the target. It also provides the ability to write custom Python code to better customise your fuzzing script.
The first section contains the configuration of the
queueRequest() function offered by Turbo Intruder. This feature provides the ability to configure the target and also the performance.
We start by choosing the endpoint of the target. In our case we use the variable
target.endpoint. Another option is to select the protocol, domain and/or port manually (example: https://evil.com:443).
Once we have set our target configuration, we can configure the number of simultaneous connections and the number of requests per connection. By changing these values, we can optimise the speed of our requests that will be used during the fuzzing process.
The last two options
timeout allow us to configure how many times a failed request should be retried and how long Turbo Intruder should wait for a response before giving up and continuing the process.
Loops / Payloads
There are several ways to make loops in Turbo Intruder. You can make a basic loop containing a range of words given directly from a list or by using a file containing a wordlist with payloads. Turbo Intruder also has the ability to use built-in wordlists that contain assets collected by other Burp Suite processes.
for word in wordlists.clipboard: engine.queue(target.req, word)
A collection of words observed (
observedWords) during Burp scans.
for word in wordlists.observedWords: engine.queue(target.req, word)
At this stage we call the function
engine.queue() and specify the
target.req object. This object contains the request properties we have specified and also the payload insertion points (
Another interesting feature is the
label. It is a label that allows us to filter the response according to our needs.
It is possible to add a speed limit to our requests in our loops so that they can be better adapted to the target we are testing (see the code below).
import time # Parameters to configure triedWords=20 timeMins=0 timeSecs=10 throttleMillisecs=500
(To be placed at the top of our Python script)
queueRequest() function, we can then define delays between requests and/or pause the process after a certain number of requests as shown below.
secs=timeMins*60+timeSecs n=0 for word in open('/ywh/Wordlist/0-9,a-Z.txt'): time.sleep(throttleMillisecs/1000) engine.queue(target.req, word.rstrip()) n+=1 if(n==triedWords): time.sleep(secs) n=0
This allows us, for example, to perform our fuzzing with a delay between each request and a maximum number of requests that can be sent before a pause occurs. In this Python code, we send a request every half second and pause for 10 seconds after 20 requests have been sent to our target (more complex rate limits can also be achieved).
handleResponse() makes it possible to filter the responses according to the data they contain. This is useful to avoid getting too much junk and/or false positives/negatives during the fuzzing process.
The code snippets below show an example of the different filters and once a response has passed the filter, the function
table.add(req) adds the response to be displayed in the result table shown during the fuzzing process.
req.status (Status of the response)
if req.status != 500: table.add(req)
req.time (Response time)
if req.time > 3000: table.add(req)
req.length (Response length)
if req.length == 5689: table.add(req)
req.wordcount (Response word count)
if req.wordcount == 31: table.add(req)
req.label (Request label)
if req.label == "test": table.add(req)
Responses to be collected
Since we use Python code, we can easily open a file and add the information to the file that we have collected during the process. The reason why this can be useful is that in some cases, requests and/or responses containing specific information can be saved in separate files and later managed by other tools.
Below you can see an example where we collect and save the request in a separate file.
Retrieve the request:
with open('C://folder/request.txt','a') as f: f.write(str(req.getRequest())) f.close()
Retrieve our query parameter:
request = str(req.getRequest()) param = request.split('?')[:1] with open('C://folder/params.txt','a') as f: f.write(param) f.close()
Collect the response:
data = req.response.encode('utf8') header, _, body = data.partition('\r\n\r\n') with open('C://folder/responses.txt','a') as f: f.write(data) #or header or body f.close()
We have now covered many different scenarios for setting up and using Turbo Intruder which can be used for advanced fuzzing against your targets. For more information you can read the article “Turbo Intruder: Embracing the billion-request attack” and also see the source code on the official Github page.