Skip to main content

CI Fuzz JavaScript/TypeScript tutorial

This tutorial intends to teach you the basics of using cifuzz with a JavaScript/TypeScript project. cifuzz directly supports npx.

This guide was created using Ubuntu 20.04 64-bit. However, if you are on MacOS or Windows, you should still be able to follow along without any issues.

If you haven't yet installed cifuzz, then first head to the installation section

note

cifuzz supports only NodeJS applications because that's a Jazzer.js limitation.

1. Setting up the project

Download or clone the following repository: ci-fuzz-cli-tutorials. The above repository contains several projects that highlight the usage of cifuzz.

note

Please change into the js_ts/nodejs-js directory for this tutorial and try to follow along, by running all highlighted cifuzz commands listed below.

note

Please change into the js_ts/nodejs-ts directory for the TypeScript application.

2. Initializing the project

The first step for any project is to initialize it. Initialization entails creating a cifuzz.yaml configuration file in the project directory. This step does not require any manual work. Please run the following command:

cifuzz init

When running cifuzz init, the tool recognizes the nature of the project. In this case, a NodeJS project, and provides dependency hints for you that you need to install. Your top level package.json file will be modified by running the given commands for JavaScript and TypeScript applications respectively.

JavaScript

For the JavaScript application, the following commands need to be executed depending on the package manager of your choice.

npm install --save-dev @jazzer.js/jest-runner @jazzer.js/core

or

yarn add --dev @jazzer.js/jest-runner @jazzer.js/core

You also need to add the following code snippet to your jest.config.js:

  module.exports = {
projects: [
{
displayName: "test",
},
{
runner: "@jazzer.js/jest-runner",
displayName: {
name: "Jazzer.js",
color: "cyan",
},
testMatch: ["<rootDir>/**/*.fuzz.js"],
},
],
};

TypeScript

For the TypeScript application, the following commands need to be executed depending on the package manager of your choice.

npm install --save-dev @jazzer.js/jest-runner ts-jest @jazzer.js/core

or

yarn add --dev @jazzer.js/jest-runner ts-jest @jazzer.js/core

You also need to add the following code snippet to your jest.config.js:

import type { Config } from "jest";

const config: Config = {
verbose: true,
projects: [
{
displayName: "Jest",
preset: "ts-jest",
},
{
displayName: {
name: "Jazzer.js",
color: "cyan",
},
preset: "ts-jest",
runner: "@jazzer.js/jest-runner",
testEnvironment: "node",
testMatch: ["<rootDir>/*.fuzz.[jt]s"],
},
],
coveragePathIgnorePatterns: ["/node_modules/", "/dist/"],
modulePathIgnorePatterns: ["/node_modules", "/dist/"],
};

export default config;

Additionally, for TypeScript applications adding the following line to globals.d.ts file introduces the fuzz test function types globally.

import "@jazzer.js/jest-runner";

Initializtion continued

  • jazzer.js/core - this dependency will install Jazzer.js and all required dependecies.
  • jazzer.js/jest-runner - this dependency enables cifuzz and Jest integration with your project.

After installing all shown dependencies, your package.json needs to look identical to:

{
"name": "TODO list server",
"devDependencies": {
"@jazzer.js/core": "^1.6.1",
"@jazzer.js/jest-runner": "^1.6.1",
...
}
},

3. Creating a fuzz test

note

The following sections are based on the JavaScript application. However, they are analogous for the TypeScript application.

The next step is to create a JavaScript fuzz test template. With one of the main goals of cifuzz being to make fuzz testing as easy as unit testing, you create one similar as you would do in a standard unit test. Run the following command:

cifuzz create js -o CommandExecution.fuzz.js 

Opening CommandExecution.fuzz.js yields a basic fuzz test template right away.

Before extending the template fuzz test, take a look at the target method that you want to fuzz. It's located in utils.js:

/**
* @param { string } command
*/

function commandExecution(command, fn) {
child_process.exec(command, (err, stdout, stderr) => {
fn(stdout)
});
}

The main part to focus on here is the parameter that the commandExecution method requires, string command. As long as you can pass the correct data types to the function, the fuzzer takes care of the rest.

Equipped with that knowledge, writing the fuzz test is next. You can add or copy/paste the following into CommandExecution.fuzz.js:

const { FuzzedDataProvider } = require("@jazzer.js/core");
const utils = require('./utils');


test.fuzz("My fuzz test", (data) => {

if (data.length <= 1) {
return;
}

const provider = new FuzzedDataProvider(data);

var command = provider.consumeString(30);
try {
utils.commandExecution(command, (_data) => {})
}catch(e) {

}
});

For this fuzz test we also considered to not send string with 0 length.

A couple of important notes regarding the fuzz test:

  • The fuzz test file needs to follow the name schema <test_file_name>.fuzz.js
  • The fuzz test must import @jazzer.js/core.
  • The above fuzz test uses the FuzzedDataProvider class. This isn't required, but it's a convenient way to split the fuzzing input in the data variable into different native data types that are easier to use than the raw bytes provided by data. Here is a link about FuzzedDataProvider in Jazzer.js.
  • Once you have prepared all required arguments for the function call you want to fuzz, call it. In the above scenario we built command using data from the fuzzer, the fuzz test has to call the target method commandExecution with the fuzz data.

4. Executing the fuzz test

You configured everything and created a fuzz test. Now, run the fuzz test using the fuzz harness name:

cifuzz run CommandExecution 

Shortly after executing the fuzz test you will get a notification that cifuzz discovered a bug in commandExecution. Here is a snippet of the output:

💥 [giggling_tiger] Command Injection in exec(): called with 'jaz_zer/' in reportFinding (node_modules/@jazzer.js/bug-detectors/findings.ts:45:17)

5. Examining a finding

When cifuzz discovers a finding, it stores the output from the finding and the input that caused it. You can list all findings discovered so far by running cifuzz findings. If you want to see the details of a specific finding just provide its name, for example cifuzz finding giggling_tiger. This provides the stack trace and other details about the finding that helps you debug and fix the issue. Examining the output from cifuzz finding giggling_tiger below shows that there was a command injection based on externally controlled data. This was triggered at line 109 in the utils.js.

[giggling_tiger] Command Injection in exec(): called with 'jaz_zer/' in reportFinding (node_modules/@jazzer.js/bug-detectors/findings.ts:45:17)
Date: 2023-07-28 16:19:40.778791579 +0200 CEST

● My fuzz test

Command Injection in exec(): called with 'jaz_zer/'

107 |
108 | function commandExecution(command, fn) {
> 109 | child_process.exec(command, (err, stdout, stderr) => {
| ^
110 | fn(stdout)
111 | });
112 | }

at reportFinding (node_modules/@jazzer.js/bug-detectors/findings.ts:45:17)
at Hook.beforeHook [as hookFunction] (node_modules/@jazzer.js/bug-detectors/internal/command-injection.ts:50:17)
at Object.exec (node_modules/@jazzer.js/hooking/manager.ts:240:10)
at Object.commandExecution (utils.js:109:19)
at CommandExecution.fuzz.js:14:15
at node_modules/@jazzer.js/core/core.ts:411:15

If you examine line 109 in utils.js, you can observe that this is the location where execution attempts to call the exec function.

cifuzz also stores the crashing input in a .cifuzz-findings directory. In this example, that would be .cifuzz-findings/giggling_tiger. This can be helpful when debugging your app.