Skip to main content

C++ Web API Fuzzing

CI Sense is capable of coverage-guided fuzzing for C++ based REST and GRPC APIs. This section guides you through the initial setup of a C++ Web API fuzzing project.

Prerequisites

Before you proceed with this guide, you should have already:

Web API server

Your Web API can run anywhere as long as it's reachable from the server hosting CI Sense. You find the Port details below.

Typically, this would be one of the following:

  • On the CI/CD server (gitlab/github/jenkins server)
  • On the CI Sense server
  • A separate server (for example your integration testing cluster)

If you choose to run your Web API on a separate server from the one hosting CI Sense, you need to make sure that:

  1. The Web API is exposed on a network interface and the port on which it is running is reachable from CI Sense.
  2. Ports 443 or 80 and 6777-7777 on the server running CI Sense are reachable from the server hosting your Web API.

Create a fuzz test

You should have already created a fuzz test based on the provided template.

Add a web service

CI Sense uses the idea of web services to associate fuzz tests to a given service when the fuzzing agent contacts CI Sense from the software under test. You can create Web services in the UI with the following steps:

  1. Open CI Sense in your browser
  2. From the left sidebar, select your project from the dropdown menu.
  3. Click Project Settings
  4. Click WEB SEVICES
  5. Click the ADD WEB SERVICE button.
  6. Add a name in the Web Service Identifier textbox. The name/identifier can be whatever you like, but it must match the name in the .code-intelligence/web_services.yaml file. If you used the template project, the name is mywebservice by default.
  7. Click NEXT.
  8. Select C/C++
  9. If you have not already, you can download the fuzzing agent by clicking DOWNLOAD NATIVE FUZZING AGENT. You can also download the fuzzing agent at any time by running cictl get nativeagent.
  10. If you have not already, you can download the static support library by clicking DOWNLOAD STATIC SUPPORT LIBRARY. You can also download the static support library at any by running cictl get fuzzingsupportlib.
  11. Click DONE

Compile the C++ bug detector framework

The C++ bug detector framework is a framework to hook into C and C++ code with the goal of detecting bugs via CI Sense. It is provided as a CMake project so that users can build it with their compiler, using their C++ runtime. The final artifact is a shared object, libhooks.so, which is meant to be preloaded into the app under test. Currently, it only supports Linux.

Requirements

  • C++ 17+ compiler toolchain.
  • If detectors with external dependencies are activated during build an internet connection to download them is necessary.

Airgapped environments

If you are building this framework in an airgapped environment, you need to download the dependencies manually and place them in the folders where CMake expects them. Download all files referenced in CMakeLists.txt by ExternalProject_Add URL and place them in <build-dir>/<project-name>-prefix/src/external-project-url-downloaded-file>.

For the sql-parser dependency this would be build/sql-parser-prefix/src/f0faaad39904f7756d1a8becf798a96e444351d0.zip downloaded from https://github.com/CodeIntelligenceTesting/sql-parser/archive/f0faaad39904f7756d1a8becf798a96e444351d0.zip

Build

mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=RELEASE ..
cmake --build .
note

Optional detectors will be enabled depending on your build environment. The CMake configuration process reports for each optional detector whether it's enabled, and why. For example, if you have a license for SQLAPI++, you can enable the SQL injection detector by pointing CMake to your SQLAPI.h using -DCMAKE_INCLUDE_PATH=/path/to/sqlapi/include.

External dependency for all detectors: sql-parser

  • SQLAPI++
    • Deactivated by default; to enable set -DCMAKE_INCLUDE_PATH=/path/to/sqlapi/include
    • Tests require SQLAPI++, point CMake to it with -DCMAKE_LIBRARY_PATH=/path/to/sqlapi/lib
  • SQLite
    • Enabled by default; to disable set -DENABLE_SQLITE=OFF
    • libsqlit3 required on build system

After building the framework, you get the libhooks.so file that you can preload with your app under test when you run it.

Instrument your code for fuzzing

Add the following flags to your compilation step:

-fprofile-instr-generate -fcoverage-mapping -fsanitize=fuzzer-no-link -Lpath/to/downloaded/agent -Wl,-rpath,path/to/downloaded/agent -lfuzzing_agent

Add the following flags to your linker step:

