Skip to content
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

child_process: harden the API #30008

Closed
wants to merge 1 commit into from

Conversation

watson
Copy link
Member

@watson watson commented Oct 17, 2019

Ensure that the options object used by exec, execSync, execFile, execFileSync, spawn, spawnSync, and fork, isn't susceptible to prototype pollution.

This is achieved by copying all the properties of the options object into another object that doesn't have a prototype.

Background

If an attacker is able to successfully pollute the prototype just before a call to exec, execSync, execFile, execFileSync, spawn, spawnSync, or fork, they will be able to perform RCE on most Linux systems by manipulating the env option.

Recommended alternative

If this PR doesn't land, or if you're running a version of Node.js that doesn't include this change, the recommended way to spawn a child process is:

const options = Object.create(null)
options.env = Object.assign(Object.create(null), process.env)
spawn(command, options)

By actively passing in an options object that contains an env property you ensure that one isn't created internally which inherits from Object.prototype.

The above example uses spawn, but this approach is also relevant for exec, execSync, execFile, execFileSync, spawnSync, and fork.

Checklist

  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
  • Run child_process benchmarks

@watson watson requested a review from vdeturckheim October 17, 2019 14:55
@watson watson self-assigned this Oct 17, 2019
@nodejs-github-bot nodejs-github-bot added child_process Issues and PRs related to the child_process subsystem. util Issues and PRs related to the built-in util module. labels Oct 17, 2019
@nodejs-github-bot
Copy link
Collaborator

vdeturckheim
vdeturckheim previously approved these changes Oct 17, 2019
Copy link
Member

@vdeturckheim vdeturckheim left a comment

Choose a reason for hiding this comment

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

LGTM regarding the code and the behavior - I forsee it can be a braking change for some people. Would doc be enough to mitigate it?

@watson
Copy link
Member Author

watson commented Oct 17, 2019

@vdeturckheim Breaking in what way? I was originally thinking it would be breaking if users relied on the prototype chain for some of the properties. But { ...options } strips that anyway and leaves only Object.prototype, so I concluded this wasn't a problem. But I might be overlooking something?

@vdeturckheim
Copy link
Member

@watson nop, I was believing that too. So LGTM! Thanks for the clarification!

Copy link
Member

@vdeturckheim vdeturckheim left a comment

Choose a reason for hiding this comment

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

LGTM

@watson watson force-pushed the hardening_child_process branch from 975e975 to e9b6e3b Compare October 17, 2019 15:14
@watson
Copy link
Member Author

watson commented Oct 17, 2019

Force pushed to fix linting errors... I wonder why my tests locally didn't catch these - I guess make test-only doesn't run the linter

@watson
Copy link
Member Author

watson commented Oct 17, 2019

@vdeturckheim I just took a second look at the code regarding your original concern, and technically normalizeSpawnArguments doesn't do the ...options thing until after it has used the options object to check a few things. But if anybody tried to rely on this, it would probably not be very stable anyway, so I'm not sure anybody actually does that in real life? 🤔

@nodejs-github-bot
Copy link
Collaborator

@watson watson force-pushed the hardening_child_process branch from e9b6e3b to 4432aeb Compare October 17, 2019 15:49
@watson
Copy link
Member Author

watson commented Oct 17, 2019

More linting errors 🙄 I found that neither make test nor make test-only runs the linting locally despite https://github.com/nodejs/node/blob/master/BUILDING.md#running-tests saying so. Only make lint does. Anyway, now it should be good 👍

@nodejs-github-bot
Copy link
Collaborator

@richardlau
Copy link
Member

More linting errors 🙄 I found that neither make test nor make test-only runs the linting locally despite https://github.com/nodejs/node/blob/master/BUILDING.md#running-tests saying so. Only make lint does. Anyway, now it should be good 👍

It does run the linting but any failures are being masked. #30012 should fix that.

@watson
Copy link
Member Author

watson commented Oct 17, 2019

Looks like the windows tests are filing, but I'm having trouble seeing exactly why. It's not clear to me from the logs, but maybe I'm looking at the wrong logs. E.g. is this the right one? https://ci.nodejs.org/job/node-test-binary-windows-2/3579/console

@richardlau
Copy link
Member

