· Tutorials · 6 min read
Writing BChecks for BurpSuite
Extending BurpSuite functionality to find more vulnerabilities

Intro
Last year (in release 2023.10.2
), the PortSwigger introduced a new method of scanning automation in BurpSuite - BChecks. It is a simple scripting language that allows users to create their own custom security checks, which will be performed during the scanning. Now, in some cases, instead of writing a new BApp extension, it is much quicker to utilize the BChecks functionality and do the automation or simply extend a list of detected vulnerabilities by the BurpSuite.
For demonstration purposes, let’s write our own script that will utilize the HTTP headers (e.g. X-Forwarded-For: 127.0.0.1
) in attempt to bypass restrictions imposed by some webservers. It is a trivial method which may trick some webserver to process the specified IP address instead of the real one, which could lead to authorization or rate limiting restrictions bypasses.
Anatomy of BCheck
Each BCheck is defined as a plain text file with a .bcheck
file extension with padding similar to the .yaml
format. Each file definition must start with a metadata
object, and have exactly one given...then
statement.
Metadata
Contains information about the check itself. This object is mandatory for all BChecks and must be placed at the very start of the definition.
metadata: language: v2-beta name: "403/429 bypass using HTTP Header" description: "Attempts to bypass 403/429 using the HTTP headers with local IP address" author: "ShadowSurface" tags: "bypass"
Control flow
These keywords control the flow of execution for the definition. For example, define
section is optional and declares variables with inner scope. The run for each
declares an array variable that can be iterated over. When this variable is called the check runs once for each item in the array.
define: bypass_ip = "127.0.0.1"
run for each: bypass_header = "Forwarded", "Via", "X-Client-IP",14 collapsed lines
"X-Forwarded-For", "X-Forwarded-Host", "X-Forwarded-Proto", "X-Forwarded-Server", "X-Forward-For", "X-Forwared-Host", "X-Host", "X-Originating-IP", "X-Real-IP", "X-Remote-Addr", "X-Remote-IP", "X-Requested-By", "X-Requested-For", "X-Trusted-IP"
It is possible to declare an array only in run for each
section, and not in the define
.
Note: you can define multiple arrays in this section and the script will iterate over each of them.
Now we need to instruct the scanner to when it should run our check. As mentioned before, each BCheck must have a single given...then
statement containing either one of the following:
Keyword | Description |
---|---|
given response then | The check runs once for each response audited. |
given request then | The check runs once for each request audited. |
given host then | The check runs once for each host audited. |
given path then | The check runs once for each path audited. |
given [any|query|header|body|cookie] insertion point then | The check runs once insertion point is audited. |
We will not use the given response then
keyword as it is meant only to be used for passive checks, and it will not allow us to send the request in attempt to bypass the restriction. Instead, we will choose the given request then
keyword to audit the base response when the request is sent, and send a new one, as it is not impose such limitations.
Conditionals
Conditional keywords control actions that happen as a result of a set condition and can be only used inside a given...then
statement.
BChecks support a range of conditions, making matching and comparing any part of the requests/responses an easy task.
given request then if {base.response.status_code} matches "(403|429)" then # [perform action] end if
Note: The base
keyword represents the request sent and response received by Burp Scanner for the specified scan mode during crawling, while the latest
keyword refers to the most recent request/response pair for this scan mode. More details about these objects are described in the “Reserved variables” section of the documentation.
Actions
This part of the BCheck will prompt Burp Scanner to perform a particular action. The actions can be
send request
send request (raw)
send payload
report issue
report issue and continue
We will select the send request
action to send the request with our specified information, which, in this case, is a new header appended to it. If there is a difference between the new response and the previous one, we will instruct BurpSuite to mark it as a potential issue.
given request then if {base.response.status_code} matches "(403|429)" then send request: replacing headers: {bypass_header}: {bypass_ip}
if not( {latest.response.status_code} is {base.response.status_code} ) then # [report issue] end if end if
Reporting the issue
To report the issue we need to initialize the report issue
object. Each of these objects consists of: name (will be taken from metadata if absent), severity (ranges from info to high), confidence*, details about the issue, and remediation advice.
*Reporting confidence ranges:
Confidence | Description |
---|---|
tentative | The issue is potentially present but there is a high chance that this could be a false positive. |
firm | The issue is probably present, but this could be a false positive. |
certain | The issue is definitely present. |
report issue: severity: high confidence: tentative detail: `Potential {base.response.status_code} bypass using {bypass_header} header.` remediation: `Avoid using {bypass_header} for authorization or rate limiting.`
Full script
By combining all the previous sections, we end up with the following BCheck script:
metadata: language: v2-beta name: "403/429 bypass using HTTP Header" description: "Attempts to bypass 403/429 using the HTTP headers with local IP address" author: "ShadowSurface" tags: "bypass"
define: bypass_ip = "127.0.0.1"
run for each: bypass_header = "Forwarded", "Via", "X-Client-IP",14 collapsed lines
"X-Forwarded-For", "X-Forwarded-Host", "X-Forwarded-Proto", "X-Forwarded-Server", "X-Forward-For", "X-Forwared-Host", "X-Host", "X-Originating-IP", "X-Real-IP", "X-Remote-Addr", "X-Remote-IP", "X-Requested-By", "X-Requested-For", "X-Trusted-IP"
given request then if {base.response.status_code} matches "(403|429)" then send request: replacing headers: {bypass_header}: {bypass_ip}
if not( {latest.response.status_code} is {base.response.status_code} ) then report issue: severity: high confidence: tentative detail: `Potential {base.response.status_code} bypass using {bypass_header} header.` remediation: `Avoid using {bypass_header} for authorization or rate limiting.` end if end if
Running BCheck
To quickly test your new check against the target, follow these steps:
Navigate to
Target -> Site map -> Site map filter
and enable the display of requests with4xx
status codes.Now you will see requests that returned
403/429
responses.Right-click the target host and select
Scan
. In theScan type
menu, chooseAudit selected items
.In the
Scan configuration
menu, clickSelect from library
and chooseAudit checks - BChecks only
. This ensures that only BChecks will run, saving you some time.(⚠️) When scanning requests that returned
429
responses, Burp Suite may throttle the scan due to the defaultResource pool
configuration.To significantly improve scan speed, create a new resource pool without throttling.
Start the scan and view the
All issues
section to see if any vulnerabilities were identified.
Wrap-up
BCheks is a simple scripting language with some current limitations and yet massive potential. It provides a convenient interface to analyze requests/responses, interact with BupSuite Collaborator, and send HTTP requests.
We have already seen the popularity of the engines that handle community-written scripts, such as nuclei templates. It is indeed noteworthy to observe PortSwigger’s entry into this arena.
By leveraging the BurpSuite platform and extending it’s capabilities, it may become day-to-day tool in the arsenal of the web security researchers.