Lessons learned from the ua-parser attack: Protect your organization

Lessons learned from the ua-parser attack: Protect your organization

The JavaScript library ua-parser-js sparked vigorous security activities over the weekend, as three malicious versions were published to the public. The hijacked package containing malware was the result of a maintainer account takeover. Once again igniting a debate and highlighting the need for more security focus in the JavaScript (and other) ecosystems.

The library, used to detect browser and user data, has close to 8M weekly downloads by developers around the world and is used as a dependency by 1200+ other packages in the public npm registry.

This critical security issue teaches us two major things for the npm ecosystem:

  • The dependency tree comes with security risks. Your direct dependencies might not be malicious, but your dependencies of direct dependencies may be targeted. These transitive dependencies often range in hundreds and are large weak points.
  • Organizations need to expand the scope of security and protect more than the CI/CD. Developer environments are often more numerous and more difficult to control, making it a more probable target to be compromised by malicious packages.

In times where ransomware is common, it’s more important than ever to protect your whole organization - controlling what packages are allowed in your environments.

The UA-parser-js incident in short

Three malicious versions (0.7.29, 0.8.0 1.0.0) of the popular library ua-parser-js were published on October 22, 2021. The versions were available as the latest major, minor and patch versions between 12:15 — 16:27 UTC (~4 hours) before all 3 versions were deprecated, unpublished and superseded by new non-malicious versions.

Users that installed npm packages during that time may be affected and should scan their environments for the malicious versions. Users that added an affected version should consider their whole system fully compromised. According to user reports, the malicious packages installed both a trojan and a crypto-currency miner.

See the security advisory for more details.

Case-study: Compromised without you even knowing it

Joe, the junior developer, helps his team with application testing over the course of the weekend. His environment is clean, so he diligently installs the required dependencies for the application using npm install.

The app only has a few direct dependencies, but with all the transitive dependencies he ends up adding over 700 dependencies. Unfortunately for Joe and his company, it’s already too late as he unknowingly installed a trojan into his environment…


This scenario demonstrates how easy it is to install npm dependencies with package managers, and what can go wrong when packages are allowed to execute arbitrary scripts as part of the install process.

Unlike when dealing with known vulnerabilities, malicious packages are a big threat for your development environment where potential user data, passwords and sensitive information is stored and therefore can be stolen by hackers.

Dependency tree and levels of dependencies

Npm dependencies in web applications are specified in the file package.json. Looking at the specifics of the ua-parser-js attack, users installing any package with a dependency resolution like ua-parser-js: ^0.7.xx would have gotten a malicious version (0.7.29) at the time of the incident. That’s because the dependency resolution tells us to fetch the latest 0.7 patch version - unless remediated by factors like locked dependency versions.

So who was affected? Projects depending directly on ua-parser-js were obviously at risk. But in the typical case the compromised dependency was added as a transitive dependency (dependency of a dependency). Leading to users working with popular libraries and frameworks like react and angular reporting inclusion of the compromised library.

# Example of dependency tree where ua-parser-js is included as a transitive dependency. 'npm ls' is used to identify the path for a specific dependency

$ npm ls ua-parser-js

yourproject@1.0.0

react@15.7.0

└─┬ fbjs@0.8.18

└── ua-parser-js@0.7.30

# Excerpt from fbjs@0.8.18 where ua-parser-js is included as a dependency

"dependencies": {

...

"ua-parser-js": "^0.7.18"

},

# ua-parser-js: ^0.7.18 would resolve to the latest 0.7.x version. Installing a compromised version with malware during the time of the incident.

With transitive dependencies, the number of packages your projects depend on increases dramatically. To the point where it quickly becomes impossible to fully grasp what and how many dependencies your team uses - unless you use appropriate tooling.

The aftermath after ua-parser-js made one thing apparent. The biggest issue wasn’t figuring out if your applications used ua-parser, instead it was trying to figure out if you were exposed - in any environment, in any way, across hundreds of developers. A task a lot of companies worked vigorously with, as they lacked proper control over packages that enter their environment.

