More up-to-date Hackvertor game-changer techniques, code examples, and tips for advanced penetration testing and bug bounty.
Hackvertor is a Burp extension that programmatically extends Burp capabilities, by allowing you to embed neat code logic directly into HTTP requests sent/proxies by Burp and its extensions. Similar to Postman pre-request scripts.
Here, I will try to provide more structured information and recipes, so in time of need, you won’t spend time on setup and basic things. Go beyond that with your attacks!
This article is the continuation of the part #1. I will address more use cases, edge cases, and will provide code examples to quickly start using Hackvertor.
Most of the things we will do here can be done manually or using any pure programming language or in combination with additional tools, such as Postman. But you will give up on all the Burp capabilities and extensions. With HV you can leverage Intruder, Extensions, Logger, Proxy, and more to work like a PRO.
For such a thing, we need a dynamic layer in between Burp and the server, luckily, we have a well-integrated and easy-to-use solution that works across all the Burp tools — Hackvertor extension (see part 1 for more info).
Of course, you can set up your custom middleware proxy that could do the same with the full code customizations as we want. Or you can write a custom Burp extension. But at what cost? Using HV custom scripts is a much faster, and more reusable solution.
When you stumble upon a custom message security layer or any variant of message level obstacle, in most cases that area will be a white spot across many testers. Always challenge the message protocol. There would probably hide some juicy issues.
With HV you can leverage Burp’s attack capabilities and Intruder automation. Also, you can use the HV tag with external tools that support proxies. Burp’s proxy also can process tags.
In delimited string “a:z:c” and them split it
_input1 = str(input).split(“:”)\[0\]_
We can pass JS_ON and parse it
jsonObj = json.loads(inputJSON)
for i in jsonObj:
str(jsonObj\[i\])_
When we want to update our request header upon body defined param that itself contains a HV input, like
GET /
Host: host.com
AuthHeader: <@_Custom(‘Signs inJSON variable’,’signing Key’,’’)><@/_ Custom>
<@set_inJSON('false')>
{“sqlQuery”:”SELECT …”,
“timestamp”: <@timestamp/> }
<@/set_inJSON>
Param processing is nested and done from within the deepest layer up. For example, in the example, below inJSON parameter that would hit the Python code would contain the execution result of <@timestamp/> tag.
Execution steps:
Another example of passing parameters:
GET <@set_b_full_url('false')>/v1/account/prefs<@/set_b_full_url> HTTP/1.1
Host: host.com
User-Agent: python-requests/2.26.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
X-API-Key: <@set_b_key('false')>XXXXXXX<@/set_b_key>
Authorization: Bearer <@_Custom('0acb0974295f467f01ff05b01b9d2e64')><@/_Custom>
Content-Length: 39
<@set_b_body('false')>{...JSON Body...}<@/set_b_body>
Param processing when using the Intruder. First, the Intruder’s payload is placed into the request, and the HV processes the tag. Everything works seamlessly. However, the Intruder Attack UI won’t show the processing result, only a tag. The actual processed request you can view in the Logger tab.
<@_Custom(“id code”)><@/_ Custom>
java -jar .\jython-standalone-2.7.0.jar .\Fireblocks.py
Note, that standalone Jython loads the classes from ./ local Lib folder, while Hackvertor’s loads from the temp folder created during the start of Burp/HV.
We have a couple of popular Python options to work with a JSON object. But be careful when using them in crypto-related flows.
The next 4 code examples show that each hashing technique could result in a different output, that will result in an invalid message signature. Know exactly how the client library works to be able to replicate its behaviour to create a valid message.
>>> sha256(json.dumps('{"a":"a"}').encode("utf-8")).hexdigest()
'c4602ab462c46e37f6677e339371bd86337051838ce1c49d9ff4b91ffffa67c4'
sha256(json.dumps('{"a": "a"}').encode("utf-8")).hexdigest()
'e37d578267a7f6d8936b8a749f1eaedcde9434a01ddb6333c2d60646d87c8132'
sha256('{"a":"a"}'.encode("utf-8")).hexdigest()
'681523631e0f5d3904d881dd163683081e0e45afdad34376ff5bf5fbadada6c7'
>>> sha256(str(json.loads('{"a":"a"}')).encode("utf-8")).hexdigest()
'8e9ee3e5afddd3561dc6e1d693ad3c90a711c579621d755447e07c357a311450'
This Hackvertor script shows the basic usage of symmetric encryption. We would use it when certain parameters should be sent encrypted. In this particular example using AES/CBC/PKCS5PADDING suit with a padded timestamp as an initialization vector.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# This Hackvertor script shows basic usage of symmetric encryption.
# We would use it when certain parameters should be sent encrypted.
# In this particular example using AES/CBC/PKCS5PADDING suit with a
# padded timestamp as an initialization vector.
import base64
import hashlib
from java.util import Base64
from javax.crypto import Cipher
from javax.crypto.spec import IvParameterSpec
from javax.crypto.spec import SecretKeySpec
timestamp = "000000"+ str(ts)
# timestamp = "0000001631634319"
# either static or external value
encryptionKey = "theKey"
inCVV = str(input)
_cipherTransformation = 'AES/CBC/PKCS5PADDING'
_aesEncryptionAlgorithem = 'AES'
encryptedText = ''
cipher = Cipher.getInstance(_cipherTransformation)
key = encryptionKey.encode('utf-8')
secretKey = SecretKeySpec(key,'AES')
ivParameterSpec = IvParameterSpec(timestamp)
cipher.init(Cipher.ENCRYPT_MODE,secretKey,ivParameterSpec)
cipherText = cipher.doFinal(inCVV.encode('UTF-8'))
encoder = Base64.getEncoder()
encryptedText = encoder.encodeToString(cipherText)
output = encryptedText
# For local debugging
# print(encryptedText)
# return encryptedText
This Hackvertor script shows a basic example of signing an HTTP body JSON message. Each message has a timestamp and signature calculated by concatenating API-specific JSON keys altogether with a secret value/token and hashing them with SHA384. The server upon message validation checks the timestamp and validates the signature by re-calculating the input JSON with the server-side stored authentication key of the client. Also, the server decrypts the CVV with a symmetric key linked to the specific client.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# This Hackvertor script shows a basic example of signing an HTTP body JSON message.
# Each message has a timestamp and signature calculated by concatenating API-specific
# JSON keys altogether with a secret value/token and hashing them with SHA384.
# The server upon message validation checks the timestamp and validates the signature
# by re-calculating the input JSON with the server-side stored authentication key of
# the client. Also, the server decrypts the CVV with a symmetric key linked to the specific client.
import json
import hashlib
# Mock Params used for local testing
# apiName = "api1"
# clientSecret = "XXXXXXXXXXXXXXXXXXX"
# inputJSON = '{"field1":"John","field2":"Smith","field3":"authorization","field4":
# "USD","field5":1000,"wallet_data":{"email":"[email protected]","phone":"1111111111",
# "login":"333"},"field6":"378","locale":"en-GB","field7":"certainID","field8":
# "http:\/\/some.callback.url.com/callback","field9":"http:\/\/some.callback.url.com/callback2",
# "field10":"someAdditionalID","request_id":null,"variable1":"val1","variable2":"val2",
# "variable3":"val3","version":"1.3","field11":1631281107}'
# Burp params
inputJSON = inJSON
clientSecret = str(mSecret)
apiName = str(api)
output = str(dir())
apiSignDic = {
"api1":
["field1","field2","field11","field3","field6","field10","field4","field5","field7","field8","field9"],
"api2":
["field1","field2","field11","intent","field6","field10","agent_name"],
"api3":
["field1","field2","field11","field3","field6","field10","field4","field5","field7","field8","field9"],
"api4":
["field1","field2","field11","field3","field6","field10","field4","field5","field7","field8","field9","field13"],
"api5":
["field1","field2","field11","field3","field6","field10","field4","field5","field7","field8","field9","field13"]}
jsonObj = json.loads(inputJSON)
# null values ignored
concat = ""
for k in apiSignDic[apiName]:
for i in jsonObj:
if i == k and jsonObj[i]:
concat += str(jsonObj[i]) + "\n"
concat = concat.replace("\n","")
m = hashlib.sha384()
m.update(concat.encode('utf-8'))
hResult = m.hexdigest()
output = hResult
# output = concat
# print(concat)
# print("\n")
# print(hResult)
# print(apiSignDic[apiName][0])
# Burp raw example
# POST /controller/api HTTP/1.1
# Host: host.com
# Accept: */*
# Content-Type: application/json
# timestamp: <@set_ts('true')><@timestamp/><@/set_ts>
# Custom-Authentication: <@_APISign('api/name' ,'client-provided secret',
# 'Hackvertor code execution code')><@/_APISign>
# Content-Length: 0
# <@set_inJSON('false')>
# {
# "field": "xxxx",
# "field2": "xxxx",
# "field3": "xxx",
# "field4": "xxx",
# "field5": 100,
# "field6": "xxxxx",
# "cvv_encrypted": "<@_EncryptCVV('client-provided secret','Hackvertor code execution code')>223<@/_EncryptCVV>",
# "timestamp": <@get_ts/>
# }
# <@/set_inJSON>
# Burp result example
# POST /controller/api HTTP/1.1
# Host: host.com
# Accept: */*
# Content-Type: application/json
# timestamp: <@set_ts('true')><@timestamp/><@/set_ts>
# Custom-Authentication:0beedf0971f784a6635a14ae8edc4c000e697f714a273409838b2fc701678d526f5848a416e7da8f4e90525f5c56f7bd
# Content-Length: 0
# {
# "field": "xxxx",
# "field2": "xxxx",
# "field3": "xxx",
# "field4": "xxx",
# "field5": 100,
# "field6": "xxxxx",
# "cvv_encrypted": "AESEncryptionResult",
# "timestamp": 1631281107
# }
This Hackvertor script shows an example for creating JWT based message signature for an HTTP request. The code takes as parameters: path, query, and HTTP body. Hashes them, and creates a one-time JWT token that is signed with the client’s RSA key. The JWT token is used for both authentication and message signing.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# This Hackvertor script shows an example for creating JWT based message signature for HTTP requests.
# The code takes as parameters: path, query, and HTTP body. Hashes them, and creates a one-time JWT
# token that is signed with the client's RSA key. The JWT token is used for both authentication and message signing.
import json
import time
import math
import random
import base64
from hashlib import sha256
from collections import OrderedDict
import re
import sys
import os
from java.util import Base64
from javax.crypto import Cipher
from javax.crypto.spec import IvParameterSpec
from javax.crypto.spec import SecretKeySpec
from java.security import Signature
from java.security import PrivateKey
from java.security.spec import PKCS8EncodedKeySpec
from java.security import KeyFactory
# Example of JWT token produced by the code
# {"typ":"JWT","alg":"RS256"}.{"uri":"/v1/controller/method","nonce":2080922210380511619,"iat":1644951606,
# "exp":1644951661,"sub":"41604da2-da3f-462a-bac1-3c4c0419f40e","bodyHash":
# "12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126"}.validsignature
private_key = """
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
"""
api_key = "41604da2-da3f-462a-bac1-3c4c0419f40e"
# Default param values for testing
if not 'b_full_url' in globals():
path = "/v1/controller/method"
# else from <@set_b_body('false')>XX<@/set_b_body> set in Burp
else:
path = str(b_full_url)
if not 'b_body' in globals():
body_json = ""
else:
# note that using the below line will produce a string object different
# from the the input, thus just take body as string
# body_json = json.loads(str(b_body))
body_json = str(b_body)
timestamp = time.time()
nonce = random.getrandbits(63)
timestamp_secs = int(math.floor(timestamp))
# In case there is a need for special escaping of an input data (HTTP request body in our case)
# before it would be signed (replicating client's behaviour)
# path = path.replace("[", "%5B")
# path = path.replace("]", "%5D")
# Here we need of OrderedDict to maintain the correct order of JSON keys
# else, the signature validation in certain cases may break, since the server may rebuild the
# payload not as it was sent
# token = OrderedDict([
# ("uri", path),
# ("nonce", nonce),
# ("iat", timestamp_secs),
# ("exp", timestamp_secs + 55),
# ("sub", api_key),
# ("bodyHash", str(sha256(json.dumps(body_json).encode("utf-8"))))
# ]
# )
# json_payload = json.dumps(token,sort_keys=False)
# json_payload = re.sub("\s+","", json_payload)
# original code from
token = {
"uri": path,
"nonce": nonce,
"iat": timestamp_secs,
"exp": timestamp_secs + 55,
"sub": api_key,
# Hashing request body as a string
"bodyHash": sha256(body_json.encode("utf-8")).hexdigest()
# Hashing request body as serialized object
# Serialize obj to a JSON formatted str using this conversion table.
# https://docs.python.org/3/library/json.html
# "bodyHash": sha256(json.dumps(body_json).encode("utf-8")).hexdigest()
}
# As with HTTP message body, making sure our JWT's token format will match the one expected by the server;
# here should be present any specific data transformations as they performed by the client, else server
# may fail to verify our signature
json_payload = json.dumps(
token, separators=(",", ":"), cls=None).encode("utf-8")
# clearing whitespaces
json_payload = re.sub("\s+","", json_payload)
# Construction header and payload parts
algorithm = "RS256"
segments = []
header = {"typ": "JWT", "alg": "RS256"}
json_header = json.dumps(header, separators=(",", ":")).encode()
segments.append(base64.urlsafe_b64encode(bytes(json_header)).replace(b"=", b""))
segments.append(base64.urlsafe_b64encode(bytes(json_payload)).replace(b"=", b""))
jwtToSign = b".".join(segments)
# Clearing out the private key; removing header,footer and whitespace
private_key = private_key.replace("-----BEGIN PRIVATE KEY-----","")
private_key = private_key.replace("-----END PRIVATE KEY-----","")
private_key = re.sub("\s+","",private_key)
# Creating the singature
keyBytes = base64.b64decode(private_key)
keySpec = PKCS8EncodedKeySpec(keyBytes);
keyFactory = KeyFactory.getInstance("RSA");
privKey = keyFactory.generatePrivate(keySpec)
Signer = Signature.getInstance("SHA256withRSA");
Signer.initSign(privKey)
Signer.update(jwtToSign)
signatureBytes = Signer.sign()
# Appending the signature to the header and the payload
segments.append(base64.urlsafe_b64encode(signatureBytes).replace(b"=", b""))
encoded_string = b".".join(segments)
# Sending back the result to Burp
output = encoded_string.decode("utf-8")
# debug output while testing the code without Burp
# output = json.dumps(body_json).encode("utf-8")
# print("\n" + str(output))
# Burp Request will look like
# POST <@set_b_full_url('false')>/v1/controller/api/object<@/set_b_full_url> HTTP/1.1
# Host: host.com
# User-Agent: python-requests/2.26.0
# Accept-Encoding: gzip, deflate
# content-type: application/json
# Accept: */*
# Connection: close
# X-API-Key: 41604da2-da3f-462a-bac1-3c4c0419f40e
# Authorization: Bearer <@_OurScriptTag('e03a3b0a2e3adb9507b2e0e758bb0a6a')><@/_OurScriptTag>
# Content-Length: 138
# <@set_b_body('false')>{"id":"someid","status":"APPROVED","address":"address","tag":"s"}<@/set_b_body>
# result
# GET /v1/controller/api/object HTTP/1.1
# Host: host.com
# User-Agent: python-requests/2.26.0
# Accept-Encoding: gzip, deflate
# content-type: application/json
# Accept: */*
# Connection: close
# X-API-Key: 41604da2-da3f-462a-bac1-3c4c0419f40e
# Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIzZWVjODY1OS03MWJmLTg1YTQ
# tMmQxNi0yY2ZlMGE4Nzc4YmQiLCJib2R5SGFzaCI6IjQyYjcxZTBhYmY0NTliNzQ4MTNmMzAwNWRjMGIwNmUzMWNjYmI1M
# 2RmMWI1YThkZTEyZGRlYzE2Mzk0ODMxODUiLCJ1cmkiOiIvdjEvY29udHJvbGxlci9hcGkvb2JqZWN0Iiwibm9uY2UiOjQ
# xNzAyOTQ0Njg4MzM4OTY2MiwiZXhwIjoxNjQ2NDgzMjk4LCJpYXQiOjE2NDY0ODMyNDN9.oDNYpgp2XdCc5uqlcthHM5lI
# Xm59Q5razTOm90AjdSzrhZv5Xab-8IMUO6GOPdioswZO4L14byRGo-mAQzivne-EROUSm9YOQMwBh6c4MHXkQCKA-U0cre
# G7hViyoSygFHsgBaPioNCBCTLSY8Xv-f3Ftz5EIZ9kd6NwThCMt0UC-GepD2QDdSutNJrNGlF-K3juOTYj3zcLxHJgbQk2
# LuwkWz0qp7kADmWmqTJNyEZaTCm16U3sS1GoT-gE_41f8W__KLdoLH3PPtZR2f6YhDBNix4P1Fb5E8xenQXTwI_eJzRi_w
# jpLmYnrtzxNxFsy4fXnRFUyRezwHVXlqdv1PbH_FaqBuKlM2u6_MqZ4h2-Ys1NTxbHU7kGqGbGw2L_xIu5A6J__9z3Q447
# uHjaIioBNUutBh_tYxFkBlL8RtKtwC3YyHim4vbyP_-C5ZmJiETx69vHW3KqdGXetRBYYjgSRW5lMJnqjuC2xRy3Dc0kRk
# yhe241kCCh3MBXjcJDFtFmbWInrvb_h5kzrKjZEc__pbyGDAlW-NmhmdF11obi8-j_Np2y93VF9xcA3F13m38z1wpEqd1k
# GG2W9-n_zgetLV4qsTK5lgzuVhlKzDiGpiydrphz2ZdiZj3YzC2w1bwL4v_fHmqnXBNyjCnNkHzi7q2vo7W67e6SZB9zTfs
# Content-Length: 138
# {"id":"someid","status":"APPROVED","address":"address","tag":"s"}
# {"typ":"JWT","alg":"RS256"}.{"sub":"3eec8659-71bf-85a4-2d16-2cfe0a8778bd","bodyHash":
# "42b71e0abf459b74813f3005dc0b06e31ccbb53df1b5a8de12ddec1639483185","uri":
# "/v1/controller/api/object","nonce":417029446883389662,"exp":1646483298,"iat":1646483243}.
# oDNYpgp2XdCc5uqlcthHM5lIXm59Q5razTOm90AjdSzrhZv5Xab-8IMUO6GOPdioswZO4L14byRGo-mAQzivne-
# EROUSm9YOQMwBh6c4MHXkQCKA-U0creG7hViyoSygFHsgBaPioNCBCTLSY8Xv-f3Ftz5EIZ9kd6NwThCMt0UC-GepD
# 2QDdSutNJrNGlF-K3juOTYj3zcLxHJgbQk2LuwkWz0qp7kADmWmqTJNyEZaTCm16U3sS1GoT-gE_41f8W__KLdoLH3
# PPtZR2f6YhDBNix4P1Fb5E8xenQXTwI_eJzRi_wjpLmYnrtzxNxFsy4fXnRFUyRezwHVXlqdv1PbH_FaqBuKlM2u6_
# MqZ4h2-Ys1NTxbHU7kGqGbGw2L_xIu5A6J__9z3Q447uHjaIioBNUutBh_tYxFkBlL8RtKtwC3YyHim4vbyP_-C5Zm
# JiETx69vHW3KqdGXetRBYYjgSRW5lMJnqjuC2xRy3Dc0kRkyhe241kCCh3MBXjcJDFtFmbWInrvb_h5kzrKjZEc__p
# byGDAlW-NmhmdF11obi8-j_Np2y93VF9xcA3F13m38z1wpEqd1kGG2W9-n_zgetLV4qsTK5lgzuVhlKzDiGpiydrph
# z2ZdiZj3YzC2w1bwL4v_fHmqnXBNyjCnNkHzi7q2vo7W67e6SZB9zTfs
I wouldn’t recommend using external python libraries. Instead, either use Java classes or copy relevant Python libs code snippets into your code.
Install the latest python 2.7 with pip, install the needed package using PIP, and then copy the library folder (Python27\Lib\site-packages\jwt) to the Lib folder loaded by Jython.
Have fun!