Get the latest, first
The vulnerability puzzle: understanding base images and their relationship to CVEs

The vulnerability puzzle: understanding base images and their relationship to CVEs

Oct 10, 2024

Jonathan Green
Cloud Security Software Engineer

Have you ever heard of CVEs? Maybe not by their acronym, but Common Vulnerabilities and Exposures, monitored by the CVE Program Mission, are everywhere. As of the writing of this article, there are over 220,000 CVE Records available—meaning many potential threats you could be exposed to. 

How can you ever protect your infrastructure against this reality? Well, the good news is, you usually don’t have to. Most of those vulnerabilities will never reach you since they’ve already been patched or fixed with a newer version of whatever caused it. 

So the real question becomes: What happens when these libraries, dependencies, and base images, all of which form the basis for so many systems, fail to keep up with the latest vulnerabilities? 

In this article, I’ll go over the most common ways you can be exposed to CVEs, sometimes without even knowing it.

Base Images

Before we dive deep into CVEs themselves, let’s talk about base images. These form the baseline for a lot of today’s infrastructure in the cloud, be it Ubuntu, Docker, Java, or any other container you can pull with a FROM statement in a Dockerfile. We use them every day, and hardly even think about it. 

Base images deliver a lot of value with minimal effort; they also bring a lot of daily-use tools into a quickly spawned environment—for development, testing, or setting up and maintaining a production environment. 

Most of the images that we use on a day-to-day basis are public, like the ones mentioned above. What’s important here is that, if it’s public, everyone can see it—especially attackers waiting for the perfect opportunity to get into your system and exploit it. Once a vulnerability is found, it’s basically malicious actors versus SecOps. One side is racing to exploit as many weaknesses as possible, while the other is trying to patch as fast as possible to avoid damage.

However, there are also private base images, which are kept private for a variety of reasons. Maybe the image is not ready for public release because there are bugs to be ironed out, or it contains some sensitive data, or it’s a new implementation that is being tested for commercial use and can’t be shown to the public. 

Base Image Examples

In the vast sea of base images, there are some that stand out. According to DockerHub, the top base images are (in no particular order): 

  1. MySQL
  2. Docker 
  3. Python
  4. Ubuntu
  5. BusyBox
  6. Postgres
  7. HTTPd
  8. Memcached
  9. Traefik
  10. RabbitMQ

Scratch and Distroless Images

Scratch images contain the most minimal base image, used as a starting point to build other base images or containers. 

Distroless images have just the application and its runtime dependencies; there are no shells, package managers, or other tools in a standard Linux distro. 

Both are very popular instances not usually discussed and are very useful to greatly reduce the number of CVEs you’ll have to deal with. They’re also really small, with the smallest distroless image being only 2 MiB in size. 

Base Image Precaution

One of the downsides of using base images is that they sometimes introduce files that are required for initial setup or startup but are never touched or even seen later on, i.e., unused config files. This has been the cause of some CVEs over the years; however, it’s not what links CVEs to base images the most.

Let’s dive into CVEs, learn more about them and how they are related to base images. 

CVEs

Common Vulnerabilities and Exposures are paths attackers can use to perform any kind of forbidden or unwanted action in your infrastructure, be it privilege escalation, cross-site scripting, arbitrary shell execution, and so on. 

As two examples of CVEs that were related to base images, I’ll point you to CVE-2023-22475, where a cross-site scripting vulnerability was discovered in Canarytokens’ Docker image, and CVE-2020-15157, where a credential-leaking vulnerability was discovered in containerd. 

Role of Dependencies

What ties CVEs and base images together the most aren’t unused config files, as I already noted above. Its dependencies. 

Any and all base images carry with them some sort of dependency: library dependencies for a certain project, or package dependencies for managers, or even transitive ones that come from one of your other dependencies. 

Every dependency is essentially a way in for attackers. If any dependency is compromised, that is, a vulnerability was found in one of them, then any part of your infrastructure or any project that is using this dependency, is suddenly vulnerable too. 

There are two comforting facts, however, that you should keep in mind. First, if you’re using a vulnerability scanner, like the one featured in ARMO Platform, and your vulnerability doesn’t report as in-use, you’re good to go. In this context, in-use refers to the runtime context of how the dependency is actually used. Second, if you’re not even using vulnerable dependencies, you also don’t have anything to worry about. Still, it’s important to note that any dependency you use can become vulnerable in the blink of an eye.

CVE Score