How can Bytesafe help? Control dependencies for the whole organization

Avoiding similar issues in the future should be a priority - and any investment into proper protection would save time and money in the long run.

So, the million dollar question - how do we avoid this in the future? We can mitigate most of the issues by inserting control over dependencies and the patch management process.

  1. Avoid unintended dependency version changes
  2. Use a single source for dependencies
  3. Continuously monitor dependencies

Locking dependency versions

Pinning inexact dependency versions grants flexibility for the ecosystem - but comes with inherent security risks. Having consecutive npm install yield slightly different and non-deterministic results is not desired in neither CI/CD nor dev environments.

Dependency versions should be updated with intention and not as a side effect. To comply, organizations should put in place a process that updates, commits and reviews project lock-files and makes sure every subsequent installation (and user) uses the files.

Using lock-files (package-lock & yarn.lock) together with npm ci for complete and deterministic installations introduces the necessary friction that make updating dependency versions a controlled process.

Organizations requiring an extra safe-guard against user error can freeze versions in a registry at a desired safe state with Bytesafe’s Freeze Policy. Temporarily disallowing updates from upstream sources, allowing to unfreeze when needed for a controlled patch management.

For more information on how to work securely with projects, locking versions and getting deterministic results, refer to Using per project registries in the documentation.

Using a dependency firewall

Depending directly on public registries and countless GitHub repositories, instead of using a single package source, quickly makes control over the flow of dependencies an impossible task. With multiple different sources, how are you going to make sure dependencies comply with your business policies, that they are safe and contain approved licenses?

The solution: a single hub like Bytesafe to enforce rules and monitor the flow of dependencies - for every developer, tester and build system.

Bytesafe is intentionally built for you to be able to have multiple registries, one for your every use-case. This segmentation allow you to separate the dependencies you use for every application and new release. With this, organizations can easily identify what dependencies have been used and where.

To make sure everyone is using the same registry source, projects should include a .npmrc config file and package-lock.json or yarn.lock files that define what registry to use.

# Example .npmrc config setting the default registry to be used by npm clients

registry=https://workspace.bytesafe.dev/r/example-registry/

Let every user in your organization have secure access to Bytesafe. Add or publish dependencies, create registries and work on issues - securely as a team.

Identify and track problems with issues

Bytesafe continuously monitors the dependencies in your supply chain to identify vulnerabilities, as well as malicious and non-compliant packages. Issues are opened for any problem detected providing you with key details on vulnerabilities, problems and license issues - so you can review and remediate easily.

Key information includes:

  • when the version was added to Bytesafe
  • affected registry
  • What event (and user) that included the problematic version

Track activity for security issues

Notifications for issues are sent in-app, through email and Slack. Making sure you never miss security critical information.

Defend your supply chain with automatic quarantine of threats

What happens when it’s no longer a developer installing dependencies, but rather an automated environment? A key component of modern security tooling is to make sure threats are actively blocked, even if no human is actively monitoring.

quarantined-package-in-bytesafe

Whenever a critical vulnerability is detected you want to take immediate actions. Making sure your teams, environments and business are protected - and your software supply chain can remain secure.

Bytesafe Quarantine allows you to automatically block the use of specific packages that surpass security threshold levels, for example critical advisories like ua-parser-js. Allowing for a conscious decision if you decide to release a version from quarantine after review.

Read more in our post on using Quarantine in your dependency firewall.

Most package maintainers do not use 2FA to protect their npm accounts

As little as 10% of all npm maintainers used two-factor authentication (2FA) to secure their accounts in 2020. Making account takeover a very real threat. A supply chain attack that gives premium access to any environment that depends on it and considers it a trusted source. Make sure you are protected.

We are here to help

Dependency security can be a complex topic - but luckily Bytesafe can help you keep unwanted dependencies out of your organization.

If you have any questions or need guidance, feel free to contact me directly at andreas.sommarstrom@bytesafe.dev.

Thanks for reading!