Visualizing the Impact of Call Graphs on Open Source Security
Call graph visualizations can support developers in the assessment and mitigation of open source vulnerabilities. This blog post demonstrates this use case by drilling down into a voluminous call graph until we clearly see the invocation of vulnerable open source methods by a given client. Understanding this invocation context helps developers understand the relevancy and impact of open source vulnerabilities and how to mitigate them.
Call graph visualizations can support developers in the assessment and mitigation of open source vulnerabilities. This blog post demonstrates this use case by drilling down into a voluminous call graph until we clearly see the invocation of vulnerable open source methods by a given client. Understanding this invocation context helps developers understand the relevancy and impact of open source vulnerabilities and how to mitigate them.
Call graph visualizations can support developers in the assessment and mitigation of open source vulnerabilities. This blog post demonstrates this use case by drilling down into a voluminous call graph until we clearly see the invocation of vulnerable open source methods by a given client. Understanding this invocation context helps developers understand the relevancy and impact of open source vulnerabilities and how to mitigate them.
Call graph visualizations can support developers in the assessment and mitigation of open source vulnerabilities. This blog post demonstrates this use case by drilling down into a voluminous call graph until we clearly see the invocation of vulnerable open source methods by a given client. Understanding this invocation context helps developers understand the relevancy and impact of open source vulnerabilities and how to mitigate them.
Call graph visualizations can support developers in the assessment and mitigation of open source vulnerabilities. This blog post demonstrates this use case by drilling down into a voluminous call graph until we clearly see the invocation of vulnerable open source methods by a given client. Understanding this invocation context helps developers understand the relevancy and impact of open source vulnerabilities and how to mitigate them.
Open source software boosts developer productivity. They can focus on the unique value proposition of their application rather than re-creating mundane functionality again and again.
But open source consumption also comes with security risks [Top-10], one of them being that vulnerabilities in open source components can be exploited in the context of applications integrating them. And with an increasing number of vulnerability disclosures, developers spend more and more time on triaging such vulnerability alerts.
As explained in a previous blog post by Georgios Gousios, call graphs are a great tool for such vulnerability assessments. They help understand whether and how a vulnerable method is reachable (can be invoked) in the context of a client application, which is a prerequisite for being exploitable.
However, material on call graph construction is often overly simplified. Explanatory schemas often consist of nothing more than a handful of functions, while real-world call graphs consist of thousands and thousands of functions (represented as nodes) and invocations (represented as edges).
The following series of graph visualizations showcase the complexity of call graphs more adequately. And once such call graphs are annotated with vulnerability information, they can be a powerful tool in the hands of developers to investigate problematic invocations of vulnerable methods and to design countermeasures.
The Call Graph of logback-access in its Full Beauty
The first visualization, which reminds of the famous Rangwali Holi festival at first sight, is in fact an example of a (relatively small) call graph created by stitching together the dependencies for the Census II component logback-access 1.4.6, and consists of 14K+ nodes that represent functions of logback-access and its 24 dependencies (with scopes compile and provided, including optional ones), and 60K+ edges that represent function invocations.
In this example, logback-access is the client component, so put yourself in the shoes of a logback developer who has to assess a vulnerability in her dependencies. The nodes of the graph representing logback functions are slightly bigger and green. All other functions get the same color if they are part of the same dependency. The dark-red area in the top-left part of the graph, for example, are all functions of the component tomcat-coyote.
All Invocations Leading to Vulnerable Code
Admittedly, the complete graph is not overly useful in terms of vulnerability assessment, so we will prune the graph step-by-step to locate paths from logback-access to a vulnerable function of the Jetty Project.
We will start by removing all function invocations that do not lead to vulnerable methods, which is possible because every node and edge has a variety of properties that can be used for applying filters. Nodes, for example, have a function name, the package they belong to, whether or not they are subject to a known vulnerability, its CVE identifier and CVSS score in case they are, etc.
The resulting graph is slightly less voluminous, and it is already possible to see vulnerable functions, which are bigger nodes labeled with the CVE identifier and CVSS score. There are seven of them in total, in different dependencies of logback-access 1.4.6.
Focus on CVE-2023-26049
The next step consists of focussing on a specific vulnerability, CVE-2023-26049, by removing all edges that do not lead to the vulnerable method CookieCutter.parseFields, which was subject to CVE-2023-26049 and fixed in commits 1be1401b and 7b8c2c1b.
This filter reduces the graph to a reasonable size allowing developers to manually explore the potential call paths from client code (green nodes) to vulnerable code. In this example, the graph only contains functions from four different components (logback-access in green, logback-core in light-red, jetty-server in orange and jetty-http in gray). Hovering over the nodes and edges would show the function names and all the additional properties.
Focus on Functions Nearby CookieCutter.parseFields
The removal of paths having a distance of more than 4 to the vulnerable function reveals one potential invocation path even more clearly, which starts from the client function AccessEvent.getCookie (part of logback-access), and goes through Request.getCookies and Cookies.getCookies until it reaches the vulnerable function CookieCutter.parseFields in component jetty-http.
This level of detail does not only show logback developers that the vulnerable method is indeed reachable, but also supports them in assessing whether it is exploitable. In the example of CVE-2023-26049, for instance, the developer could evaluate whether at that point there are any cookies with confidential information that would be impacted by the vulnerable, non-standard cookie parser implemented in this version of Jetty.
And suppose there’s no fix available, thus, the vulnerability cannot be fixed by just updating a dependency: In such cases, the graph can be useful to understand where and how to best implement a client-specific mitigation, e.g. additional sanitization or a redundant cookie parser.
Conclusion
In summary, the drill-down demonstrated how to get from overwhelmingly voluminous call graph visualizations to something digestible and highly useful for developers. This level of detail gives developers a wealth of information to assess the potential invocation and exploitation of vulnerable open source code. Moreover, showing all potential invocations helps design effective mitigations in case dependency updates are not simple or possible at all, making sure that no invocation is missed in case of custom, client-side safeguards.