Open rotem-cider opened 2 years ago
How is this any different than running npm run foo
and defining the "foo" script to run insecure code? If someone has control of the local filesystem, they can always run whatever code they want (albeit with constrained permissions)
I would argue that to be true if it wasn’t a CI command. Even if this isn’t changed, it’s a good thing to document.
Hey @ljharb , this is different in the case where the developer does not control the ci environment.
in a scenario where I know builds will run with “npm ci —ignore-scripts” (so all the normal scripts won’t run) then the developer should not be able to force the build scripts to execute his code.
because of this scenario the developer can always bypass with this method and execute his own code.
if for instance in the pipeline there was a npm run foo, then the developer would be able to change the foo script and run it. Then best practice would be to run it in a subshell without any env variables which may leak.
worst in npm6 any command can be hijacked, so even “npm publish” that usually has write permissions will execute the developers code
I agree with @rotem-cider.
While I'm aware this is a feature, a user who's unfamiliar with .npmrc
files may run npm x
and the shell will be executed.
The major difference between having a malicious script foo
and this case, is that foo
will run iff one explicitly runs the foo
script(npm run foo
), while here even a basic npm ci
will result in RCE (regardless of --ignore-scripts
specification)
This is unexpected behavior for the untrained eye.
while I know that scripts can be called using the pre/post hooks, they can be disabled using
--ignore-scripts
. In the custom binary case the flags are not respected
https://docs.npmjs.com/cli/v8/commands/npm-ci#ignore-scripts
The way I read this is that is lifecycle script behavior is undefined according to the docs.
I found this issue while reading the article at https://snyk.io/blog/visibly-invisible-malicious-node-js-packages-when-configuration-niche-meets-invisible-characters/
That reminds me of the log4j CVE labeled as RCE which was an ACE and was about something similar (configuration file changes or code changes could allow arbitrary code execution).
I agree with @ljharb.
if for instance in the pipeline there was a npm run foo, then the developer would be able to change the foo script and run it
How is this different from simply changing the code in your own fork or with GitHub Codespaces?
There are some security recommendations regarding pipelines.
Also on GitHub Actions you can define some further settings (who is allowed to run the actions, which actions are allowed to run, who can run the actions for PRs, ...).
Further information: https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#good-practices-for-mitigating-script-injection-attacks https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#modifying-the-contents-of-a-repository
https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs-or-python
I didn't verify this but shouldn't the highest verbosity level of npm make such tricks visible?
@DanielRuf you have some valid points but are reliant on the fact that all companies work with strict configurations.
First about log4j, it is a full easy to create and exploit remote code execution which affected thousands of companies by default configuration. I think the comparison was wrong, also that this vulnerability is not critical like log4j but very important.
About the ability to code execute in the pipeline, as the pipeline is a very sensitive area which directly controls what will be in our final production build but also has Access to many secrets a company should protect it with proper security measures
adding the security measures you mentioned In GitHub is super important, especially to make sure no untrusted developer can execute code in secret aware areas in the ci.
Unfortunately these measures are not always used, not enough and companies have many different ci systems which have different security measures (jenkins, circleci, codefresh, …)
as for the GitHub security measures:
When separating properly the different actions and giving them proper permissions we can avoid such issues but when we have a wildcard that allows users to submit a file and wait for shell our job as security engineers gets much more complicated
you can read more implications about different perspectives of attacking the ci in Omer’s excellent blog post here - https://www.cidersecurity.io/blog/research/ppe-poisoned-pipeline-execution/
@rotem-cider if you use a dependency's code, you are trusting it. You run your dependencies' code in dev, CI, and often prod. Someone who wants to use this as an attack vector can also just write code to do it, instead of relying on npm to do it - and npm typically never runs in prod, making it safer than "node itself" in this regard.
@rotem-cider regarding log4j, I do not mean the original CVE but this one: CVE-2021-44832 (ACE caused by manipulated configuration, where attackers need access to your sourcecode)
Regarding the other points: you can always make it more strict, please see my links and screenshots.
Still, someone who has access to the sourcecode can do anything. That has nothing to do with npm and any other program with a config and even env variables can be theoretically abused. This is by design and technically no real vulnerability.
I think the nuance here, at least with regards to Aviad's article on employing both trojan source and configuration overwriting is that this doesn't have to be a code/dependency issue at all. If a collaborator is able to somehow trick you into making a change to the project's .yarnrc / .npmrc and also to somehow sneak in the malicious code as part of the repository's source code then they put the project maintainers at risk.
I do agree that at this is however not much of a higher elevated risk than anyone just proposing a pull request with malicious code/intent behind it. That said, the interesting use of trojan source attacks (invisible characters, etc) for high-impact insecure defaults is quite interesting. At the very least, this reminds me my research about lockfile insecurities which will mostly go unnoticed to anyone.
So, to be more practical, I'd ask not whether we want to spend more time to convince each other if it's a vulnerability or not, but what actions can we drive forward to advance security for end-users. Are there changes we can drive to package managers with regards to secure defaults?
That still requires us to agree on what defaults are more secure. What do you suggest here?
Rereading the first post I wonder how this would help when someone would export or create an alias (using bash files in the repo, for example for husky / git hooks, ...) to use some other binary than the original npm or git, which would ultimately lead to the same outcome (and is harder to track, at least environment related stuff, and could remove itself from the file using sed or some self-destruct method).
For any solution I think about (disable loading of config files, disable git usage, disable programmatic usage of npm) there are other ways to achieve the same. GitHub and others would need some "protected files" feature similar to "protected branches" - but maybe it's too similar to the code owners solution (https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners).
Or maybe this for package managers:
That still requires us to agree on what defaults are more secure. What do you suggest here?
One idea I can think of is as follows:
yarnPath: “./evil.sh”
also includes a hash signatureNot saying that's ideal, but potentially adds a bit more friction before users are executing arbitrary commands automatically.
What is “yarnPath”?
yarn-path "./bin/yarn"
Instructs yarn to defer to another Yarn binary for execution. Useful if you want to bundle Yarn into your repository and have everyone use the same version for consistency. This was introduced in Yarn 1.0, so all developers must have Yarn >= 1.0 installed.
Value must be a relative file path, or false to disable (default).
Source: https://classic.yarnpkg.com/lang/en/docs/yarnrc/#toc-yarn-path
This is npm’s repo.
I was "riding" on the example that Daniel pointed out from the Snyk article on trojan source + yarn config. Similar issues exist for npm, as Rotem pointed out in Cider's article.
ah, i hadn’t read the links. What would be a concrete example for npm that wouldn’t be outweighed by the DX cost? (For example, retaining and updating checksums any time scripts are edited is typically untenably frictionful)
For me the main issue here is when an organization is separating permissions between regular developer capabilities to Devops capabilities bypasses to execute code are very easy by using these methods
Aliasing the npm or any other tool needs command execution permissions.
The jump from static configuration files which we do want to give the developers control over and allowing them to execute code in sensitive ci/cd environments can be dangerous.
codeowners is a nice mechanism but does not control if the pipeline should run, but more if it should require approval after it already ran.
Some suggestions just throwing ideas here:
You could certainly build a system that required approval before running a pipeline; that’s well beyond the scope of a cli tool.
Hashing the config file doesn’t solve the problem unless you add an untenable amount of friction on top; options 1-3 there have nothing to do with npm. What default configuration are you thinking of in option 4?
About the 4th option, a big part of the problem is that cli tools pick up the default configuration file from the current working directory. This has many pros and almost all cli tools do that. But it does introduce the option for unexpected configuration overrides.
The solution here can be to deprecate the picking up of .npmrc automatically and add the need to specifically request it’s usage by adding a flag
As this is a breaking change, I think if going down this road will need to do it in multiple phases.
first add a warning to the cli that it may pick up unintended configuration files while recommending users to select a specific configuration file
Then next phase can be to break if no config file or more practically stop processing certain dangerous commands if configuration file is not explicitly set.
Unfortunately I think this will be a complicated decision in this stage
It's more than just a breaking change; it's far too much friction - especially since, as you indicated, virtually every tool does this by default, which means that's what users almost universally expect.
I agree, maybe just adding a warning when executing will help with user education and a comment in documentation and revisiting this in next major version
see similar changes done in other tools
https://github.com/bridgecrewio/checkov/commit/d5ca203b586582f77e227c2757d77f23f1ab8cf7
Sure - but "a file in the repo" is a trusted source.
first add a warning to the cli that it may pick up unintended configuration files while recommending users to select a specific configuration file
Normally they are merged / combined.,
I agree with @ljharb that this is too much friction. We have config files on a few more levels like the user, the global, project root, subfolder (monorepo) and so on.
If some of your coworkers introduces a malicious change in your code and commits this, then you have a bigger problem and this is people-related - a malicious actor who has direct access to the sourcecode can do anything in theory. Code owner configurations, protected branches, merge request policies, CI policies and so on mitigate some scenarios.
Budging in here 👋 [disclosure - I wrote the article @DanielRuf refd to]
What if we put a verification system behind a flag(?) and if the dev opted in - then perform the verification?
assuming opted in - all opts start falsy, and we can allow a granular pick of features
ie:
{
"strictRC": true, // <-- the feature flag
"rcOpts":{
"allowCustomBinary": false,
"allowCustomGitBinary": true
}
}
// disregard namings ;)
this way it's: 0/ less to no friction (behind a flag the defaults to false) 1/ allows granular control over rc files (regardless of which one was picked) 2(optional)/ allows future introduction of this system as a breaking change as a double security gate (rc file + explicit allowance of feature)
As long as your environment (bashrc, profile, ...) is there you can bypass these "checks" by pointing the aliases / binaries (git and more) to something else.
Is there an existing issue for this?
This issue exists in the latest npm version
Current Behavior
Intro
NPM is a package manager used in our CI/CD environments, We are using it to download and install all our different dependencies inside our building phase for production.
In order to use it safely, we use some precautions, We try to minimize as much as possible its exposure to secrets but still sometimes we have no choice but to add them.
Reading the documentation here and best practices using the cli around the internet it is best to run in ci environments with
npm ci --ignore-scripts
which should not run any scripts, additionally, we saw that npm sees bypassing this command as a high severityResearching this subject we found out that a developer or malicious actor with access to the codebase can in fact force npm to run scripts although we configured it explicitly to ignore scripts. Talking with the bounty team the response was that this is not eligible for bounty and is not considered a risk as this is the intended behavior of npm.
We agree that this is a design issue, but nevertheless, this can be used as an attack vector by actors, we want to raise the issue here and understand what mitigations can we have in the meantime, and if this design will be changed in the near future?
The Issue
When adding a .npmrc file to our repo, npm will pick up the file and read its configuration. This means that any command using npm will automatically pick up the configuration file.
It is possible to inject a configuration that will then execute code in the installation context and attempt to attack the installation environment by two vectors:
git=${PWD}/rce.sh
to .npmrc and adding an installation from git to the package.json, such as"dependencies": { "ini":"git://github.com./npm/ini.git#v2.0.0" }
will execute the rce.sh script when runningnpm install
version 7,8onload-script=${PWD}/rce
to .npmrc will invoke the rce.js script in the library when running ANY npm version 6 commandExpected Behavior
As we are not expecting to run any scripts when running with "--ignore-scripts" this is can and will affect our devops installations if a malicious actor gains access to parts of our codebase.
I understand that this was not the intended behavior of the flag, but it became the only security measure in use and implies that there will be no unintended scripts running
Steps To Reproduce
Using npm version 7,8
Attack phase
Using npm version 6
Attack phase
Environment
; "project" config from fuzz/npm-lion/.npmrc git = "npm-lion/test.sh"