In their inaugural report, the Station 9 research team explores the complexities of open source dependencies and the top security considerations for open source adoption at the enterprise.
The software industry is in turmoil. Vulnerabilities in widely used open source
components continue making headlines, and so do an increasing number of software
supply chain attacks, where adversaries try to sneak malicious code both onto
developer and end-user systems. Remarkable incidents include the now infamous
Log4Shell vulnerability disclosed in December 2021, and protestware published in March
2022, where open source maintainers weaponize their own project to express a political
Addressing above-described supply chain security risks requires an understanding of the state of dependency management in modern application development. To this end, we chose the Census II report as a starting point, a data set meant to contain “the most widely used FOSS deployed within applications by private and public organizations”. Published in March 2022 by the Linux Foundation and Laboratory for Innovation Science at Harvard, it’s been created on the basis of scan data provided by several commercial Software Composition Analysis (SCA) vendors.
As consumers and maintainers of open source software realized the risks associated with its
use, several initiatives started identifying critical projects. The Census II report is one such example. Another is the OpenSSF Criticality Score, which computes a score for any Git repository hosted on GitLab or GitHub based on data such as age or the number of contributors. Comparing those initiatives, however, reveals that it is anything but straightforward to develop an algorithm that determines general project criticality: Many projects highlighted by Census II receive relatively low criticality scores.
The OpenSSF provides tools and guidance to help strengthen the security of major OSS projects. However, organizations consuming OSS are responsible for addressing security risks according to their specific circumstances, and must evaluate for themselves - what is critical.
There is little overlap between what Census II considers critical, and OpenSSF criticality scores, as shown by the comparison on Chart 3. The Alpha-Omega project considers both Census II and OpenSSF Criticality Score - which will help to provide coverage to many projects. Initiatives such as those from the OpenSSF go a long way to determine which OSS projects are “critical”, but OSS consumers keep overall responsibility and need to address security risks according to their specific circumstances, e.g., deployment model, legal and contractual obligations or risk appetite.
In the early days of cloud adoption, the concept of the “shared responsibility model” for cloud security emerged. In the world of open source software, the model would be a different. When an organization chooses to rely on an open source project, they take responsibility for how that code might affect their security posture. While many maintainers hold their own projects to high security standards, they have no obligation to do so as per the license terms. Fortunately, open source foundations like the OpenSSF, CNCF or OWASP, step up to secure the way today's software is built by advocating security best-practices or developing security tooling.
The version of the “shared responsibility model” in open source security is a trust-based partnership between the organizations that rely on OSS, and initiatives such as the OpenSSF, CNCF and OWASP, who strive to improve the overall security posture of major open source projects.
Dependency relationships between open source packages are intricate – this has been shown in numerous academic studies, and is no different for the Java packages mentioned in the Census II report. A component depends directly on another component if there’s an edge between their corresponding nodes in the dependency graph (solid edges in the chart). In the case of transitive or indirect dependencies, two components are only connected through other ones, e.g., application App transitively depends on P2, thanks to the direct dependencies of App on P1 and P1 on P2 (dotted edges).
The latest versions of the 254 packages analyzed have an average number of 14 dependencies (direct and transitive), which is less than what has been reported for other ecosystems, esp. npm (which reports 77 dependencies on average), whose inflated dependency trees are due to the phenomenon of micro-packages. However, considering that a typical application declares several direct dependencies, they easily end up with a total of dozens if not hundreds of dependencies.
The average depth of those dependent trees is 2, just like the distance of P1 and P3 in the picture above, and the maximum depth is 7.Six outliers stand out with more than 100 dependencies each. Among those are aws-java-sdk v1.12.316 from Oct 4, 2022 with a total of 331 dependencies and log4j-core v2.19.0 from Sep 13, 2022 with 141 dependencies. Even though the majority of those is used for testing - who would have thought before Log4Shell that logging comes with such complexity?
50% of the most used Census II packages didn’t have a release in 2022, and 30% had their latest release before 2018 - these can cause serious security and operational issues in the future. Also, 25% of packages that saw releases in 2022, still have up to 18 vulnerabilities!
In total, roughly 95% of the vulnerable dependencies are transitive ones from the perspective of the application (level 1 or greater in the bar chart), which makes it difficult for application developers to assess whether a given vulnerability in such transitive dependency is reachable and exploitable in their application context. Such assessments are required for the simple reason that not all of the code contained in the many components pulled into a development project is actually executed and needed in a given application. The recent vulnerability discovered in Apache Commons Text, for instance, only matters for an application if the vulnerable class StringSubstitutor is used (by the application itself or in any of its dependencies). But how could a developer ever know whether that is the case for hundreds of dependencies?
Reaching vulnerable code is a prerequisite for exploitability, and early studies of the phenomenon of software bloat in Java applications indicate that a significant share of code pulled into a project is not used at all in its context, sometimes entire packages. Continuing the example introduced before, vulnerable methods in P2 and P3, highlighted in red, can be fixed with lower priority if they cannot be reached from the application code.
Timely patches are key to prevent systems from being exploited. One study showed that 37% of 11,079 public exploits in Exploit Database were available before (0-days) or within one week after a patch was released, and 80% of those were available before the corresponding CVEs were published. Another observed that “75% of exploit code is observed within 28 days”. CVE-2017-5638, a severe vulnerability in Apache Struts that led to the Equifax data breach, is another example demonstrating that public vulnerability disclosure and large-scale, automated exploit attempts happen within a range of a few hours only. But be careful with updates: 9% of vulnerability fixes will require a major update, which by definition will add a breaking change. 44% will require a minor change, but studies show that since not everyone follows semver very closely, these updates are just as likely to cause outages.
A particular case of updates are semantic updates: those are not API incompatible but modify the updated code’s contract, for example, by fixing bugs that change the results returned for the same input. While, in theory, tests should be able to catch those changes, a recent study of 500 OSS Java projects found that tests only cover around 50% percent of calls in direct dependencies and only 20% of the calls in transitive ones. The same study advocates static analysis as a way to mitigate the issue of semantic updates, raising the number of detected semantic updates to 70% on average.
But what if no patch is available yet? Chart 16 shows how many weeks lie between CVE vulnerability publication and the release of a corresponding patch. Numbers highlighted in red indicate vulnerabilities that were published in the different weeks before the patch was available, those in green were published in the weeks after patch release.
This report is a result of long weeks of research. Rather than (only) looking for attention-grabbing headlines, we wanted to understand what’s driving the biggest challenges for development and security teams today, and how those challenges can be mitigated. The most significant emerging challenges can be broken down into three main categories:
Today, the industry is focused on known vulnerabilities (CVEs) as an indicator of security. This has led software composition analysis (SCA) tools to drown developers in an endless stream of security alerts. After getting these alerts from security teams, developers must evaluate whether or not vulnerable code is actually reachable, or if the vulnerability is actually impactful. This slows down development considerably, as developers spend much of their time investigating and fixing vulnerabilities, and not writing value-adding code.
Most of the major supply chain attacks that have used OSS as their vector, or target, would not have been caught by looking at CVEs. Attacks like Typosquatting and Dependency Confusion target the maintainer, or the method in which OSS packages are consumed. In these cases, the focus on known vulnerabilities, while important, is not helping to enhance security.
80% of code in modern applications is open source code, and as the report finds - 95% of vulnerabilities are found in transitive dependencies. Most security threats, including known vulnerabilities, lurk within the sea of transitive dependencies. The challenge is that developers rarely have visibility into their dependency tree, or how deep it goes.
Our conclusions on how to mitigate these issues are the same as the one that drove the core technology behind Endor Labs - Program analysis is required throughout the dependency management lifecycle. Program analysis can grant security and development teams a deep understanding of how code is actually being used. Without that understanding, teams will continue to struggle with the selection, security, prioritization, and maintenance of dependencies.
The full report includes expanded research on all topics mentioned above, as well as a look at next-gen supply chain attacks and a full reference list.