A Step-by-step Guide to Preventing Dependency Confusion Attacks
Published November 26, 2023.
Third-party dependencies have become more than just a tool – they are the backbone of modern software development. Using them, you can push the boundaries of innovation, simplify your workflows, and leverage the genius of others through pre-built solutions. But, as with most good things, there's a catch. As you integrate these external components, you also open the door to potential security vulnerabilities lurking just out of sight.
Supply chain attacks aimed at libraries and other development tools are up by an astounding 633% year-over-year. Among these, the popularity of dependency confusion is rapidly climbing the ranks, posing a formidable challenge to the very efficiencies these dependencies promise. You could say the industry has a bit of a dependency problem.
This article explores how these attacks exploit the trust developers place in package managers and public repositories and, more importantly, provides a comprehensive guide on how to fortify your software supply chain against such threats.
What is a Dependency Confusion attack?
Dependencies are external software libraries, modules, or packages a software application integrates with to achieve specific functionalities. These dependencies are typically sourced from public repositories or package managers, allowing developers to leverage pre-existing solutions and streamline the development process. Unfortunately, leveraging these public repositories could expand your cyber attack surface, as their open and collaborative nature makes them more prone to cyberattacks. One such attack is the “dependency confusion.”
First identified by Alex Birsan, dependency confusion is a relatively new and novel attack within the application security world. Some package managers will look to private and public repositories when resolving a dependency. If they find packages with the same name in both repositories, they might prioritize the one in the public repository, especially if it has a higher version number. Attackers can exploit this behavior.
There are three primary vectors for executing a dependency confusion attack:
- Namespacing – By exploiting how package managers handle namespaces, an attacker can create a package in a global namespace that conflicts with a package in a scoped or private namespace.
- DNS Spoofing – DNS Spoofing attacks can happen when the package manager fetches dependencies over an insecure channel. Attackers can perform a Man-in-the-Middle attack, redirecting the request to a malicious server. The malicious repository will appear to have a legitimate URL.
- Scripting – By tweaking build scripts or messing with CI/CD security settings, attackers can trick systems into sourcing dependencies from a malicious repository.
How Dependency Confusion attacks work
Dependency confusion attacks exploit software developers' trust in package managers and public repositories. A typical dependency confusion attack may unfold as follows:
- Creating a Fake Malicious Package – The attacker then publishes a package with the same name to a public repository but with a higher version number. This fake package contains malicious payloads or backdoors within the package's codebase. These embedded elements are typically obfuscated or concealed to avoid immediate detection by static code analysis tools or manual reviews.
- Publishing the Malicious Package – The attacker then uploads this malicious package to a public repository. They often assign it a higher version number than the legitimate private package, making it appear as an "update" to the genuine package.
- Malicious Code Execution – The attack's crux occurs during the target application's build or deployment process. When the package manager attempts to resolve and fetch dependencies, it gets deceived by the versioning trick and pulls the malicious package from the public repository. Post-installation scripts embedded within the package may then be automatically executed.
- Exploitation – With the malicious package integrated into the application, the attacker can exploit the embedded vulnerabilities. This exploit ranges from unauthorized data access, system control, or even using the compromised system as a launchpad for further attacks within the network.
7 Tips to Prevent Dependency Confusion Attacks
1. Implement Strict Naming Conventions
Adopt a standardized naming scheme for all internal packages by using company-specific identifiers, detailed descriptors, and a systematic versioning approach. For instance, a naming structure such as “orgname-functionality-version” can offer clarity and reduce the chances of mistakenly integrating an external package with a similar name.
In the context of package management systems like npm, it's beneficial to proactively reserve specific namespaces that align with your organization's identity. However, you will need a different strategy for platforms like PyPI, which currently lack a namespacing feature. Here, it's advisable to preemptively claim or "namesquat" the titles of your private packages in the public domain.
2. Proactive Monitoring and Dependency Scanning
Embed continuous dependency scanning and monitoring mechanisms like Software Composition Analysis (SCA) tools within your CI/CD workflow. With Jit, you can easily activate robust SCA tools, such as Npm-audit, OSV-scanner, and Nancy, directly within your GitHub environment.
You can automate these tools to run on a recurring schedule and manage them within a single dashboard, getting enriched reports and real-time remediation suggestions for every new vulnerability. Remediation is available in Pull Request or via Jit’s Actions page, as shown below. In this case, you simply click the “Create a Fix PR” button, which will generate a new Pull Request and introduce the fix code so you can apply the fix in your GitHub environment.
3. Use Private Package Repositories
Opt for private repositories with stringent data access controls where only specific roles or individuals with the appropriate credentials can access, upload, or modify the packages. These repositories should enforce multi-factor authentication (MFA).
Popular options for private repositories include GitHub Private Repositories, GitLab Private Repositories, Nexus Repository OSS, JFrog Artifactory, Docker Hub Private Repositories, and Bitbucket. These platforms offer enhanced security features tailored to organizations prioritizing code and package security.
4. Enforce Package Whitelisting
Incorporate strict package allowlisting mechanisms within your CI/CD pipeline. Automated tools or scripts can scrutinize every dependency introduced or updated during the build phase. These tools should cross-reference each package's name, version, and possibly its checksum or digital signature against a pre-defined approved list.
If a package doesn't match the criteria or isn't found on the allowlist, the CI/CD pipeline should immediately flag it and halt the build or deployment process. This interruption is a protective measure, so potentially harmful or unvetted packages don't make their way into the production environment without a thorough manual review.
5. Implement Strong Access Controls
Adopt a robust access control strategy by integrating Role-Based Access Controls (RBAC) within your package management and repository infrastructure. Doing so lets you define specific IAM roles and assign granular permissions associated with each function.
For instance, you can restrict package publishing or modification rights solely to senior developers, DevOps teams, or other trusted personnel. Junior developers or external contributors might only have read access or require multi-factor authentication for specific actions.
This hierarchical access model shields critical packages from malicious changes, which reduces the risk of introducing vulnerabilities or compromised dependencies into the codebase. Detailed access logs can also help you swiftly detect and address any unusual or unauthorized activity.
6. Package Signing and Verification
Embrace cryptographic measures by signing your packages using private keys. This process embeds a unique digital signature within each package, acting as a seal of authenticity. When the package is fetched or integrated into an application, its embedded signature should be cross-verified against a trusted public key corresponding to the private key used for signing.
This practice also deters attackers from introducing rogue packages or tampering with existing ones, as any such attempts would break the signature validation.
7. Version Locking
Leverage the power of lock files to maintain consistency and security in your dependency management. Files such as `package-lock.json` in the npm ecosystem or `Gemfile.lock` in the Ruby world are snapshots of the exact versions and configurations of dependencies your project relies on.
By locking down specific versions, you prevent the accidental introduction of newer versions of packages that might contain vulnerabilities or untested changes. Version locking is especially crucial when specific packages might introduce breaking changes or when more recent versions haven't undergone rigorous security vetting. Lock files also provide a clear record of which versions of dependencies are in use, making it easier to audit, review, and roll back if necessary.
Safeguarding Your Digital Assets
As we've seen, dependency confusion attacks are a lurking threat. Companies often harbor a false sense of security regarding their third-party dependencies. However, today's risk landscape requires a serious, zero-trust approach. This means that even open-source components must be verified as secure, continuously monitored, and updated regularly.
Jit’s DevSecOps orchestration platform helps you confront these dependency challenges. With its intuitive tools and real-time insights, Jit seamlessly integrates into your CI/CD pipeline, ensuring your software's defenses are always up-to-date. Activate powerful SCA tools like npm audit, OVS Scanner, and Nancy to fortify your software like never before. Explore more here.