Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,3 @@ node_modules/

# Output of 'npm pack'
*.tgz

# corpus files in the path traversal example except for manual test.zip
examples/bug-detectors/path-traversal/corpus/
92 changes: 86 additions & 6 deletions docs/fuzz-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,15 +196,95 @@ Bug detectors are one of the key features when fuzzing memory-safe languages. In
Jazzer.js, they can detect some of the most common vulnerabilities in JavaScript
code. Built-in bug detectors are enabled by default, but can be disabled by
adding the `--disable_bug_detectors=<pattern>` flag to the project
configuration. For example, to disable all built-in bug detectors, add
configuration. To disable all built-in bug detectors, add
`--disable_bug_detectors='.*'` to the project configuration.

Following built-in bug detectors are available in Jazzer.js:
### Command Injection

| Bug Detector | Description |
| ------------------- | -------------------------------------------------------------------- |
| `command-injection` | Hooks all functions of the built-in module `child_process`. |
| `path-traversal` | Hooks all relevant functions of the built-in modules `fs` and `path` |
Hooks all functions of the built-in module `child_process` and reports a finding
if the fuzzer was able to pass a command to any of the functions.

_Disable with:_ `--disable_bug_detectors=command-injection`, or when using Jest:

```json
{ "disableBugDetectors": ["command-injection"] }
```

### Path Traversal

Hooks all relevant functions of the built-in modules `fs` and `path` and reports
a finding if the fuzzer could pass a special path to any of the functions.

_Disable with:_ `--disable_bug_detectors=path-traversal`, or when using Jest:

```json
{ "disableBugDetectors": ["path-traversal"] }
```

### Prototype Pollution

Detects Prototype Pollution. Prototype Pollution is a vulnerability that allows
attackers to modify the prototype of a JavaScript object, which can lead to
validation bypass, denial of service and arbitrary code execution.

