Skip to content

Convert gl-apifuzzing-runner to csharp

Problem

API Fuzzing has a python script gl-apifuzzing-runner that consumes various static definition files converting them into HTTP requests and sending them to the API Fuzzing scanner.

This worked great at first, however the features of gl-apifuzzing-runner are expanding and will require code duplication if kept as a python script. Also, long term, generating HTTP requests from definition files will need to move into the scanner core. By rewriting the runner in csharp we will be ready for the integration.

The following commands will be migrated:

Command Migrate
burp No
cmd No
folder No
har Yes
postman Yes
swagger Yes

Proposal

Create a new command line csharp tool that implements the existing runner functionality in csharp. The implementation will leverage existing work and classes from the Peach.Web project. The implementation will also make use of existing dependencies used by PeachWeb such as NSwag for OpenAPI/Swagger support, and the Newtonsoft json library.

  1. Add a new command line csharp application Peach.Web.Runner
    1. Executable name gl-apifuzzing-runner
  2. Use the same command line arguments and commands
    1. See https://github.com/commandlineparser/commandline

Internally the requests will end up as Peach.Web.Core.Models.WebApi.Operation objects. This will include creating EndPoint and Resources objects as well. These WebApi models what the scanning engine operates on. In the future this new runner code will be integrated and these models will be operated on directly.

The WebApi is modeled after REST API concepts and language used by the OpenAPI specifications.

To send the requests through the scanner proxy, convert Operations into the standard csharp web client. Multipart messages must be supported.

graph LR
  HAR --> Operation --> HttpClient

Runner Overview

The purpose of runner is to allow testing of static requests from supported file formats. This is done by sending the requests provided by a support file format repeatedly using the scan engine as a proxy.

High level internal logic:

  1. Convert static file into an array of requests
    1. Update with base url if provided via --base-url
    2. Update headers if provided via --header (note- multiple --header options can be provided)
  2. Start a new test session if we are running stand alone
  3. For each request in requests
    1. Tell PeachWeb a new request is being tested
    2. Do
      1. Update request with overrides.
        1. Overrides can change over time if an --overrides_cmd is provided
      2. Send request via PeachWeb proxy port
    3. While PeachWeb says continue

runner has two modes of operation: stand alone, or in conjunction with gl-apifuzzing-ci. When running standalone the runner will create a new scan job and then send the requests through the proxy. When used by gl-apifuzzing-ci the runner will not create a new job.

The method of use can be detected by looking for the following environment variables:

  1. FUZZAPI_SESSIONID - This provides the job id needed to call the PeachWeb APIs
  2. FUZZAPI_PROXY - This provides the proxy URL used when sending requests
CLI Options

Here are the existing CLI options. All of the long form parameters (--api, --header, etc.) can also be provided as environment variables with the FUZZAPI_ prefix. So for --api foo you could set FUZZAPI_API=foo. This is handled automatically by the python click module the options are defined using. If possible I would like to support environment variables in the csharp version, but it's not a must have.

Usage: gl-apifuzzing-runner [OPTIONS] COMMAND [ARGS]...

  Run one or more commands that generate traffic through API Fuzzing.

Options:
  --ci / --no-ci              Running from CI script, ensure no auto start.
  -a, --api TEXT              API Fuzzing API URL. Defaults to FUZZAPI_API
                              environ.
  -p, --project TEXT          API Fuzzing project to use. Ex. Default
  -r, --profile TEXT          API Fuzzing profile to use. Ex. Quick
  -u, --base-url TEXT         Base url for requests.  This overrides the 
                              recorded base url. Ex: '-u http://api.foo.com', 
                              '-u http://api.foo.com:7777'"
  -H, --header TEXT           Provide header. Multiple header arguments can be 
                              provided. Ex: '-H "Header: Value"'
  -o, --overrides TEXT        File containing overrides for headers and
                              cookies.
  --overrides_env TEXT        Provide overrides file contents via command line
  --overrides_cmd TEXT        Command that creates/updates overrides file. If
                              --overrides_interval provided, run at provided
                              interval.
  --overrides_interval FLOAT  Interval in minutes to run --overrides_cmd.
  --dryrun / --no-dryrun      Try operation with out API Fuzzing
  --timeout INTEGER           Timeout for a single test response. Defaults to
                              30 seconds.
  -v                          Increase verbosity of output. Supply more than
                              once to continue increasing.
  -F, --config TEXT           Use to specify full path to config file for this
                              job run.  Defaults to FUZZAPI_CONFIG environ
  --auth [httpbasic]          Select authentication type: httpbasic
  --username TEXT             Provide a username for authentication. Requires
                              --auth parameter.
  --password TEXT             Provide a password for authentication. Requires
                              --auth parameter.
  --help                      Show this message and exit.

Commands:
  har       Replay recorded requests from exported HTTP...
  postman   Replay recorded requests from exported...
  swagger   Generate requests using Swagger/OpenAPI...

Overrides

The overrides feature allows users to set or override headers, cookies, and parameters using a simple json document. The document is provided through a command line option --overrides_env={...}, a file on disk --overrides=filename, or a combination of file on disk, command, and run interval.

