The Risks of Dependency Confusion Breaches and Safeguarding Your Projects
April 21, 2023 No CommentsBy Uzair Nazee
Dependencies are a great way to reduce the overall development time of a project by using someone else’s publicly hosted code that does the task quickly and efficiently rather than reinventing the wheel. An added advantage is popular packages are actively maintained. This means new features and bug fixes are continuously undertaken and released, so a chunk of your software is up-to-date and robust by just updating the dependencies. Isn’t that amazing?
Tradeoffs
If dependencies are such great icebreakers in the development cycle, is it a wise decision to prefer using dependencies over writing modules/code in-house? Well, not really, for several reasons.
Firstly, not all the packages are well-written. A few packages meet just the bare minimum, while others may be well-written but not bundled well, so they end up consuming more space than needed. Since public packages are open-sourced, there is always the risk of security which is also prone to software supply chain attack, which we will explore in detail below.
Finally, not all are actively maintained. Packages published and not updated for a long time might not just be vulnerable but might also have become outdated with the ecosystem (for example, CommonJS v/s ESModule in JavaScript) over time.
The Rabbit-Hole of Dependencies
A package you happen to use might internally be using other dependencies that might further be dependent on other packages. This chain of dependencies is usually tracked via a dependency tree. In the JavaScript world, you can auto-increment the dependency’s patch or minor version using the symbols ~ and ^.
But which of the two should you use? Or should you pin the dependency and not make updates at all? What are the outcomes? Given that you are working on a big industry project that uses 100s if not thousands of dependencies, tracking all these versions gets really tedious and might lead to dependency confusion.
Dependency Confusion Attacks
When I heard the phrase “dependency confusion attack”, I assumed it was an attack that results from wrongly configuring dependencies, like using an outdated package. However, this attack happens during the dependency installation/build time when hackers exploit private packages. It wouldn’t be wrong to consider such attacks as a subset of software supply chain attack.
Typically, organizations use private packages that are local to the organization and are inaccessible to developers outside the network. These packages are unavailable on the public package registry (npm in the case of node packages and PyPI for Python).
However, attackers then create a public package containing malware with an identical name with an incremented minor or patch version. So, if you have dependency version auto upgrade enabled, the system downloads the public package thinking it’s the same as the private package when dependencies are being installed during the build process. This way, the attacker can successfully break into the internal systems and implant a backdoor in the execution environment or implement a security threat.
While uploading malicious software libraries to the public registry is one way to perform dependency confusion attacks, other methods include DNS spoofing and manipulating build scripts in the CI/CD pipeline.
Mitigation and Workarounds
In general, the key to enhancing cybersecurity is preventing dependency confusion vulnerabilities. Unfortunately, since it impacts every programming language’s package management, including JavaScript’s npm, Python’s pip, Ruby’s Rubygems, and Java’s Maven and Gradle, there isn’t a single solution that can eliminate all potential substitution concerns. Instead, there are certain recommended practices that can be used to reduce dangers.
To begin with, pinning dependencies is a simple measure to avoid dependency confusion attacks. Freezing the version of dependencies enables iterative installations and finer-grained control over dependencies. Additionally, dependencies of dependencies can also be pinned using force resolutions via preinstall.
The way this works is that by adding the preinstall script, the sub-dependencies’ versions are locked to what is mentioned in the resolutions property.
Another simple workaround is to make use of namespaces and scoped dependencies of what’s commonly known as scoped packaging.
It is suggested to impose a policy that forces developers to only reference declared namespaces or scope names when loading packages to guarantee that all components are retrieved from reputable repositories.
Further, to avoid such attacks in the integration pipeline, one can build a secure build environment with restricted permissions and vulnerability monitoring. By doing this, the likelihood that attackers may introduce harmful dependency routes into build scripts and CI/CD configurations or pull in remote transitive dependencies during a build phase will be reduced.
Next, package installers need to support client-side verification, like a hash-checking mode that compares all downloaded packages to a client-side hash. With constantly changing dependencies/versions, this can be challenging to automate, but if a clear list of dependencies is established, you can use your package manager’s support to lock files and automate hash checking. Such integrity validation improves security posture while deterring vulnerabilities that require control of the repository on server and client machines.
Conclusion
Dependency confusion preys on the current application architecture’s need for modularity. These attacks are relatively easy to plan because they don’t require attackers to have privileged access or specialized tools. Furthermore, when attackers fool continuous delivery builds into retrieving and installing malicious versions of software packages, the victim user need not take any action to stop the attack.
Since a large number of enterprises rely on dependencies and open-source packages, organizations need to have a strong construct of security, ranging from thorough validation of installed packages to having a secure build environment. After all, attacks like dependency confusion are caused by literally creating “confusion”, in addition to all the security checks, doing a thorough dev-testing would always help.
Sorry, the comment form is closed at this time.