/openapi-fuzzer

Black-box fuzzer that fuzzes APIs based on OpenAPI specification. Find bugs for free!

Primary LanguageRustGNU Affero General Public License v3.0AGPL-3.0

OpenAPI fuzzer

ci

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!

image

Findings

The fuzzer has been used to find bugs in numerous software. Some of the well-known fuzzed software include1:

Kubernetes
Gitea
Vault

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.

Building & installing

From crates.io

To build the fuzzer, you will need to have rust installed.

cargo install openapi-fuzzer

From source

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

Using containers

podman pull ghcr.io/matusf/openapi-fuzzer

Usage

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.

Tips

  • 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 the results 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=="

Replaying findings

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

  1. not all found bugs are linked