The level of severity of a vulnerability is measured by what is known as a CVSS (Common Vulnerability Scoring System) score. This scale, going from 0 to 10, gives you a way of evaluating and ranking vulnerabilities in a consistent, standardized way.

A CVSS score is generally used to assign priority to vulnerabilities, with higher ones needing to be fixed first. Even though the score is relevant, the number of CVEs detected via regular container scannings using the CVSS scoring system usually presents a much higher number of vulnerabilities than one would expect—and that are actually in-use and reachable. 

This is why CVSS is not an “end-all” scoring system and why you should apply a degree of relevancy to your scans. If you want a deeper dive into different scoring systems, check out this blog post. You may also want to read about inflated CVE counts in container scannings and its implications.

Reporting CVEs

CVEs are mostly detected and reported by the community. Whenever someone finds a vulnerability, they can report it on cve.org either as a contributor or a CNA; after it’s verified, it’s up there for everyone to see. This way, people can monitor the list for new vulnerabilities they might be exposed to and take preventive measures to mitigate them. 

This list is also heavily used by container scanners, which compare entries to dependencies you might have. However, attackers can also monitor this list and use it against you. Given the right vulnerability—and a large enough window of time—malicious actors can take advantage of one (or more) of them to gain access to critical infrastructure or credentials.

Mitigating CVEs

Some of these exposures are easily mitigated, by either upgrading to the latest version (hopefully with a patch in place) or downgrading to an older version that doesn’t have this vulnerability. However, it’s not always that easy. 

Sometimes, a patch takes forever to come, enlarging the window of time during which you’re vulnerable. Other times, the vulnerability exists in multiple versions, even older ones, making it hard to downgrade due to the probable loss of certain functionality. In these cases, mitigations outside of the image are recommended (e.g. seccomp profiles).

Transitive Dependencies: Example Scenario

Most often, however, the problem with changing versions is transitive dependencies. In most languages and applications, dependencies are closely tied together. 

Say, for example, your project has three dependencies: A, B, and C. Dependency A version 3 depends heavily on Dependency B version 2. If there’s a vulnerability found in dependency A, and you need to downgrade to version 2, that might be just fine. However, the case is usually that dependency C depends on dependency A version 3, and now you also need to downgrade C.

In the image above, you can visualize the example given above, where to downgrade to dependency A version 2, you’d also need to downgrade dependency C to version 1.

This gets exponentially more painful the more dependencies you have, not to mention other factors such as compatibility with package managers or build tool versions (such as sbt for Scala). Then there is the availability of said dependencies, as some may be retired or transitioned between companies and no longer available. 

All of this makes some issues outright impossible to fix, leaving you vulnerable to attacks.

CVE Shock

Last but not least, there is a very common problem in this space I can’t fail to mention: CVE shock. 

Let’s say you run a scan for the first time, and you’re suddenly facing a report that says you’re exposed to +2000 vulnerabilities. How can you ever fix all of these? Especially given that chances are one fix will override another, or that two fixes will be incompatible with each other. This greatly slows time to remediation—not to mention, makes it feel like a daunting task. 

Unfortunately, such a scenario is very common. But there are ways around it. Like I said before, some of these reported vulnerabilities aren’t reachable (or at least not immediately). This is why applying a degree of relevancy and prioritizing CVEs is so important; you also need to adopt a resolution strategy that accommodates your workflow, needs, and capacity.

Here’s where a good CVE patching plan can be useful. Organizations need to follow some general steps to identify the degree of exposure and level of threat: 

  1. Vulnerability assessment
  2. Prioritization of vulnerabilities
  3. Patch acquisition
  4. Patch testing/validation in a non-production environment
  5. System backup
  6. Patch deployment
  7. Monitoring and validation
  8. Communication and documentation

From here, you will be able to make informed decisions on actionable measures to safeguard your system. For a deeper dive into getting the right process in place for effective patching, take a look at our CVE patching guide.

Final Thoughts

CVEs are definitely something to watch out for. If you stay on top of them with regular container and dependency scannings, weigh their reachability, and prioritize them, you can always be protected against the most common attacks. If not, you open yourself up to dependency hell and risk attackers getting into your infrastructure and performing all kinds of shenanigans. 

Using ARMO Platform’s prioritization feature, you can always stay ahead of the curve, and deal with CVEs swiftly and effectively—before they can affect your infrastructure. 

So, get out there and patch those vulnerabilities away!

slack_logos

Continue to Slack

Get the information you need directly from our experts!

new-messageContinue as a guest