The development of applications requires the integration of several third-party dependencies for smooth application functionality and support of various features. Third-party dependencies include libraries, frameworks, or other components created by other organizations or individuals that integrate into the application. These dependencies speed up the development process, as they provide vital functions and features that can be reused and customized to suit the application's needs.
Today, most applications are powered by open-source software and libraries, made available through package managers such as NPM (Node Package Manager), Python package manager, Ruby Gems, GitHub/GitLab open-source projects, etc., to mention a few.
These open-source packages might be vulnerable to a 'Dependency Confusion' attack, which targets third-party dependencies. Threat actors inject malicious code into the dependencies the application uses, allowing them to access the application and its data. This is a severe threat, and it is essential to protect it. This blog post will look at dependency confusion and what steps can be taken to protect it.
Dependency Confusion - Understanding the Depths of the Attacks
Developers rely on existing code libraries or packages ("dependencies") to save time and effort for the repetitive tasks the libraries already exist. These packages are in public repositories like npm or PyPI, and developers can easily download them with a few lines of code.
Let’s assume an attacker can create a package with the same name as an existing closed-scope package used internally by a company or organization. Suppose the attacker's package has a higher version number, and the developer's code is configured to automatically update to the latest dependency version. In that case, the developer's code could unknowingly download the malicious package instead of the legitimate one.
For example, imagine a developer is working on a Python project and wants to use a popular package called "requests" to make HTTP requests. The developer includes the following line of code in their project:
However, an attacker has uploaded a malicious package called "requests" with a higher version number to PyPI. As a result, when the developer runs their code, the software automatically downloads the malicious package instead of the legitimate one. This could lead to security problems, such as data theft or remote code execution.
Similarly, let’s assume someone changed the original repository for any reason which left the original repository to be unclaimed. Since the developer has embedded the old repository in the code, an attacker can attempt to claim the new repository and host their malicious code. When the application attempts to fetch the library from the attacker-controlled repository, it will execute malicious code in the application.
In a nutshell, dependency confusion attacks are based on the fact that package managers may look for public code registries for a package before private registries. Accordingly, if a package exists in a private registry, an attacker could register a package of the same name with the public registry. Due to this, when a new installation/run happens, the malicious version of the repository hosted by an attacker will be pulled in, allowing an attacker to inject the malicious code.
Understanding Dependency Confusion in Depth
This code imports the "some_library" package without specifying a version number or source, making it susceptible to a dependency confusion attack.
This code imports the "some_library" package with a specific version number, which makes it less vulnerable to a dependency confusion attack. Even if an attacker uploaded a malicious package with the same name, the secure code would still download the legitimate package with the specific version number specified in the code.
Another way to make the code more secure is to use a private repository that only includes trusted packages. For example,
pip install --index-url https://myprivaterepo.com/pypi/some_library
This code downloads the "some_library" package from a private repository controlled by the developer or organization, reducing the risk of a dependency confusion attack.
However, if the attacker takes over the repository, it can still be vulnerable to dependency confusion attacks.
Finding Dependency Confusion Vulnerability
Step - 1: Performing Enumeration to find Packages in Use
Before searching for potential dependency confusion vulnerabilities, it is important to identify the packages and dependencies the target application uses are multiple ways to accomplish this:
Technology Fingerprinting: This involves analyzing the technology stack of the target application to identify the programming languages, web servers, and other technologies in use. This can be accomplished using automated technology lookup tools such as Wappalyzer or BuiltWith, which can scan the target website and generate a report of the technologies in use.
Checking Application Sources: The application source code can provide valuable information about the packages and dependencies. The developer may have included comments or documentation that list the packages in use.
Checking lock files: Most modern-day languages have a lock file containing a list of dependencies and versions. This file is typically located in the root directory of the application. Some common lock files include package-lock.json, Pipfile.lock, etc.
Information disclosure through Error Message: Error messages generated by the application may also provide information about the packages and dependencies. For example, a stack trace may include the names of packages or functions involved in the error.
Step - 2: Identifying the vulnerable package for exploitation
Once you have identified your target packages for a potential dependency confusion attack, the next step is to find a vulnerable dependency target. You can accomplish this by looking for publicly unavailable packages, expired domains, and unclaimed repositories or user profiles.
Publicly unavailable packages are once available on a public repository but have since been removed or deprecated. Suppose a dependency in your target package refers to one of these packages.
Expired domains are website domains that were once registered but are no longer actively used. If a dependency in your target package refers to a package hosted on an expired domain, an attacker could register the domain and host a malicious package.
Unclaimed repositories or user profiles are public ones created but must be actively maintained or used if a dependency in your target package refers to a package hosted on one of these repositories or profiles.
Step - 3: Exploiting the vulnerable package
After identifying a vulnerable package, the attacker must create a malicious package to further the attack. An attacker can create a malicious package in different ways, depending on the type of vulnerability they are targeting.
If the vulnerability is due to a lack of a publicly available package, the attacker may register a new package with the same name and version as the missing package on a public package repository. The attacker can then host a malicious package on this new package with the same name and version as the missing package. If the vulnerability is due to an expired domain, the attacker can register the expired domain and host a malicious package on it.
Similarly, if the vulnerability is due to an unclaimed repository or user profile, the attacker can claim the repository or profile and host a malicious package on it.
Once the attacker has created the malicious package and the target application has downloaded it, they can perform further attacks, such as executing malicious code on the target system, stealing sensitive data, or taking control of the target system.
Impact - What can an attacker achieve?
The Dependency Confusion attack can allow an attacker to inject malicious code into a legitimate application, which can have several consequences, including:
Remote Code Execution: The attacker can execute arbitrary code on the server where the application is running. This means the attacker can remotely take control of the system, run unauthorized commands, and modify or steal data stored on the server.
Backdoor Injection: The attacker can also inject a backdoor into the application, which they can use to maintain a persistent presence on the system even if the original vulnerability is fixed. The attacker can use the backdoor to remotely access the system later and perform various malicious activities such as data exfiltration, reconnaissance, or further exploitation.
Data Theft & Lateral Movement: By gaining access to the application, an attacker can steal sensitive data such as passwords, user credentials, credit card numbers, or other confidential information. Once inside the system, the attacker can move laterally and try to infect other systems on the network, increasing the scope and impact of the attack.
How to Avoid Dependency Confusion Attack?
It is essential to mitigate the risk of a dependency confusion attack. One can use the following remediations to secure against these attacks:
Version Pinning: It involves explicitly specifying the exact versions of an application's dependencies. This ensures that the application will only download and use the specific version of a dependency that it requires and will not fall back to downloading a dependency with the same name and a different version that could be malicious.
Using Client-side Verification: When a package downloads from a public repository, the client side can verify the package's authenticity and integrity by comparing the downloaded package's checksum/hash against the checksum/hash provided by the package maintainer. The client side should not use the downloaded package if the two hashes do not match.
Utilize Scopes/Namespaces: Scopes/Namespaces can be used to avoid naming collisions between private and public packages. By creating a namespace or scope for private packages, developers can ensure that their private packages have unique names and are not confused with public packages. This makes it harder for an attacker to create a malicious package with the same name as a private package.
Developer Education: Developers must know about the risks of dependency confusion and the importance of secure package management. Developers should follow best practices for package management, such as using package lock files to ensure consistent dependencies, verifying the authenticity and integrity of packages, and avoiding using public package repositories for private packages.
Using Internal Artifactory: An internal Artifactory can manage and store all dependencies in a private repository. Using an internal Artifactory, developers can ensure that all dependencies used by their applications are approved and controlled by the organization, reducing the risk of a dependency confusion attack. Moreover, internal Artifactory can also enable version control and ensure consistency across all applications using the same dependencies.
In conclusion, the threat of dependency confusion attacks is real and can have devastating consequences for any organization. As more and more software applications rely on open-source packages and third-party dependencies, the risks continue to increase.
Fortunately, there are several remediations that developers can use to mitigate the risk of dependency confusion attacks, such as version pinning, client-side verification, utilizing scopes/namespaces, developer education, and using an internal Artifactory. By implementing these measures, developers can ensure their applications are secure against attacks.
At Cobalt, we offer Pentest as a Service to help organizations identify and remediate vulnerabilities in their software applications. Our team of expert pentesters uses the latest techniques and tools to identify potential threats and provide comprehensive reports with actionable recommendations. Contact us today to learn more and protect your applications against potential threats.
References & Further Reads