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

Harden shell.exec by writing the child process in a source file #782

Closed
nfischer opened this issue Oct 12, 2017 · 4 comments
Closed

Harden shell.exec by writing the child process in a source file #782

nfischer opened this issue Oct 12, 2017 · 4 comments
Assignees
Labels
exec Issues specific to the shell.exec() API refactor security

Comments

@nfischer
Copy link
Member

Inspired by #524.

Exec has some security concerns, as pointed out in other issues. Due to the API contract, it's inherently vulnerable to shell-command-injection (same as child_process.exec()). Additionally, due to its architecture, it's also vulnerable to javascript-code-injection. This is because we write out a string (containing JS code) to a file and exec it.

I don't think we have any known security vulnerabilities here, but this is poor design and easy to mess up in future changes.

We should be able to improve the design while maintaining backwards compatibility by adopting the architecture in #524. We can write the child process code in a source file and pass parameters via IPC (more on that later).

This has 3 main benefits:

  1. Reduces attack surface (no JS injection)
  2. Child process code is more readable
  3. Meaningful line coverage for child process code

Care needs to be taken when passing child parameters. It's not valid to pass values on the shell commandline (shells do weird escape regarding \n, \t, etc.). I think our best option for IPC is the filesystem. I think IPC modules typically use sockets, but I'm not sure how well these perform for Windows. I think it's acceptable to use the filesystem for now, and come back later with a different IPC system as an enhancement.

@nfischer nfischer added exec Issues specific to the shell.exec() API refactor security labels Oct 12, 2017
@nfischer nfischer self-assigned this Oct 12, 2017
nfischer added a commit that referenced this issue Oct 13, 2017
This PR refactors `shell.exec()` by putting its child process in a separate code
file. This also slightly cleans up dead code.

There's more potential to clean this up (e.g. exit status), but this is a good
enough start.

Issue #782
nfischer added a commit that referenced this issue Oct 19, 2017
This PR refactors `shell.exec()` by putting its child process in a separate code
file. This also slightly cleans up dead code.

There's more potential to clean this up (e.g. exit status), but this is a good
enough start.

Issue #782
@nfischer
Copy link
Member Author

nfischer commented Oct 19, 2017

Initial PR has landed. Additional improvements:

  • Swap child_process.execSync with child_process.execFileSync in src/exec.js (this is still backwards compatible, but continues to reduce attack surface) (Use execFileSync to launch child process #790)
  • Replace codeFile with actual exit status (reduce file IO) (Remove codeFile parameter #791)
  • Consider passing parameters via CLI arg (reduce file IO, this should be OK after switching to execFileSync)
  • Watch for exceptions when calling execFileSync (exceptions might mean the command has non-zero exit status or that there was a bug running the command)

@freitagbr
Copy link
Contributor

I'm not sure replacing child_process.execSync with child_process.execFileSync will work, at least with the same API. For example, I can do this with child_process.execSync:

> child_process.execSync('echo hi; echo yo').toString()
'hi\nyo\n'

But I cannot do that with child_process.execFileSync:

> child_process.execFileSync('echo', ['hi; echo yo']).toString()
'hi; echo yo\n'

@nfischer
Copy link
Member Author

I'm referring to the call within src/exec.js. Its responsibility is "launch a subprocess to run exec-child.js". This is an implementation detail, not exposing the API.

The API depends on the call to child_process.exec() from within src/exec-child.js. That's the part which cannot be changed.

nfischer added a commit that referenced this issue Oct 20, 2017
This uses `child_process.execFileSync` instead of `execSync` to launch the child
process. This further reduces the attack surface, removing a possible point for
command injection in the ShellJS implementation.

This does not affect backwards compatibility for the `shell.exec` API (the
behavior is determined by the call to `child_process.exec` within
`src/exec-child.js`).

Issue #782
nfischer added a commit that referenced this issue Oct 20, 2017
This parameter isn't needed, we can easily rely on exit code status for this.

Eliminating the parameter reduces file IO, code complexity, and removes a busy
loop.

Issue #782
nfischer added a commit that referenced this issue Oct 27, 2017
This uses `child_process.execFileSync` instead of `execSync` to launch the child
process. This further reduces the attack surface, removing a possible point for
command injection in the ShellJS implementation.

This does not affect backwards compatibility for the `shell.exec` API (the
behavior is determined by the call to `child_process.exec` within
`src/exec-child.js`).

Issue #782
nfischer added a commit that referenced this issue Oct 31, 2017
This parameter isn't needed, we can easily rely on exit code status for this.

Eliminating the parameter reduces file IO, code complexity, and removes a busy
loop.

This also removes some legacy code related to streams.

Issue #782
nfischer added a commit that referenced this issue Nov 16, 2017
The `paramsFile` is obsolete now that we use `execFileSync()` for our
internal implementation. Instead, we pass parameters to the child
process directly as a single commandline parameter to reduce file I/O.

Issue #782
nfischer added a commit that referenced this issue Nov 16, 2017
The `paramsFile` is obsolete now that we use `execFileSync()` for our
internal implementation. Instead, we pass parameters to the child
process directly as a single commandline parameter to reduce file I/O.

Issue #782
nfischer added a commit that referenced this issue Jan 19, 2018
This reverts commit 64d5899.

Reason for revert: If stdin is large, then the param object can become
an extremely long string, exceeding the maximum OS size limit on
commandline parameters.

Original change's description:
> refactor(exec): remove paramsFile (#807)
>
> The `paramsFile` is obsolete now that we use `execFileSync()` for our
> internal implementation. Instead, we pass parameters to the child
> process directly as a single commandline parameter to reduce file I/O.
>
> Issue #782
nfischer added a commit that referenced this issue Jan 19, 2018
This reverts commit 64d5899.

Reason for revert: If stdin is large, then the param object can become
an extremely long string, exceeding the maximum OS size limit on
commandline parameters.

Original change's description:
> refactor(exec): remove paramsFile (#807)
>
> The `paramsFile` is obsolete now that we use `execFileSync()` for our
> internal implementation. Instead, we pass parameters to the child
> process directly as a single commandline parameter to reduce file I/O.
>
> Issue #782

Fixes #818
nfischer added a commit that referenced this issue Jan 20, 2018
This reverts commit 64d5899.

Reason for revert: If stdin is large, then the param object can become
an extremely long string, exceeding the maximum OS size limit on
commandline parameters.

Original change's description:
> refactor(exec): remove paramsFile (#807)
>
> The `paramsFile` is obsolete now that we use `execFileSync()` for our
> internal implementation. Instead, we pass parameters to the child
> process directly as a single commandline parameter to reduce file I/O.
>
> Issue #782

Fixes #818
@nfischer
Copy link
Member Author

nfischer commented Feb 7, 2018

I think this issue is done. I unchecked the bit about passing via CLI arg, since it turns out that's not OK (see #819).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
exec Issues specific to the shell.exec() API refactor security
Projects
None yet
Development

No branches or pull requests

2 participants