The movie ‘Inception’, by director Christopher Nolan is frequently cited as the most confusing movie of all time. The primary reason for the confusion is the fact that the movie moves across multiple dream states, or layers. The graphic below is the best attempt I’ve found to help us visualize and provide clarity on the dream states/levels and the timelines associated with each, but it’s still very confusing (the similarity to the graphic above is not a coincidence).
What *is* clear about the plot of the movie is that there’s a bad guy who conducts crime in a way that’s completely different from how crimes have been pulled off before - by crossing from one layer to another layer to another layer in a way that nobody expected crime to be committed - something people didn’t expect and weren’t prepared to deal with.
And this is the similarity between ‘Inception’ and modern, cloud-native application vulnerabilities - they span layers which most people haven’t been really paying attention to, because they aren’t intuitive or obvious.
How did that happen, though? In the next sections, we’ll dive into how applications and software development have evolved over the years, and how these shifts have changed the shape of application vulnerabilities and the demands on modern application security.
From Monoliths to Microservices and Multi-Layered Applications
Twenty years ago, applications were written as a monolith - a large chunk of code. Developers would write the software in their IDEs, compile it, then give their IT team a call. They’d say “Hey, I need a server deployed, here are my packages. I also need a database, so please create a database instance for me.” And the IT team would go off and do all the work. Incidentally, the IT team or the IT security team was also responsible for securing the infrastructure.
Developers didn't care about the server or any vulnerabilities in the OS. It was the IT security team’s responsibility. Fast forward to today, and we have modern, cloud-native applications, which are applications that are written in microservices architecture. Each microservice is made up of the code and the dependencies, and deployed in a container managed by an orchestration platform such as Kubernetes, and deployed in the cloud. The cloud could be a public cloud, like Amazon Web Services (AWS), Google Cloud Platform (GCP), or Microsoft Azure, or a private cloud.
The change in how software is written has been accompanied by a shift in who’s responsible for defining the infrastructure and defining the network, and configurations. This responsibility has moved from the IT team to the dev and/or DevOps teams. They do this through infrastructure as code templates - even more code - which means that the line between code and infrastructure has completely blurred, to the point where IT teams are no longer involved in the creation of infrastructure for deploying applications.
These additional layers introduce additional complexity to application vulnerabilities. To compound matters, while the burden for application security has shifted to development teams, their workload has increased significantly as digital transformation demanded more features, and shorter development cycles, adding to developers’ workload.
A New Breed of Software Vulnerabilities - Just Like Inception
Cloud-native software architectures introduce a new type of complexity to software vulnerabilities through the infrastructure layer. Since these layers are also software-defined, a misconfiguration could significantly elevate risk, and transform what would ordinarily be considered a low-risk vulnerability into one having critical risk.
A great example of this type of multi-layered vulnerability is the Google cloud vulnerability originally described here. (Hats off to the researcher and the author of the blog post for the incredibly witty title “How to contact Google SRE: Dropping a shell in cloud SQL”)
In this example, a SQL injection on a DB service, which sits on top of a misconfigured container which is publicly exposed, ultimately leads to a remote code execution (RCE) on the host. At the time that this vulnerability was discovered and disclosed in accordance with responsible disclosure policies, Google had a Cloud SQL service that enabled people to execute arbitrary queries within a MySQL database.
The researcher’s first step was to find a SQL injection on that service and elevate it to a remote code execution on the container. But the container shared the network with the host, which allowed full intervention in the network traffic. Because the database ran in the Google Compute Engine (GCE), the researcher was able to manipulate Google’s metadata server by spoofing requests, taking advantage of the behavior of the Google Guest Agent service, which ran by default on any GCE instance. These requests eventually caused the host to create a user with a predefined SSH public key.
By combining the aforementioned steps, a malicious actor would have been able to execute commands as root through sudo on the host VM and access all its resources. The graphic below illustrates the multi-layered nature of this vulnerability clearly, showing how the attack path traverses code, container and cloud to allow an attacker to achieve their intended goals.
We’re Not In The Movies Anymore
During the era when software was written as a giant monolith, and the production infrastructure was handled by someone in IT, this type of attack would have been the stuff of movies - a B-grade film or bad Hollywood flick using tired tropes like teenagers dressed in dark hoodies (at least one of them had to be a skateboarder) hammering away at their keyboards in their parents’ basement or garage, breaking into missile control systems, causing widespread panic in the government. The very notion of being able to jump from layer to layer to layer didn’t quite exist yet, because there was a clear distinction between ‘software’ and ‘infrastructure’, or ‘hardware’. But that time has long passed us.
As applications become more entangled and intertwined with the underlying infrastructure, the use of open source and third party components expands, and the pace at which software is delivered increases, we need to finally acknowledge that the old way of securing software - by looking at applications as giant boxes, whether an open box the way Static Application Security Testing (SAST) tools approach AppSec, or a closed one, like Dynamic Application Security Testing (DAST) tools do - are antiquated and add to the problems instead of helping to solve them. What are the security-related downsides of applying old AppSec approaches to modern application paradigms?
Time To Wake Up From Our Dream State
In addition to the vulnerability example above, communications among microservices enable new vectors of attack that were incomprehensible when SAST and DAST tools were developed. They do so by allowing malicious payloads to be passed through one or more services into a vulnerable internal service, even if it appears to a static scanner that the service isn’t directly accessible from the Internet.
As script-kiddie level attacks get neutered by better developer training and awareness, processes, and tools, threat actors will become more sophisticated and propagate more of these multi-layer, multi-dimensional attacks. Legacy tools will simply fail to detect the vulnerabilities that facilitate these attacks, because they lack the visibility necessary to properly detect them and trace their path. SAST tools have no runtime context, and DAST tools cannot connect the vulnerabilities they discover to code. The massive blind spot between static analysis and dynamic testing cannot be ignored any longer.
The solution? We must adopt a new approach that marries static and runtime analysis, so that we have full visibility from the externally-facing API, through the attack path of attack, and into the vulnerable line of code. Greater visibility that is unlocked by observability will give us insights that were previously impossible to get, and help ensure more secure cloud-native applications, and prevent nightmares for you, your team, and organization.
Oxeye provides contextualized application vulnerability results by combining static and runtime analysis and the functions of SAST, DAST and SCA into a single tool. The Oxeye Application Security Platform uses observability to analyze code, container, cluster, cloud and the connections and communications among them to find custom code, open source and third party package vulnerabilities, and filter out vulnerabilities that can’t be exploited. Check out our demo here.