Skip to content

feat: add secure JSON parsing with prototype poisoning handling #603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

bjohansebas
Copy link
Member

Users of this middleware are given the option to control prototype poisoning
closes: #347

Copy link

socket-security bot commented Apr 5, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedsecure-json-parse@​3.0.210010010088100

View full report

@bjohansebas bjohansebas marked this pull request as ready for review April 5, 2025 01:22
@bjohansebas
Copy link
Member Author

secure-json-parse also has protection against constructor poisoning. Should I include that as well? Are there any use cases for it?

@@ -55,6 +56,7 @@ function json (options) {

var reviver = options?.reviver
var strict = options?.strict !== false
const protoPoisoning = options?.onProtoPoisoning || 'ignore'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a future major version, we can make this throw an error or ignore it, but in the meantime, to maintain compatibility, this option is handled.

@@ -17,6 +17,7 @@
"on-finished": "^2.4.1",
"qs": "^6.14.0",
"raw-body": "^3.0.0",
"secure-json-parse": "^3.0.2",
Copy link
Member Author

@bjohansebas bjohansebas Apr 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a version 4, but they theoretically support from node 20 onwards, so to avoid any potential issues, we’re sticking with version 3, which supports Node 18+

@@ -74,7 +76,7 @@ function json (options) {

try {
debug('parse json')
return JSON.parse(body, reviver)
return secureJson.parse(body, reviver, { protoAction: protoPoisoning })
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to always protect against this issue by passing 'error' or 'remove', but I'm not sure. Other packages chose to make it a configurable option, and I think it would make some sense to do the same.

@UlisesGascon
Copy link
Member

UlisesGascon commented Apr 25, 2025

Do we want to cover this type of scenarios (constructor.prototype) or just __proto__? Anyway... I will like to include constructor.prototype in the tests so we can document the current situation. Otherwise might we misleading as most developers are aware of __proto__ but not constructor behaviors.

const obj = {};
const otherObj = {}

console.log("obj.polluted", obj.polluted) // undefined
console.log("otherObj.polluted", otherObj.polluted) // undefined

obj.constructor.prototype.polluted = true;

console.log("obj.polluted", obj.polluted) // true
console.log("otherObj.polluted", otherObj.polluted) // true

@bjohansebas
Copy link
Member Author

Yeah, although I don't think it hurts to add an option for constructor poisoning, so I'm going to add tests for the constructor prototype and add the option

@bjohansebas bjohansebas requested review from Phillip9587 and a team April 27, 2025 01:00
@bjohansebas bjohansebas requested a review from Copilot April 27, 2025 01:09
Copilot

This comment was marked as off-topic.

@jonchurch
Copy link
Member

jonchurch commented May 14, 2025

Im not crazy about the current form of the PR. I'd prefer we fight against bloating the options further, especially in cases where there is no strong reason for someone to deviate from the default.

Philosophically... I don't like these kinds of mitigations for language level issues, they should be fixed in the language. That ship has long since sailed, as `Object.assign` is likely never to change. At least the spread operator was fixed to no longer set prototype.

I also am not convinced that the output of body-parser should ever be considered trusted input. There is a vuln here only in that some code may pass req.body into Object.assign. This particular issue happens to have a name because it is so common, but is one of many such issues that arise when developers take the objects they receive from users and blindly copy them.

We cannot code around all of them, but since this one is common and the intent of developers so clear (nobody is meaning to pollute the root proto, whether there was something malicious or not in the input) it is worth considering.

I'd consider dropping __proto__, constructor.prototype (and possibly even constructor outright) from user inputs silently in a major, and not providing an option for people to toggle it off. If someone needs to deviate that far, they can extend the Generic parser to do it.

But whatever we do, lets be consistent. the extended urlencoded parser (using qs) already drops proto, and there is no way to turn that off. The non extended urlencode uses querystring and would still pass through proto.

@bjohansebas
Copy link
Member Author

I'd consider dropping proto, constructor.prototype (and possibly even constructor outright) from user inputs silently in a major, and not providing an option for people to toggle it off. If someone needs to deviate that far, they can extend the Generic parser to do it.

I agree with that approach, adding the option was more about being permissive, but I’d really prefer to just remove it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement a __proto__ check option
4 participants