The Prototype Pollution bug detector can be configured in the
[custom hooks](#custom-hooks) file.

- `instrumentAssignmentsAndVariableDeclarations` - if called, the bug detector
will instrument assignment expressions and variable declarations and report a
finding if `__proto__` of the declared or assigned variable contains any
properties or methods. When called in dry run mode, this option will trigger
an error.
- `addExcludedExactMatch` - if the stringified `__proto__` equals the given
string, the bug detector will not report a finding. This is useful to exclude
false positives.

Here is an example configuration in the [custom hooks](#custom-hooks) file:

```javascript
const { getBugDetectorConfiguration } = require("@jazzer.js/bug-detectors");

getBugDetectorConfiguration("prototype-pollution")
?.instrumentAssignmentsAndVariableDeclarations()
?.addExcludedExactMatch('{"methods":{}}');
```

Adding instrumentation to variable declarations and assignment expressions
drastically reduces the fuzzer's performance because the fuzzer will check for
non-empty `__proto__` on every variable declaration and assignment expression.
In addition, this might cause false positives because some libraries (e.g.
`lodash`) use `__proto__` to store methods. Therefore, in the default
configuration these options are disabled.

_Shortcoming:_ The instrumentation of variable declarations and assignment
expressions will not detect if the prototype of the object in question has new,
deleted, or modified functions. But it will detect if a function of a prototype
of an object has become a non-function. The following example illustrates this
issue:

```javascript
class A {}
class B extends A {}
const b = new B();
b.__proto__.polluted = true; // will be detected
b.__proto__.test = [1, 2, 3]; // will be detected
b.__proto__.toString = 10; // will be detected
b.__proto__.toString = () => "polluted"; // will not be detected
delete b.__proto__.toString; // will not be detected
b.__proto__.hello = () => "world"; // will not be detected
```

However, our assumption is that if the fuzzer is able to modify the methods in a
prototype, it will be able also find a way to modify other properties of the
prototype that are not functions. If you find a use case where this assumption
does not hold, feel free to open an issue.

_Disable with:_ `--disable_bug_detectors=prototype-pollution`, or when using
Jest:

```json
{ "disableBugDetectors": ["prototype-pollution"] }
```

For implementation details see
[../packages/bug-detectors/internal](../packages/bug-detectors/internal).
Expand Down
7 changes: 4 additions & 3 deletions examples/bug-detectors/command-injection/custom-hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
*/

const { registerReplaceHook } = require("@jazzer.js/hooking");
const { reportFinding } = require("@jazzer.js/bug-detectors");
const { guideTowardsEquality } = require("@jazzer.js/fuzzer");
const { guideTowardsEquality, reportFinding } = require("@jazzer.js/core");

/**
* Custom bug detector for command injection.
* Custom bug detector for command injection. This hook does not call the original function (execSync) for two reasons:
* 1. To speed up fuzzing---calling execSync gives us about 5 executions per second, while calling nothing gives us a lot more.
* 2. To prevent the fuzzer from accidentally calling commands like "rm -rf" on the host system during local tests.
*/
registerReplaceHook(
"execSync",
Expand Down
4 changes: 2 additions & 2 deletions examples/bug-detectors/command-injection/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "bug-detectors",
"name": "command-injection-example",
"version": "1.0.0",
"main": "fuzz.js",
"license": "ISC",
"dependencies": {
"global-modules-path": "^2.3.1"
},
"scripts": {
"customHooks": "jazzer fuzz -i global-modules-path --disable_bug_detectors='.*' -h custom-hooks --timeout=100000000 --sync -- -runs=100000 -print_final_stats=1",
"fuzz": "jazzer fuzz -i global-modules-path --disable_bug_detectors='.*' -h custom-hooks --timeout=100000000 --sync -x Error -- -runs=100000 -print_final_stats=1",
"bugDetectors": "jazzer fuzz -i global-modules-path --timeout=100000000 --sync -- -runs=100000 -print_final_stats=1",
"dryRun": "jazzer fuzz --sync -x Error -- -runs=100000 -seed=123456789"
},
Expand Down
13 changes: 13 additions & 0 deletions examples/bug-detectors/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "examples-bug-detectors",
"version": "1.0.0",
"scripts": {
"dryRun": "npm run test",
"test": "run-script-os",
"test:linux:darwin": "sh ../../scripts/run_all.sh fuzz",
"test:win32": "..\\..\\scripts\\run_all.bat fuzz"
},
"devDependencies": {
"run-script-os": "^1.1.6"
}
}
2 changes: 2 additions & 0 deletions examples/bug-detectors/path-traversal/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
!corpus/test.zip
corpus/*
6 changes: 3 additions & 3 deletions examples/bug-detectors/path-traversal/package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"name": "custom-hooks-bd",
"name": "path-traversal-example",
"version": "1.0.0",
"main": "fuzz.js",
"license": "ISC",
"dependencies": {
"jszip": "3.7.1"
},
"scripts": {
"fuzz": "jazzer fuzz -i fuzz.js -i jszip corpus -- -runs=10000000 -print_final_stats=1 -use_value_profile=1 -max_len=600 -seed=123456789",
"fuzz": "jazzer fuzz -i fuzz.js -i jszip -x Error corpus -- -runs=10000000 -print_final_stats=1 -use_value_profile=1 -max_len=600 -seed=123456789",
"dryRun": "jazzer fuzz --sync -x Error -- -runs=100000 -seed=123456789"
},
"devDependencies": {
"@jazzer.js/core": "file:../../packages/core"
"@jazzer.js/core": "file:../../../packages/core"
}
}
9 changes: 9 additions & 0 deletions examples/bug-detectors/prototype-pollution/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const {
getBugDetectorConfiguration,
// eslint-disable-next-line @typescript-eslint/no-var-requires
} = require("../../../packages/bug-detectors");

getBugDetectorConfiguration("prototype-pollution")
?.instrumentAssignmentsAndVariableDeclarations()
?.addExcludedExactMatch("example");
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/env node
/*
* Copyright 2022 Code Intelligence GmbH
* Copyright 2023 Code Intelligence GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,20 +14,13 @@
* limitations under the License.
*/

import {
exploreState,
guideTowardsContainment,
guideTowardsEquality,
} from "@jazzer.js/fuzzer";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const protobuf = require("protobufjs");

export interface Jazzer {
guideTowardsEquality: typeof guideTowardsEquality;
guideTowardsContainment: typeof guideTowardsContainment;
exploreState: typeof exploreState;
}

export const jazzer: Jazzer = {
guideTowardsEquality,
guideTowardsContainment,
exploreState,
module.exports.fuzz = async function (data) {
try {
protobuf.parse(data.toString());
} catch (e) {
// ignore
}
};
16 changes: 16 additions & 0 deletions examples/bug-detectors/prototype-pollution/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "prototype-pollution-example",
"version": "1.0.0",
"main": "fuzz.js",
"license": "ISC",
"dependencies": {
"protobufjs": "7.2.3"
},
"scripts": {
"fuzz": "jazzer fuzz -i protobufjs -i fuzz -e nothing --timeout=60000 -x Error -- -runs=1000000 -print_final_stats=1 -use_value_profile=1 -rss_limit_mb=10000 -dict=userDict.txt",
"dryRun": "jazzer fuzz -i protobufjs -- -runs=100000000 -seed=123456789"
},
"devDependencies": {
"@jazzer.js/core": "file:../../../packages/core"
}
}
1 change: 1 addition & 0 deletions examples/bug-detectors/prototype-pollution/userDict.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"option (foo).constructor.prototype.test = true;"
8 changes: 6 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading