Introduction
The CSRF token is a protection that requires the insertion of a random and dynamic value in a request. This value is then analysed by the server to determine whether the request is legitimate. During your penetration tests, you have probably already come across an application using these CSRF tokens. In this case, you may have noticed how confusing it is to analyse such an application with Burp.
Indeed, Burp‘s native tools do not allow the arbitrary insertion of such a dynamic value in queries. There is the possibility to create macros natively, but this feature is not intuitive and does not always meet our needs. That’s why we’ve chosen to talk about Stepper, a Burp extension.
Example of context
As explained earlier, we want to analyse an application that uses CSRF tokens as protection. Consider the worst case situation: the CSRF token is single-use. This means that a new CSRF token value is required each time a request is sent.
Situation
One can imagine a login function that requires a token to be obtained from another endpoint.
CSRF Token Endpoint
Request:
GET /user/csrf-token HTTP/2
Host: some-example.site.com
Cookie: session=0228df79-2459-48fa-b642-85962e6f24d7;
Response:
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 0
Set-Cookie: csrf_token=c91074f8-2929-4068-8adc-e2c7092d1d61; Path=/
Set-Cookie: session=0228df79-2459-48fa-b642-85962e6f24d7; Expires=Fri, 08-Apr-2022 08:04:32 GMT; HttpOnly; Path=/
Strict-Transport-Security: max-age=15724800; includeSubDomains
Here, our endpoint gives the new CSRF token value to use.
Login Endpoint
On the login endpoint, the following behaviour is observed:
Request:
POST /user/login HTTP/2
Host: some-example.site.com
Cookie: session=0228df79-2459-48fa-b642-85962e6f24d7; csrf_token=c91074f8-2929-4068-8adc-e2c7092d1d61;
X-Csrf-Token: c91074f8-2929-4068-8adc-e2c7092d1d61
{"email":"[email protected]","password":"*****************"}
Response if the CSRF token has been updated with the new value:
HTTP/2 200 OK
Content-Type: application/json
Content-Length: 3
Set-Cookie: session=0228df79-2459-48fa-b642-85962e6f24d7; Expires=Fri, 08-Apr-2022 08:05:57 GMT; HttpOnly; Path=/
{}
Response if not:
HTTP/2 403 Forbidden
Content-Type: application/json
Content-Length: 38
{"message": "Invalid CSRF token""}
Here, we observe that the value of the csrf_token cookie and the X-Csrf-Token header must be updated before the POST login request otherwise it is refused by the server.
Challenge
This situation is problematic, because natively, Burp does not have an (intuitive) solution to perform a scan while renewing the CSRF token for each request. Fortunately, this limitation can be overcome with the Stepper extension.
Stepper principle
Stepper works by running a set of one or more queries called Sequences. For each sequence, it is possible to define variables whose values depend on the responses to these queries. These variables are updated each time the Sequence is executed. The prior execution of these Sequences and the consultation of the variables are possible from the other burp tools. This makes available dynamic variables that depend on predefined queries.
Definition of the CSRF Sequence
To satisfy our example, we need to create a sequence that contains the request to the endpoint /user/csrf-token. To do this, we can directly select our request in the proxy and add it to a new Sequence using the right-click context menu (Extensions > Stepper > Add 1 item to Stepper > New Sequence).
After defining the name of the new sequence, the query is added in Stepper.
Definition of global variables
Stepper has a menu for defining global variables. These are constants that can be reused in Sequences. From this menu, I define the value of my session cookie before authentication. Thus I can reuse its value in the request to obtain the CSRF token.
NB: The definition of this global variable is not necessary in the context of our example. It is simply a demonstration of the use of this feature.
The call to a variable from a sequence is done like this:
$VAR:<VARNAME>$
Thus, to use the session variable from the CSRF Sequence, you only need to make this change in the Sequence :
- Cookie: [...]; session=0228df79-2459-48fa-b642-85962e6f24d7;
+ Cookie: [...]; session=$VAR:session$;
Execution of the sequence
It is possible to manually execute a sequence via the Execute Step button. In our case, this allows us to get the response from the server.
The global variable $VAR:session$ has been replaced by its value in the sequence request.
It is in this server response that we will retrieve the value of our CSRF token. To do this, we need to declare a post execution variable.
Definition of post-execution variables
Post-execution variables are variables that are updated after each execution of the sequence. It is possible to define a regular expression that will retrieve the desired value from the server response. The example below shows how to define a post-run variable that will retrieve the value of our CSRF token.
The regular expression for capturing the value of the csrf_token cookie is as follows:
csrf_token=([\-0-9a-f]+); Path
This CSRF variable can be consulted by Burp’s tools. It is even possible to automate the execution of the Sequence to update the token value before each request.
Using stepper from Repeater
Now, if we take our login request and try to replay it with Burp Repeater, we find ourselves in the situation mentioned above.
Without updating our CSRF token, the server does not process our request. However, now that we have defined the CSRF Sequence, we can use its variables. Using the variables from another tool is done in this way:
$VAR:<Sequence_name>:<var_name>$
Finally, to indicate that the sequence must be executed before the request is sent, this header must be added to the request:
X-Stepper-Execute-Before: <Sequence_name>
Thus, to meet our needs, the query must be modified as follows:
POST /user/login HTTP/2
Host: some-example.site.com
- Cookie: session=0228df79-2459-48fa-b642-85962e6f24d7; csrf_token=c91074f8-2929-4068-8adc-e2c7092d1d61;
+ Cookie: session=$VAR:CSRF:session$; csrf_token=$VAR:CSRF:CSRF$;
- X-Csrf-Token: c91074f8-2929-4068-8adc-e2c7092d1d61
+ X-Csrf-Token: $VAR:CSRF:CSRF$
+ X-Stepper-Execute-Before: CSRF
{"email":"[email protected]","password":"*****************"}
Thus, each time the request is sent, the Sequence is previously executed and the CSRF token is updated.
This can be seen by the success of the login request above. This is proof that our request has been processed by the server.
Using Stepper in Intruder
Well, that’s all well and good, but what we’ve done so far isn’t very impressive. To be of any real use, we need to extend this configuration to other tools.
Now let’s imagine that we want to perform a brute force attack on the login endpoint. This is an attack that consists of making numerous login attempts until the correct password is obtained.
Here, the Repeater request that contains our Stepper setting can be sent directly to the Intruder tool.
All that remains is to select our list of potential passwords for the attack.
The attack can be launched without worrying about the rest. In the results of the attack, no errors related to the CSRF token are observed.
The management of the CSRF token was handled by Stepper. Moreover, if we look at the details of the requests sent by Burp during the attack, we can see that before each login attempt, a request to retrieve the new CSRF token value is sent.
Conclusion
The above examples are of course only an introduction. It is possible to apply this automation in other tools like Burp’s Scanner, or to define a more complex sequence or with more variables. But the potential of stepper is there. Thanks to Corey Arthur for this great extension.