Looks like the windows tests are filing, but I'm having trouble seeing exactly why. It's not clear to me from the logs, but maybe I'm looking at the wrong logs. E.g. is this the right one? https://ci.nodejs.org/job/node-test-binary-windows-2/3579/console

https://ci.nodejs.org/job/node-test-binary-windows-2/3579/testReport/

@watson watson force-pushed the hardening_child_process branch from 4432aeb to a671e14 Compare October 18, 2019 10:28
Ensure that the options object used by exec, execSync, execFile,
execFileSync, spawn, spawnSync, and fork, isn't susceptible to prototype
pollution.

This is achieved by copying all the properties of the options object
into another object that doesn't have a prototype.
@watson watson force-pushed the hardening_child_process branch from a671e14 to 2267b31 Compare October 18, 2019 10:30
@watson
Copy link
Member Author

watson commented Oct 18, 2019

There was an oversight in my original patch, which I just fixed and pushed. Unfortunately, that revealed that we actually do "have official support" for inheriting environment variables in the child_process module via the prototype. Support for this was added in an old PR: nodejs/node-v0.x-archive#713

Today the test manipulates the prototype like this:

Object.setPrototypeOf(env, {
'FOO': 'BAR'
});

And expects FOO to be present here:

assert.ok(response.includes('FOO=BAR'));

If we want to keep support for this (which I guess we have to if we don't want to make this a breaking change), one solution might be to keep the entire prototype chain, except the very last one that points to Object.prototype. Would that be an acceptable solution?

@gireeshpunathil
Copy link
Member

@watson -

If an attacker is able to successfully pollute the prototype

can you please elaborate a little more on the attack surface? If an attacker has access to the said objects, can't they very well initiate any arbitrary code in the process?

@watson
Copy link
Member Author

watson commented Oct 18, 2019

@gireeshpunathil

can you please elaborate a little more on the attack surface? If an attacker has access to the said objects, can't they very well initiate any arbitrary code in the process?

Prototype pollution can't by itself be used to do RCE, but given the right set of circumstances in the code base, you might be able to find a way to leverage the polluted prototype to perform RCE.

The child_process module is especially powerful as a polluted prototype will spill over into the environment variables of the spawned child processes. If the child process being run is itself a node program, this can easily be used to perform an RCE attack. For details see this deck.

@sam-github
Copy link
Contributor

I think this change needs to touch the docs. If console.log(options.whatever); child_process.spawn(options) does not actually use options.whatever because it wasn't a direct property, people need to be told.

I've mixed feelings about this. I think using prototype inheritance on options objects is fairly rare, so I'm OK losing it as a feature. On the other hand... having a node API surface that sometimes allows it and sometimes doesn't seems pretty horrid. Should we do the check everywhere?

@devsnek
Copy link
Member

devsnek commented Oct 18, 2019

Should we do a benchmark run for the child_process APIs? I assume this would hit them a bit.

@watson
Copy link
Member Author

watson commented Oct 21, 2019

@sam-github

I think this change needs to touch the docs.

Good idea. I'll update the docs 👍

@devsnek

Should we do a benchmark run for the child_process APIs? I assume this would hit them a bit.

Good point. Once we have the final version of the PR ready we should run the child_process benchmarks 👍

@Trott Trott added the wip Issues and PRs that are still a work in progress. label Oct 22, 2019
@BridgeAR
Copy link
Member

What's the status here?

@BridgeAR BridgeAR force-pushed the master branch 2 times, most recently from 8ae28ff to 2935f72 Compare May 31, 2020 12:19
@aduh95 aduh95 added the stalled Issues and PRs that are stalled. label Nov 8, 2020
@github-actions
Copy link
Contributor

github-actions bot commented Nov 8, 2020

This issue/PR was marked as stalled, it will be automatically closed in 30 days. If it should remain open, please leave a comment explaining why it should remain open.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 9, 2021

Closing this because it has stalled. Feel free to reopen if this PR is still relevant, or to ping the collaborator who labelled it stalled if you have any questions.

@github-actions github-actions bot closed this Jan 9, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
child_process Issues and PRs related to the child_process subsystem. stalled Issues and PRs that are stalled. util Issues and PRs related to the built-in util module. wip Issues and PRs that are still a work in progress.
Projects
None yet
Development

Successfully merging this pull request may close these issues.