Black-box fuzzer that fuzzes APIs based on OpenAPI specification. All you need to do is to supply URL of the API and its specification. Find bugs for free!
The fuzzer has been used to find bugs in numerous software. Some of the well-known fuzzed software include1:
Kubernetes
The category of bugs differ, but some of the common are parsing bugs, invalid format bugs and querying non-existent entities. If you have found bugs with this fuzzer, please reach out to me. I would love to hear from you. Feel free to submit a PR and add your finding to the list above.
To build the fuzzer, you will need to have rust installed.
cargo install openapi-fuzzer
git clone git@github.com:matusf/openapi-fuzzer.git
cd openapi-fuzzer
# Install to the $PATH
cargo install --path .
# Or build inside the repo
cargo build --release
podman pull ghcr.io/matusf/openapi-fuzzer
After installation you will have the openapi-fuzzer
binary available to you, which offers two subcommands - run
and resend
. The run
subcommand will fuzz the API according to the specification and report any findings. All findings will be stored in a JSON format in a results
directory (the name of the directory can be specified by --results-dir
flag).
If the fuzzer finds a bug it will save the seed that leads to the generation of the payload triggering the bug. Those seeds are saved in a regressions file called openapi-fuzzer.regressions
. The seeds will be used in the next runs of the fuzzer to check if the bug persists. You shall save it alongside your project.
When you are done with fuzzing, you can use openapi-fuzzer resend
to resend payloads that triggered bugs and examine the cause in depth.
OpenAPI fuzzer supports version 3 of the OpenAPI specification in YAML or JSON format. You can convert older versions at editor.swagger.io.
- When the fuzzer receives an unexpected status code, it will report it as a finding. However, many APIs do not specify client error status codes in the specification. To minimize false positive findings ignore status codes that you are not interested in with
-i
flag. It is advised to fuzz it in two stages. Firstly, run the fuzzer without-i
flag. Then check theresults
folder for the reported findings. If there are reports from status codes you do not care about, add them via-i
flag and rerun the fuzzer. - Most APIs use some base prefix for endpoints like
/v1
or/api
, however, the specifications are sometimes written without it. Do not forget to include the path prefix in the url. - You may add an extra header with
-H
flag. It may be useful when you would like to increase coverage by providing some sort of authorization. You can use the-H
flag to add cookies too. e.g.-H "Cookie: A=1;"
. Use a single-H
flag when adding multiple cookies as well. e.g.-H "Cookie: A=1; B=2; C=3;"
. - Currently, the fuzzer makes 256 requests per endpoint. If all received responses are expected, it declares the endpoint as ok and continues to fuzz the next one. You can adjust this number by setting a
--max-test-case-count
flag. - To disable the verification of TLS certificates and thus use, for example, self-signed certificates, you can use the
--skip-tls-verify
flag. - By default, the fuzzer uses rate limiting. If it receives an HTTP status code of 429 or 503, it will wait for a number of seconds specified by the
Retry-After
header. If the header is not present, it will use an exponential backoff algorithm with a starting value of 1 second. After 10 unsuccessful retries, fuzzing of the endpoint is aborted.
$ openapi-fuzzer run --help
Usage: openapi-fuzzer run -s <spec> -u <url> [-i <ignore-status-code>] [-H <header>] [--max-test-case-count <max-test-case-count>] [-o <results-dir>] [--stats-dir <stats-dir>] [--skip-tls-verify] [--no-rate-limiting]
run openapi-fuzzer
Options:
-s, --spec path to OpenAPI specification file
-u, --url url of api to fuzz
-i, --ignore-status-code
status codes that will not be considered as finding
-H, --header additional header to send
--max-test-case-count
maximum number of test cases that will run for each
combination of endpoint and method (default: 256)
-o, --results-dir directory for results with minimal generated payload used
for resending requests (default: results).
--stats-dir directory for request times statistics. if no value is
supplied, statistics will not be saved
--skip-tls-verify disable verification of TLS certificates
--no-rate-limiting
do not use rate limiting
--help display usage information
$ openapi-fuzzer run -s ./spec.yaml -u http://127.0.0.1:8200/v1/ -i 404 -i 400 -H "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
When you are done fuzzing you can replay the findings. All findings are stored in the results
folder. Name of each file consists of concatenated endpoint, HTTP method and received status code. To resend the same payload to API, you need to run openapi-fuzzer resend
and specify a path to the finding file as an argument and a url of the api. You can overwrite the headers with -H
flag as well, which is useful, when you need authorization.
$ ls -1 results/
api-v1-componentstatuses-{name}-GET-500.json
api-v1-namespaces-{namespace}-configmaps-GET-500.json
api-v1-namespaces-{namespace}-endpoints-GET-500.json
api-v1-namespaces-{namespace}-events-GET-500.json
api-v1-namespaces-{namespace}-limitranges-GET-500.json
api-v1-namespaces-{namespace}-persistentvolumeclaims-GET-500.json
api-v1-namespaces-{namespace}-pods-GET-500.json
api-v1-namespaces-{namespace}-podtemplates-GET-500.json
api-v1-namespaces-{namespace}-replicationcontrollers-GET-500.json
api-v1-namespaces-{namespace}-resourcequotas-GET-500.json
api-v1-namespaces-{namespace}-secrets-GET-500.json
api-v1-namespaces-{namespace}-serviceaccounts-GET-500.json
api-v1-namespaces-{namespace}-services-GET-500.json
api-v1-watch-namespaces-{name}-GET-500.json
...
$ openapi-fuzzer resend --help
Usage: openapi-fuzzer resend <file> [-H <header...>] -u <url>
resend payload genereted by fuzzer
Positional Arguments:
file path to result file generated by fuzzer
Options:
-H, --header extra header
-u, --url url of api
--help display usage information
$ openapi-fuzzer resend --url https://minikubeca:8443 results/api-v1-componentstatuses-\{name\}-GET-500.json -H "Authorization: Bearer $KUBE_TOKEN" | jq
500 (Internal Server Error)
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Component not found: ኊ0",
"code": 500
}
Footnotes
-
not all found bugs are linked ↩