通过有状态的Swagger模糊测试进行漏洞发现
项目描述
fuzz-lightyear
fuzz-lightyear是一个受pytest启发的,DAST框架,能够通过有状态的Swagger模糊测试在分布式、微服务生态系统中识别漏洞。
有状态模糊测试有什么特别之处?
传统的模糊测试基于这样的假设:命令调用失败表明存在漏洞。这种方法不适用于Web服务模糊测试,因为失败在不良输入下是预期的 -- 事实上,带有故意恶意负载的成功请求更加危险,应该相应地捕捉。
有状态模糊测试允许我们这样做。通过在请求之间保持状态,我们可以组装一个请求序列,并构建它来模拟恶意攻击向量并发出意外成功的警报。使用假设测试,我们能够动态生成这些测试案例,以便我们可以继续发现新的向量。最后,当我们找到错误时,这个测试框架会输出一系列cURL命令,以便于重现。
示例
$ fuzz-lightyear https://petstore.swagger.io/v2/swagger.json -v --ignore-exceptions
安装
pip install fuzz-lightyear
用法
$ fuzz-lightyear -h
usage: fuzz-lightyear [-h] [-v] [--version] [-n [ITERATIONS]] [--schema SCHEMA]
[-f FIXTURE] [--seed SEED] [-t TEST] [--ignore-exceptions]
[--disable-unicode]
url
positional arguments:
url URL of server to fuzz.
optional arguments:
-h, --help show this help message and exit
-v, --verbose Increase the verbosity of logging.
--version Displays version information.
-n [ITERATIONS], --iterations [ITERATIONS]
Maximum request sequence length to fuzz.
--schema SCHEMA Path to local swagger schema. If provided, this
overrides theswagger file found at the URL.
-f FIXTURE, --fixture FIXTURE
Path to custom specified fixtures.
--seed SEED Specify seed for generation of random output.
-t TEST, --test TEST Specifies a single test to run.
--ignore-exceptions Ignores all exceptions raised during fuzzing (aka.
only fails when vulnerabilities are found).
--disable-unicode Disable unicode characters in fuzzing, only use ASCII.
--depth DEPTH Maximum depth for generating nested fuzz parameters.
固定装置
固定装置是 fuzz-lightyear
的核心组件,允许您自定义工厂来补充对各种端点的模糊测试努力。这对于微服务生态系统至关重要,因为服务本身可能不是 CRUD 应用程序。这意味着作为请求序列的一部分创建临时资源的端点可能不会在 Swagger 规范中可用。
为了解决这个问题,我们允许开发人员提供必要的自定义命令,以填充模糊请求参数的某些部分。
示例
假设我们有以下 Swagger 片段
paths:
/biz_user/{userID}/invoices:
get:
tags:
- business
operationId: "get_business_by_id"
parameters:
- name: "userID"
in: "path"
required: true
type: integer
responses:
200:
description: "success"
403:
description: "forbidden"
404:
description: "business not found"
我们需要一个有效的 userID
来访问其发票。显然,fuzzer 为 userID
放置随机值将是浪费时间,因为我们不关心攻击者是否尝试访问不存在的业务。此外,此服务不了解如何创建业务(以获取有效的 userID
),因此 fuzzer 在测试此端点时将不会有效。
为了解决这个问题,我们定义了一个固定装置来告诉 fuzz-lightyear
如何处理此类情况。
# fixtures.py
import fuzz_lightyear
@fuzz_lightyear.register_factory('userID')
def create_biz_user_id():
return 1
现在,当 fuzz-lightyear
尝试模糊 /biz_user/{userID}/invoices
时,它将识别有一个用户定义的 userID
工厂,并在模糊测试中使用其值。
$ fuzz-lightyear -f fixtures.py http://localhost:5000/schema -v
================================== fuzzing session starts ==================================
Hypothesis Seed: 152367346948224061420843471695694220247
business E
====================================== Test Failures =======================================
_________________________ business.get_business_by_id [IDORPlugin] _________________________
Request Sequence:
[
"curl -X GET http://localhost:5000/biz_user/1/invoices"
]
================================== 1 failed in 1.2 seconds =================================
我们可以通过在 create_business
函数中指定自定义方法来修改此示例,以创建业务。
嵌套固定装置
按照上述示例,假设您在创建 biz_user 之前需要先创建一个业务。我们可以通过以下方法完成此操作
# fixtures.py
import fuzz_lightyear
@fuzz_lightyear.register_factory('userID')
def create_biz_user_id(businessID):
return businessID + 1
@fuzz_lightyear.register_factory('businessID')
def create_business():
return 1
然后,
$ fuzz-lightyear -f fixtures.py http://localhost:5000/schema -v
================================== fuzzing session starts ==================================
Hypothesis Seed: 152367346948224061420843471695694220247
business E
====================================== Test Failures =======================================
_________________________ business.get_business_by_id [IDORPlugin] _________________________
Request Sequence:
[
"curl -X GET http://localhost:5000/biz_user/2/invoices"
]
================================== 1 failed in 1.2 seconds =================================
我们还可以通过使用 类型注解 来进行嵌套固定装置的类型转换。
# fixtures.py
import fuzz_lightyear
@fuzz_lightyear.register_factory('userID')
def create_biz_user_id(businessID: str):
return businessID + 'a'
@fuzz_lightyear.register_factory('businessID')
def create_business():
return 1
这将产生
$ fuzz-lightyear -f fixtures.py http://localhost:5000/schema -v
================================== fuzzing session starts ==================================
Hypothesis Seed: 152367346948224061420843471695694220247
business E
====================================== Test Failures =======================================
_________________________ business.get_business_by_id [IDORPlugin] _________________________
Request Sequence:
[
"curl -X GET http://localhost:5000/biz_user/1a/invoices"
]
================================== 1 failed in 1.2 seconds =================================
特定端点固定装置
假设我们还有一个需要不同类型 userID
的端点 get_user_by_id
。我们无法使用现有的 userID
固定装置,因为它生成错误的 ID 类型。我们可以通过编写特定端点固定装置来解决这个问题。
# fixtures.py
import fuzz_lightyear
@fuzz_lightyear.register_factory('userID')
def create_biz_user_id(businessID):
return businessID + 1
@fuzz_lightyear.register_factory('userID', endpoint_ids=['get_user_by_id'])
def create_user_id():
return 'foo'
@fuzz_lightyear.register_factory('businessID')
def create_business():
return 1
这将产生
...
_________________________ user.get_user_by_id [IDORPlugin] _________________________
Request Sequence:
[
"curl -X GET http://localhost:5000/user/foo"
]
================================== 1 failed in 1.2 seconds =================================
我们还可以将其与嵌套固定装置结合使用,但如果我们在固定装置中指定端点,则依赖于该固定装置的每个固定装置也必须指定端点。
# fixtures.py
import fuzz_lightyear
# We have to specify get_business_by_id here!
@fuzz_lightyear.register_factory('userID', endpoint_ids=['get_business_by_id'])
def create_biz_user_id(businessID):
return businessID + 1
@fuzz_lightyear.register_factory('businessID', endpoint_ids=['get_business_by_id'])
def create_business():
return 1
这将产生
...
_________________________ user.get_user_by_id [IDORPlugin] _________________________
Request Sequence:
[
"curl -X GET http://localhost:5000/biz_user/2/invoices"
]
================================== 1 failed in 1.2 seconds =================================
身份验证固定装置
我们可以使用固定装置来指定 Swagger 规范中的身份验证/授权方法。这允许开发人员根据个别用例自定义会话 cookie 或 API 令牌的使用。
这些固定装置对于 IDORPlugin
是必需的。我们可以在固定装置中包含一个 operation_id
参数,以便自动传递操作 ID。其他参数将不会被模糊。
"""
These values are passed into the configured request method as keyword arguments.
Check out https://bravado.readthedocs.io/en/stable/advanced.html#adding-request-headers
for more info.
"""
import fuzz_lightyear
@fuzz_lightyear.victim_account
def victim_factory():
return {
'_request_options': {
'headers': {
'session': 'victim_session_id',
},
}
}
@fuzz_lightyear.attacker_account
def attacker_factory():
return {
'_request_options': {
'headers': {
'session': 'attacker_session_id',
}
}
}
设置固定装置
我们可以使用设置固定装置来指定希望在所有测试运行之前运行的代码。这允许开发人员设置任何自定义配置或测试应用程序所依赖的外部应用程序。
import fuzz_lightyear
@fuzz_lightyear.setup
def setup_function():
print("This code will be executed before any tests are run")
包括和排除 Swagger 标签和操作
我们可以使用固定装置来控制是否让 fuzz-lightyear
模糊 Swagger 规范的某些部分。这允许开发人员仅模糊测试环境中可以模糊的规范部分。
import fuzz_lightyear
@fuzz_lightyear.include.tags
def get_tags_to_fuzz():
"""fuzz_lightyear will only fuzz operations from
these tags.
"""
return ['user', 'transactions']
@fuzz_lightyear.exclude.operations
def get_operations_to_exclude():
"""fuzz_lightyear will not call these Swagger
operations.
"""
return [
'get_user_id',
'operation_doesnt_work_in_test_environment',
]
@fuzz_lightyear.exclude.non_vulnerable_operations
def get_non_vulnerable_operations():
"""fuzz_lightyear will not check these Swagger
operations for vulnerabilities.
This is different from `fuzz_lightyear.exclude.operations`
in that these operations can still be executed by the
fuzzer to generate request sequences, but the vulnerability
plugins will not verify that these operations are secure.
"""
# Accessing a user's public profile shouldn't require
# authentication.
return ['get_user_public_profile']
模糊后挂钩
有时,工厂固定装置和随机模糊测试不足以构建一个有效的请求。例如,API 可能有一个未声明的必需头,将头添加到 Swagger 规范中是不切实际的。在这种情况下,我们可以使用模糊后挂钩将模糊数据转换为有效格式。
@fuzz_lightyear.hooks.post_fuzz(
tags='user',
operations='some_function',
rerun=True,
)
def apply_nonce(
operation: bravado.client.CallableOperation,
fuzzed_data: Dict[str, Any],
) -> None:
"""This hook creates and adds a nonce to any request against
operations with the 'user' tag, and additionally to the
'some_function' operation.
In addition, this nonce cannot be reused by a fuzz-lightyear
request object, so we mark this hook is needing to be `rerun`.
"""
nonce = make_nonce()
fuzzed_data['nonce'] = nonce
注意:这些挂钩运行的顺序没有保证。
项目详情
下载文件
下载适用于您平台的应用程序文件。如果您不确定选择哪一个,请了解更多关于安装包的信息。