-fprofile-instr-generate -fsanitize=fuzzer-no-link -L${CIFUZZ_DIR}/lib/code-intelligence -Wl,-rpath,${CIFUZZ_DIR}/lib/code-intelligence -lfuzzing_agent

Link the static support library with your binary with the following flags:

-Wl,--whole-archive path/to/downloaded/libfuzzing_support_lib.a -Wl,--no-whole-archive

Run the instrumented binary

Before you start your fuzz test, you need to start the instrumented binary you created. This binary requires some environment variables so that the agent (libfuzzing_agent.so) knows how to reach CI Sense. There are several environment variables you can provide when invoking the instrumented binary. Most of them are required:

  • ASAN_OPTIONS=alloc_dealloc_mismatch=0 - deactivates allocation checks that would be triggered in the hook.
  • LD_PRELOAD - path to the libhooks.so when you built the bug detector framework. This is required.
  • CIFUZZ_AUTH_TOKEN - API token or password you configured when you configured authentication.
  • CIFUZZ_SERVICE_NAME - the full web service you added. This can be obtained by running cictl list webservices and copying the appropriate one. Be sure to copy the full path. It should be of the form projects/<project_name>/web_services/<web_service_name>.
  • CIFUZZ_FUZZING_SERVER_HOST - the IP or domain where CI Sense is reachable.
  • CIFUZZ_FUZZING_SERVER_PORT - the port where CI Sense is reachable.
  • CIFUZZ_TLS - if CI Sense is configured with TLS, this should be set to 1.
  • CIFUZZ_TLS_CERT_FILE - if you are using a custom certificate, specify the path here.
  • CIFUZZ_HALT_ON_ERROR - a discovered bug does not exit the app by default. If you want the app to halt on errors, set this variable to 1.

Warning: if TLS is enabled for CI Sense and CIFUZZ_TLS isn't set to 1, the connection fails.

Example of running the instrumented binary with appropriate environment variables:

ASAN_OPTIONS=alloc_dealloc_mismatch=0 \
LD_PRELOAD=/home/demo/cpp-bug-detector-framework/build/libhooks.so \
CIFUZZ_TLS=1 \
CIFUZZ_AUTH_TOKEN=d24573f2-b6d5-4180-9ce6-9a93872942ad \
CIFUZZ_SERVICE_NAME=projects/cpp-web-app-4d9d77bd/web_services/cpp-sql-service \
CIFUZZ_FUZZING_SERVER_PORT=8080 \
CIFUZZ_FUZZING_SERVER_HOST=127.0.0.1 \
/home/demo/repos/cpp-web-app/web-app-demo

Example run script

The following is an example run script for running the fuzz tests for a gRPC project. You can also use this as the basis for a CI/CD script to automate the fuzzing process.

#! /usr/bin/bash

# duration to monitor the campaign run
TIMEOUT=120
# type of findings to report
FINDINGS_TYPE=CRASH,RUNTIME_ERROR
# [IP | DOMAIN]:PORT where CI Sense is currently reachable
FUZZING_SERVER=
# http[s]://[IP | DOMAIN]:PORT where CI Sense is currently reachable
WEB_APP_ADDRESS=
# project name obtained from: cictl list projects
PROJECT=
# API token (or password) you configured for connecting to CI Sense
CI_FUZZ_API_TOKEN=
# path to the root of the local repository
LOCAL_REPO=
# [IP | DOMAIN]:PORT where the target API is running
SOFTWARE_UNDER_TEST

# build the artifacts to submit to CI Sense
cd $LOCAL_REPO
ci-build fuzzers --directory=./
mkdir fuzzing_artifacts

# authenticate to the fuzzing server
CICTL_CMD="cictl -s $FUZZING_SERVER"
echo $CI_FUZZ_API_TOKEN | $CICTL_CMD login

# import the artifact bundle to CI Sense
ARTIFACT_NAME=$($CICTL_CMD import artifact $LOCAL_REPO/fuzzing-artifacts.tar.gz --project-name "$PROJECT")

# start the fuzz tests
TEST_COLLECTION_RUN=$($CICTL_CMD start --application-base-url $SOFTWARE_UNDER_TEST "${ARTIFACT_NAME}")

# monitor the output of the run
$CICTL_CMD monitor_campaign_run --dashboard_address="${WEB_APP_ADDRESS}" --duration="${TIMEOUT}" --findings_type="${FINDINGS_TYPE}" ${TEST_COLLECTION_RUN}