BChecks是什么?
BChecks可以创建和导入的自定义扫描检查。Burp Scanner在执行其内置扫描例程的同时运行这些检查,帮助您定位扫描并使测试工作流尽可能高效。
每个BCheck都定义为一个以.bcheck文件扩展名结尾的纯文本文件。这些文件使用自定义语言来指定检查的行为。
为什么用BChecks?
BurpSuite 自带的扫描引擎只能扫描某种类型的漏洞,如SQL注入,XSS,没有对特定漏洞,如某OA漏洞,shiro反序列化漏洞等漏洞的扫描,当我们要扫描特定漏洞时,需要用Java编写burpsuite插件,门槛较高,而且不同的漏洞往往有不同的作者编写,需要加载不同的插件,比较麻烦。同时我们往往需要使用外部的扫描器来补充对特定漏洞的检测,如用 xray 等扫描器。但BCheck 的出现,将改变这一现状,使 burpsuite可以直接加载各种漏洞的 poc,获得对特定漏洞的强大检测能力。 理想情况下,只要BCheck脚本足够多,使用burpsuite 就可以检测几乎所有Web漏洞,不需要额外的Web扫描器。
如何使用 BCheck?
要使用BCheck 脚本,你需要安装 Burp Suite 2023.7.1 以及更新的版本。然后插件处导入 BCheck 脚本,现在官方维护着一个 BCheck 脚本的仓库,地址如下:
https://github.com/PortSwigger/BChecks
本次演示使用 Burp Suite 2023.10.2
在 Extensions -> BChecks 处点击 Import ,导入 .bcheck 后缀的 bcheck 脚本。
导入后把 bcheck 脚本勾上就可以在扫描时调用了。
接着在想扫描的请求上右键,新建扫描
我们也可以在新建扫描时,只调用 bcheck 扫描,具体操作如下:
在扫描设置处点击 select from library
选择 BChecks only,即可只调用BCheck
扫描,不过在用默认的扫描配置时,会默认调用BCheck,并不用特意选择。
在Dashborad面板处可以看到已经扫描出一个 Shiro 漏洞了。
如何编写BCheck脚本?
怎样编写BCheck脚本呢?可以先看看 BCheck 的定义文档:
https://portswigger.net/burp/documentation/scanner/bchecks/bcheck-definition-reference
还可以看看官方的一些例子脚本,从而快速上手:
https://github.com/PortSwigger/BChecks/tree/main/examples
下面是根据文档翻译的定义,如果想直接看编写实操,可以跳过这段
BCheck 定义
Metadata 元数据
包含有关检查本身的信息。对于所有BChecks,必须将元数据对象放置在定义的开始。
metadata:
language: v1-beta
name: "Simple SQLi"
description: "Tests insertion points for basic SQLi"
author: "Peter Wiener"
tags: "sql", "active", "sql-injection"
属性 | 描述 | 类型 |
---|---|---|
language* | BCheck 的语言版本。 | enum [v1-beta] |
name* | BCheck 的名字. | 当BCheck 报告一个问题时,这将显示在Burp Suite UI中。 |
String | description | BCheck 的描述。 |
String | author | 作者名 |
String | tags | 标签名 |
Control flow 控制流
这些关键字控制定义的执行流程。
run for each
声明一个可以迭代的数组变量。当变量称为检查时,该数组中的每个项目一次运行一次。声明的变量具有外部范围。
run for each 关键字的运行是可选的。如果使用,则必须将其放置在given…then 之前,然后在定义中进行陈述。
run for each:
variable_name = "variable value 1", "variable value 2", etc.
例子
该示例声明一个称为 url_array 的变量,列出了三个URL。如果您正在编写需要针对多个URL进行相同的测试(例如,依次添加作为 Origin 头来检测 CORS 错误配置)。
run for each:
url_array =
null,
"http://example.com",
`https://{random_str(5)}{base.response.url}`
define 声明
声明具有内部作用域的变量。define关键字是可选的。如果使用,你必须把它放在given…then 语句的前面。
define:
variable_name = "variable_content"
例子
该示例调用generate_collaborator_address()函数生成新的Burp collaborator地址,并将其存储在名为collaborator_address的变量中。
define:
collaborator_address = {generate_collaborator_address()}
given…then
根据被检测的内容,定义检查何时运行。
given [response|request|host] | [ [any|query|header|body|cookie]* + insertion point] then
每个 BCheck 必须有一个 given / then 语句,其中包含:
以下任一情况:
- given response then - 针对每一个检查的响应运行一次。
- given request then - 针对每一个检查的请求运行一次。
- given host then - 针对每一个检查的主机运行一次。
或者以下插入点关键字之一: - given any insertion point then - 检查针对审核的每个插入点(任何类型)运行一次。如果您未指定插入点类型(即使用给定的插入点),Burp Scanner 也会使用此默认选项。
- given query insertion point then - 该检查针对每个审核的查询运行一次。
- given header insertion point then - 该检查针对每个审核的 http 头运行一次。
- given body insertion point then - 该检查针对审核的每组http body 内容运行一次。
- given cookie insertion point then - 该检查针对每个审核的 cookie 运行一次。
可以使用 or 关键字来组合插入点。例如:
- given query or body insertion point then - 该检查针对审核的每个查询或一组 body 内容运行一次。
- given header or cookie insertion point then - 该检查针对每个审核的 http 头或 cookie 运行一次。
Conditionals 条件语句
这些关键字控制由于设定条件而发生的操作。只能在给定given…then 语句中使用条件关键字。
if… then
如果满足设定条件则执行操作。需要以下内容:
一个条件
一个 操作
一个end if 关键词.
例子
如果最新响应包含 Apache Struts 版本号,则报告问题:
if {latest.response} matches "Apache Struts [\d\s\.]+" then
report issue:
severity: info
confidence: certain
detail: "Disclosed Apache Server"
remediation: "Do not reveal error messages"
end if
else if… then
在 if 语句中使用。如果前面的条件为假且 else if 条件为真,则执行操作。您可以在 BCheck 中使用多个 else if 语句。
if [condition 1] then
[action 1]
else if [condition 2] then
[action 2]
end if
例子
如果检查结果表明Collaborator 收到 DNS 交互,则坚定地报告问题。否则,检查协作者是否已收到任何 SMTP 交互。如果有,请暂时放心地报告问题。
if dns interactions then
report issue:
severity: high
confidence: firm
else if smtp interactions then
report issue:
severity: high
confidence: tentative
end if
else then
在 if 语句中使用。如果该语句的条件和任何前面的 else/if 语句的条件不满足,则执行操作。
if [condition] then
[action]
else then
[action 2]
end if
例子
如果最新响应与基本响应不同,但具有相同的 HTTP 状态代码,请坚定地报告问题。否则,请暂时放心地报告问题。
if {latest.response} differs from {base.response} and {latest.response.status_code} is {base.response.status_code} then
report issue:
severity: high
confidence: firm
else then
report issue:
severity: high
confidence: tentative
end if
end if
表示 if 语句的结束。
if [condition] then
[action]
end if
Conditions 条件
您可以在 if 语句中使用以下条件:
- {X} matches “[regex]” - 如果 X 与提供的正则表达式匹配,则为 True
- {X} differs from {Y} - 如果响应 X 与响应 Y 不同,则为 True。
- {X} in {Y} - 如果 Y 包含 X,则为 True(例如,Y 可以是响应,X 可以是要在该响应中搜索的标头)。
- {X} is {Y} - 如果提供的参数相同,则为 True。
- any interactions - 如果 Burp Collaborator 已收到任何交互,则为 True。
- dns interactions - 如果 Burp Collaborator 已收到任何 DNS 交互,则为 true。
- http interactions - 如果 Burp Collaborator 已收到任何 HTTP 交互,则为 true。
- smtp interactions - 如果 Burp Collaborator 已收到任何 SMTP 交互,则为 True。
您还可以组合和修改条件:
- {condition} and {condition} - 如果两个条件都为真,则为真。
- {condition} or {condition} - 如果一个或两个条件为真,则为 True。
- not({condition}) - 如果条件不为真则为真。
Brackets 括号
您可以在条件中使用括号来优化检查的逻辑流程。例如,A 或 B 和 C 创建一个条件,如果满足以下条件之一,则该条件的计算结果为 true:
A is true.
Both B and C are true.
Actions 动作
这些关键字提示 Burp Scanner 执行特定操作。
其中一些关键字只能与某些插入点结合使用。有关定义在哪些插入点上使用检查逻辑的更多信息,请参阅 given…then。
send request
发送包含指定信息的请求。可与除给定响应之外的所有扫描检查模式一起使用。
注意:如果未提供操作关键字,则所有请求属性默认为替换。例如:
headers:
"name_of_header1": "value of header1",
"name_of_header2": "value of header2"
与下面是相同的
replacing headers:
"name_of_header1": "value of header1",
"name_of_header2": "value of header2"
send request [called request_name]:
[appending|replacing] headers:
"name_of_header1": "value of header1",
"name_of_header2": "value of header2"
removing headers:
"name_of_header1",
"name_of_header2"
[appending|replacing] queries:
"name_of_query_1=http://portswigger.net",
"name_of_query_2=https://portswigger.net"
removing queries:
"name_of_query_1",
"name_of_query_2"
[appending|replacing] path: "/test"
removing path
[appending|replacing] method: "GET"
removing method
[appending|replacing] body: "contents of body"
removing body
关键词 | 描述 | 例子 |
---|---|---|
called(string) | 为请求命名,以便稍后在检查中引用 | “Example request” |
appending headers:(1-n name:value pairs) | 将指定的标头添加到请求中。如果请求中已存在同名标头,则添加具有新值的第二个标头。您可以列出多个标头/值对 | “Origin”: “http://example.com” |
removing headers:(1-n header names) | 从请求中删除指定的标头。您可以列出多个标头名称 | “Origin” |
appending queries:(1-n query names and values) | 将指定的查询参数添加到请求 URL。如果请求中已存在同名查询,则会添加具有新值的第二个标头。您可以列出多个查询 | “title=example_query” |
replacing queries:(1-n query names and values) | 将指定查询参数的值替换为新值。如果没有具有指定名称的查询,则会附加一个新查询。您可以列出多个查询 | “title=example_query” |
removing queries:(1-n query names) | 从请求中删除指定的查询参数。您可以列出多个查询 | “title” |
appending path:(string) | 将指定的路径参数添加到请求 URL | “/admin” |
replacing path:(string) | 将 URL 中的路径参数替换为指定值。保留所有查询参数 | “/admin” |
removing path | 从请求中删除所有路径参数。 | |
appending method:(string) | 将指定的 HTTP 方法添加到请求中 | “GET” |
replacing method:(string) | 将请求方法替换为指定值 | “GET” |
removing method | 删除请求方法 | |
appending body:(string) | 将指定的正文内容附加到请求中 | “body content” |
replacing body:(string) | 将请求体替换为指定内容 | “body content” |
removing body | 删除请求正文 |
send request (raw)
将包含指定信息的请求作为原始 HTTP 请求发送。可与除given response之外的所有扫描检查模式一起使用。
例子
send request [called request_name]:
"GET /catalog/product?productId=2 HTTP/2
Host: ginandjuice.shop
Cookie: session=qwertyuiop
Content-Length: 2"
send payload
发送附加或替换指定负载的新请求。只能与 given insertion point一起使用。
send payload [called payload_name]:
appending: "payload"
replacing: "payload"
属性 | 描述 | 类型 |
---|---|---|
appending: | 将指定的负载附加到请求的负载集中。 | 字符串/变量 |
replacing: | 将请求的现有负载集替换为指定的负载。 | 字符串/变量 |
report issue
导致 Burp Scanner 报告问题。BChecks 报告的所有问题都具有声明性扫描检查生成的问题类型。问题的显示名称取自 BCheck 的名称元数据属性。
report issue:
severity:
confidence:
remediation:
detail:
属性 | 描述 | 类型 |
---|---|---|
severity* | 问题的严重性 | [info |
confidence* | 问题存在的置信度 | [certain |
remediation | 补救建议 | String |
detail | 有关该问题的更多信息 | String |
报告问题时控制流程
report issue 操作会终止 BCheck,即使检查的某些部分尚未运行。您在设计 BCheck 时应该考虑这一点。
例如,考虑以下 BCheck:
given host:
send request:
method: `GET`
headers: `Host`: `{collaborator_address}`
if dns interactions then
report issue:
severity: info
confidence: certain
end if
if http interactions then
report issue:
severity: high
confidence: certain
end if
此检查首先查找 DNS,然后查找由 Burp Collaborator 负载产生的 HTTP 交互。但是,如果检查发现 DNS 交互,则它会执行第一个报告问题操作,并且随后不会报告任何 HTTP 交互,即使存在 HTTP 交互也是如此。
Reserved variables 保留变量
这些关键字引用特定的请求或响应属性。
注意:确保在命名自己的变量时不要使用这些保留的属性名称,因为这会导致 BCheck 运行时出错。
request / response
这些关键字引用特定的请求或响应属性。您可以参考:
- base - Burp Scanner 在抓取过程中针对指定扫描模式发送的请求和接收的响应。
- latest - 此扫描模式的最新请求/响应对。
- {request_name} - 特定的请求/响应对,使用 Called 关键字命名。
base|latest|{request_name} {
response {
status_code
headers
body
http_version
}
request {
method
url {
protocol
host
port
path
file
query
}
http_version
headers
body
}
}
Response
属性 | 描述 |
---|---|
response | 返回响应的全部内容。 |
response.status_code | 返回响应的 HTTP 状态代码。 |
response.headers | 返回响应头的列表。 |
response.body | 返回响应的正文内容。 |
response.http_version | 返回响应中使用的 HTTP 版本。 |
Request
属性 | 描述 |
---|---|
request | 返回请求的全部内容。 |
request.method | 返回请求中使用的 HTTP 方法。 |
request.url | 返回请求发送到的 URL。 |
request.url.protocol | 返回请求 URL 的协议。 |
request.url.host | 返回请求发送到的主机。 |
request.url.port | 返回发送请求所通过的端口。 |
request.url.path | 返回请求 URL 路径。 |
request.url.file | 返回 URL 文件(即 URL 中初始斜杠之后的部分)。 |
request.url.query | 返回请求 URL 中使用的查询字符串。 |
request.http_version | 返回请求中使用的 HTTP 版本。 |
request.headers | 返回请求头列表。 |
request.body | 返回请求的正文内容。 |
Functions 函数
这些关键字在调用时执行特定任务。
请注意,所有 BCheck 函数都用大括号 {} 表示。
random_str
{random_str(int)} 返回指定长度的随机字符串。长度必须介于 0 和 9,999,999 之间。输入 0 将返回空字符串。
Example
{random_str(13)} // "qpald95nbjkig"
regex_replace
regex_replace(String source, String regex, String replacement) 获取源字符串并用替换字符串替换与指定正则表达式模式匹配的任何内容。
Example
regex_replace ({var}, "[a-z]*@", "abc@")
在此示例中,假设 {var} 的值为“xyz@portswigger.net”。正则表达式模式匹配电子邮件地址的第一部分,并将其替换为替换字符串,以创建 abc@portswigger.net 的最终输出。
to_lower
{to_lower(string)}
将指定字符串转换为小写。
Example
{to_lower("Carlos MONTOYA")} // carlos montoya
to_upper
{to_upper(string)}
将指定字符串转换为大写。
Example
{to_lower("Peter weiner")} // PETER WEINER
base64_encode
{base64_encode(string)}
使用 base64 对指定字符串进行编码。
Example
hello world // aGVsbG8gd29ybGQ=
base64_decode
{base64_decode(string)}
从 base64 解码指定的字符串。
Example
hello world // aGVsbG8gd29ybGQ=
generate_collaborator_address
{generate_collaborator_address}
生成一个新的 Burp Collaborator 地址以在请求中使用。您可以通过多次调用 {generate_collaborator_address} 在同一个 BCheck 中生成多个地址。
SHA1
sha1(String)
使用 SHA1 哈希函数对提供的输入进行哈希处理。
Example
sha1("Hello") // aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
SHA256
sha256(String)
使用 SHA256 哈希函数对提供的输入进行哈希处理。
Example
sha256("Hello") // 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
MD5
md5(String)
使用 MD5 哈希函数对提供的输入进行哈希处理。
Example
MD5("Hello") // 5d41402abc4b2a76b9719d911017c592
组合函数
如果需要,可以嵌套这些函数。例如,以下函数将变量转换为小写,然后通过一个操作将其编码为 Base64:
{base64_encode(to_lower(my_variable))}
注意:确保在命名变量时不要使用这些函数名称,因为这会导致 BCheck 运行时出错。
杂项
Strings
使用双引号 (“”) 表示文字字符串 - “delete carlos”
使用反引号 (``) 表示可插值字符串 - `https://{random_str(5)}.com`
处理多行字符串时,Burp Scanner 将行结尾替换为 \r\n 并修剪每行上的尾随和前导空格(左引号之后和右引号之前的空格除外)。
例如:
"__GET / HTTP/1.1__
___Host: portswigger.net_"
变成
"__GET / HTTP/1.1
Host: portswigger.net__"
要在可插值字符串中显式指定尾随/前导空格,请改用 \s 字符。
字符转义
在字符串中使用 \ 字符来转义字符。例如,{var} 被转义,但 {var} 打印 var 的值。
正则表达式
BChecks 支持 Java 风格的正则表达式。
“2[0-9][0-9]”
注释
使用 # 来表示注释行。
#Hello
特殊字符
这些字符可以用作可插值字符串的一部分。例如,“\s”被解释为空格,但文字字符串“\s”被解释为两个单独的字符。
\n - 新行
\r - 回车
\s - 空格
\t - 制表符
\b - 退格键
\f - 换页
BCheck 结构
为了创建有效的 BCheck,您需要遵循一些结构规则:
每个定义必须以 metadata 对象开始。.
每个定义必须有一个given… then 语句.
条件语句只能出现在 given… then 语句中.
任何define 或run for each 语句使用时必须放在metadata 块后面并在given… then 语句之前.
变量定义使用 run for each 具有外部作用域,而使用 define 声明的变量具有内部作用域. 这意味着您不能在run for each语句的后续运行中使用通过 Define 声明的变量。
bcheck定义介绍到这里结束了,下面进行 bcheck 编写实操教程
bcheck 编写实操教程
点击 New
选择从模板创建,这样可以省不少时间
点击相应模板,如想写一个被动扫描的插件,可以选择 response-level这个
下面编写一个被动检测使用了 shiro 框架的插件,代码如下:
metadata:
language: v1-beta
name: "Shiro Cookie Check Passive"
description: "Check for Shiro Vulnerability in Response"
author: "timeshatter"
tags: "Shiro,passive"
given response then
if "rememberMe=deleteMe" in {latest.response} then
report issue:
severity: info
confidence: certain
detail: "Shiro Vulnerability Detected: rememberMe=deleteMe"
remediation: "Investigate and fix the Shiro vulnerability immediately."
end if
第8行的 given response 为接收到请求响应时进行检测,也就是被动的检测,第9行处如果 rememberMe=deleteMe 在响应内容中,则报告检测到 shiro 框架。写完代码后,我们使用 vulhub的靶场来搭建个 shiro环境
https://github.com/vulhub/vulhub/tree/master/shiro/CVE-2016-4437
输入账号密码,勾选 Remember me,点击登录
可以看到响应中有 deleteMe
在这个请求上右键,选择 Send to BCheck editor
点击 Run test,可以看到在 issue activity 上已经有结果了
可以看到,已经成功检测到该漏洞了
没问题了,可以点Save&close保存成一个bcheck脚本。
接下来再写一个主动检测 shiro框架的脚本,代码如下:
metadata:
language: v1-beta
name: "Shiro Cookie Check Active"
description: "Detects Shiro Vulnerability by Manipulating Cookie"
author: "timeshatter"
tags: "Shiro,active"
define:
shiroExploit = "rememberMe=1"
issueDetail = "Shiro Cookie Exploit Detected: rememberMe=deleteMe"
issueRemediation = "Immediately investigate and fix the Shiro vulnerability."
given request then
send request called check:
replacing headers:
"Cookie": {shiroExploit}
if "rememberMe=deleteMe" in {check.response} then
report issue:
severity: info
confidence: certain
detail: {issueDetail}
remediation: {issueRemediation}
end if
第13行的 given request 为主动发送一个请求进行检测,也就是主动的检测,当我们对某个请求进行扫描时会调用,第16行处替换请求头的内容为rememberMe=1 ,18行处检测如果 rememberMe=deleteMe 在响应内容中,刚报告漏洞。
同样的,我们把一个正常的请求发送到 BCheck editor
成功发现漏洞
点击Logger可以看这次发送的检测请求,也可以用来调试是否有问题
BCheck脚本的编写教程就写到这里,下面是我写的一些检测 shiro和 spring漏洞的 bcheck脚本,在 github上:
https://github.com/QdghJ/burpsuite-bchecks
还有官方的BChecks库:
https://github.com/PortSwigger/BChecks
欢迎大家编写poc提交到我的仓库或者官方的仓库,丰富漏洞库,这样就不用再开额外的扫描器了,直接 burpsuite 一把梭。
虽然现在 BCheck 脚本刚出来不久,还有一些 bug,功能也不算很强大,但基本可以满足我们对漏洞检测,毕竟检测漏洞也就是发送请求,然后检测响应内容,相信在以后可以得到广泛应用。
本文章也在我的公众号发布