When the user provides a command to run, the command is expected to update the overrides json file when it runs. This feature is provided to handle cases where authentication tokens expire. When using a command the following options are required:

  1. --overrides - Profile a filename
  2. --overrides_cmd - Provides a command line to execute
  3. --overrides_interval - Interval in minutes to run the command

The command is run before sending any requests to create the initial json file. Then in the background every N minutes.

One of the design goals for overrides is providing a simple, small format that can be provided easily through an environmental variable.

The current format used by the python runner will be extended for the csharp runner to include several optional properties: query, body-form, body-json, body-xml.

{
  "headers":    { "name": "value"  }, 
  "cookies":    { "name": "value"  },
  "query":      { "name": "value"  },
  "body-form":  { "name": "value"  },
  "body-json":  { "jsonpath": "value" },
  "body-xml" :  { "xpath":    "value" }
}
  1. Changing a header

    { "header": {"Authorization": "Token b5638ae7-6e77-4585-b035-7d9de2e3f6b3"} }
  2. Parameter in query string

    { "query": {"api_key": "b5638ae7-6e77-4585-b035-7d9de2e3f6b3"} }
  3. Parameter in form encoded body

    { "body-form": {"api_key": "b5638ae7-6e77-4585-b035-7d9de2e3f6b3"} }
  4. Parameter in json encoded body

    The parameter key is a JSONPath expression.

    { "body-json": {"$.api_key": "b5638ae7-6e77-4585-b035-7d9de2e3f6b3"} }
  5. Parameter in xml encoded body

    The parameter key is an xpath expression.

    Set value to all xml elements named api_key:

    { "body-xml": {"//api_key": "b5638ae7-6e77-4585-b035-7d9de2e3f6b3"} }

    Set value to all attributes named api_key:

    { "body-xml": {"//@api_key": "b5638ae7-6e77-4585-b035-7d9de2e3f6b3"} }
  6. Changing multiple parameters

    When changing multiple parameters, additional properties are added.

    { 
      "body-json": {
        "$.api_key": "b5638ae7-6e77-4585-b035-7d9de2e3f6b3",
        "$.other": "some other value"
      } 
    }
  7. Changing a header and parameter

    { 
      "header": { "X-Special-Header": "XYZ" },
      "body-json": {
        "$.api_key": "b5638ae7-6e77-4585-b035-7d9de2e3f6b3",
        "$.other": "some other value"
      } 
    }

HTTP Archive (HAR)

  1. Only one version v1.2 is supported
  2. Support loading as local file and URL

OpenAPI

  1. Support both v2.0 and v3 OpenAPI specification formats
  2. Support both local files and also URLs to load the specification
  3. Use example/default data for operations if provided in the specification

Postman

  1. Support both v2.0 and v2.1 Postman Collection types
  2. Support nested item groups
  3. Support loading by file name and URL
Postman variables overrides

In order to support postman variables. There are two possible sources for the values of postman variables. They can be specified thru environmental variables:

  • FUZZAPI_POSTMAN_COLLECTION Contains a file path to a postman file.
  • FUZZAPI_POSTMAN_COLLECTION_VARIABLES contains a file path to a json file. The JSON file is a collection of key-value pais, for example: { "var_name1" : "value_1", "var_name2" : "value_2" }

When the runner is converting Postman request into Operation and it finds a variable reference. It can look up for its value in the following order:

  1. First, into the JSON defined by FUZZAPI_POSTMAN_COLLECTION_VARIABLES.
  2. Second, into the postman file defined by FUZZAPI_POSTMAN_COLLECTION to look up the value of the variable.
  3. Third, the input postman (input) file provided in the command line.

If the value is not found in any of the sources, then an error can be thrown indicating the missed value for the variable.

Tasks

  1. Implement new runner in csharp
    1. Postman variable support
      1. Mike: Update the CI Template so it's passed to the docker containers MR Link
      2. Mike: Update worker-entry to identify the variable and pass it to the runner
    2. Command Line parameter updates
      1. Updating worker-entry to order arguments correctly (different between py and csharp)
  2. Create tests
    1. Herb: Unit tests using nunit
    2. Herb: Integration tests using nunit (see existing runner tests)
    3. Herb: Add an integration test for openapi v3 to existing openapi integration tests
    4. Herb: Add an integration test for FUZZAPI_POSTMAN_COLLECTION_VARIABLES to worker-entry
  3. Documentation (additions to existing documentation)
    1. Herb: Document support for OpenApi v3
    2. Herb: Update documentation for overrides
    3. Herb: Document the new postman collection variables usage
    4. Doc review from Mike Eddington
    5. Doc review from tech writer
  4. Mike: Final testing and review of runner
  5. Mike: Merge MR and publish to staging
  6. Add additional e2e tests to cover new features in a separate MR (https://gitlab.com/gitlab-org/security-products/tests/api-fuzzing-e2e)
    1. Herb: Add a new e2e test for OpenApi v3 (normal + dnd)
      1. project names: openapi-v3, opernapi-v3-dnd
    2. Herb: Add a new e2e test for Postman Variables (normal + dnd)
      1. project names: postman-v2.1-vars, postman-v2.1-vars-dnd
  7. Mike: Add an issue for dast api to investigate openapi v3 server definitions with variable parameters {host}.
  8. Mike: Publish to production
Edited by Herber Madrigal