Howto: Use Burp Hackvertor Plugin to Re-sign Requests

Overview

Summary

More and more in modern web applications, particularly sensitive applications such as financial apps, we see the introduction of signature headers which are used to provide some mechanism of tamper-proofing of the request from the client. These signatures can be problematic if using common tools such as Burp Suite or even automated tools such as sqlmap. In this article I describe a technique for recalculating these header values using the excellent Burp Suite plugin Hackvertor and custom tag definitions available in this plugin.

Please note, there are other viable techniques for doing this such as the use of mitmproxy, and I don't claim this is the best technique for solving this problem, but it is a technique 😄

Describing the Problem

Typically a signed request will look something like this:

1POST /foo/bar?some=arg HTTP/1.1
2Host: www.example.com
3...
4X-Signature-Header: <some_hex_value>
5...
6
7{"json":"attribute"}

In many cases, the value of the X-Signature-Header is calculated by the browser at the time the request is sent over the wire. Typically, this value is calculated using some hash-type or HMAC function, based on some subset of the contents of the request itself. I've run across multiple of these in the real world, and I've seen the following as any/all inputs to the hashing function:

  • HTTP method (e.g. POST)
  • HTTP url (e.g. /foo/bar)
  • HTTP query string (e.g. some=arg)
  • HTTP body (e.g. {"json":"attribute"})
  • Other values such as a pre-shared key or API key

Correctly calculating this header after tampering requires understanding how the browser client is determining the value in the first place.

Finding the Signature Calculation in Javascript Source

Because the header value is determined by the browser when it is making requests, it is almost always the case that the Javascript associated with the site will have the methodology for calculating the header. Usually once can simply use the "Search" function in the browser dev tools to find the header name (in our example, X-Signature-Header) in the Javascript, and trace the value of this header. If there is a sourcemap available, this is often even easier to determine, but even in obfuscated Javascript there are only a handful of libraries typically used such as crypto-js. This may take some practice but it's a good opportunity to learn to use the Developer Tools in your browser.

A slightly modified example from a rececnt test where the sourcemap was available:

1export const signRequest = (data: any): string => {
2  return md5(JSON.stringify(data));
3};

In this case, the header value is calculated using an MD5 hash of the POST body (the POST body is passed to this function).

How to Use Custom Hackvertor Tags

If you're reading this, I assume you already know how to use Hackvertor. If not, I'd suggest getting familiar with the overall use of the tool first. By default for security reasons, Hackvertor disables the use of custom tags. You'll have to enable them by going to the "Hackvertor > Allow code execution tags" menu.

Our Scenario

Let's assume the site we are tampering has the following signature mechanism:

  • Only POST requests are signed
  • POST requests are JSON
  • The value of the X-Signature-Header is calculated as the MD5 hash of the JSON body of the request

We want our custom Hackvertor tag to replace the X-Signature-Header if it is present, or add the header if it's not present.

Creating our Custom Hackvertor Tag

In Burp Suite, click "Hackvertor > Create custom tag". Name the custom tag something and pick the Python language (we pick this because the built-in hashlib contains many common hashing functions). We'll paste the following tag body:

 1import hashlib
 2
 3def sign(request_bytes):
 4    # sign using the hash function as identified in the Javascript
 5    hash = hashlib.md5()
 6    hash.update(request_bytes)
 7    return hash.hexdigest()
 8
 9full_request=input
10
11# assume correct line endings per HTTP spec
12line_endings = '\r\n'
13# find the end of the headers block
14end_of_headers = full_request.find(line_endings + line_endings)
15
16# split the request by line endings into an array - NB: this require tweaking for binary protocols used in the body such as Protobuf
17lines = full_request[0:end_of_headers+2].strip().split(line_endings)
18
19# in case we need to parse out the method and URL for inclusion in the signature
20if lines:
21    first_line_words = lines[0].strip().split()
22    if len(first_line_words) >= 2:
23        method = first_line_words[0]
24        url = first_line_words[1]
25    else:
26        method = None
27        url = None
28else:
29    method = None
30    url = None
31
32body_index = -1
33signature_index = -1
34# find the location of the signature header if it exists, and the body
35for i in range(0,len(lines)):
36    if lines[i].startswith('X-Signature-Header'):
37        signature_index = i
38    if lines[i].strip() == '':
39        body_index = i
40        break
41
42# get the body content
43request_content = full_request[end_of_headers+4:]
44# calculate the hash
45signature = sign(request_content)
46
47# update or insert the header into the array using the calculated value
48if signature_index == -1:
49    lines.insert(i-1,"X-Signature-Header: %s" % signature)
50else:
51    lines[signature_index] = "X-Signature-Header: %s" % signature
52
53# reassemble the full request
54output = line_endings.join(lines) + line_endings + line_endings + request_content

Hopefully the comments in the code are helpful, essentially we are:

  • Splitting the HTTP headers by line endings
  • Locating the signature header (if present) and body content
  • Signing the body content
  • Reassembling the entire HTTP request

Unlike a typical Hackvertor tag, which is usually used to wrap a specific piece of data e.g. a query string parameter, this tag is designed to wrap the entire request! This is to make using the tag much easier in practice and avoid the annoying copy/paste of parameters etc. I've found this style of Hackvertor custom tag to be very convenient to use and also to explain to triagers and customers.

Please save the tag and let's continue on to demonstrate its use in a request.

Example Filled Custom Tag Form

Note It seems Hackvertor recently introduced a code length restriction? of 1337 bytes. To use this tag you may need to remove the comments.

Using our Custom Hackvertor Tag

Open a new Repeater tab. Paste the sample request from the introduction:

1POST /foo/bar?some=arg HTTP/1.1
2Host: www.example.com
3X-Signature-Header: <some_hex_value>
4
5{"json":"attribute"}

Next, let's insert our new tag.

  • Select the entire request
  • Right-click, select "Extensions > Hackvertor > Custom > "
  • Send the request

Right-click Menu Tree

The request should look like this (please note the tag will differ slightly because of Hackvertor's use of custom tags):

1<@__demo_signature_tag('5634400c8889932e588fc892746c69e3')>POST /foo/bar?some=arg HTTP/2
2Host: www.example.com
3X-Signature-Header: <some_hex_value>
4Content-Length: 44
5
6{"json":"attribute"}<@/__demo_signature_tag>

And if we look in Logger we should see our calculated X-Signature-Header:

1POST /foo/bar?some=arg HTTP/1.1
2Host: www.example.com
3X-Signature-Header: 068f44ed2f714f315dcbcb762470ee63
4Content-Length: 20
5
6{"json":"attribute"}

Using Hackvertor Tags in Other Burp Functions

The awesome thing about Hackvertor is that it can be used in other Burp functions e.g. Scanner/Intruder, so you can construct a payload using these tags and then run an active scan. The Hackvertor custom tag will ensure the signature is updated with every payload Burp generates!

Conclusion

If you find something wrong or just want to share similar experiences please feel free to reach out to me, I love hearing comments from folks!