Analyzing Docker Image Security

Docker containers are not inherently secure as many people think. So, it’s crucial use tools and scanners to rid your containers of vulnerabilities…

Photo by Dima Pechurin on Unsplash

A lot of people assume that Docker images and containers are secure by default, which — unfortunately — is not the case. There are quite a few things that effect security of your Docker images. Whether it’s packages installed in the image, libraries used by your application or even the base image — all these components might introduces vulnerability into your application. A lot of these problems are easily avoidable, though…

Anchore & Clair

The easiest way to find vulnerabilities in Docker images is to run inspection against them using tools like or :

Anchore Engine: Anchore is a centralized service for inspection, analysis and certification of container image. It scans images using vulnerability data (feeds) from OS vendors like Red Hat, Debian or Alpine. For non-OS data it uses NVD (National Vulnerability Database), which includes vulnerabilities for RPM, Deb, APK as well as Python (PIP), Ruby Gems, etc.

Clair: Clair is a static analyzer developed by CoreOS for Docker and APPC containers. It uses vulnerability metadata from similar sources as Anchore — Red Hat Security Data, NVD, Ubuntu CVE Tracker, Alpine SecDB, Debian Security Bug Tracker, etc.

Setting Up

Now that we know the tools we want to use, it’s time spin them up. Both Anchore and Clair include various integration and can be deployed to Kubernetes or OpenShift, but for the purpose of this demonstration, we will set them up using docker-compose on local machine:

To setup Anchore run following:

To setup Clair run following:

And with that we are ready to analyze!

Note: We will come back to Clair little later in the article.

Check Image for Vulnerabilities

Let’s start with Anchore and basic Debian image. What we need to do, to get our image analyzed is to add it and wait for the analysis to complete:

With the image analyzed let’s see what vulnerabilities were found:

We first run image vuln command with all flag to list both OS and package vulnerabilities present in the image. All of those are Negligible or Unknown , so that's pretty good. Next, we run evaluate check command to see whether this image passes the default policy check and as you can see above ( Status: pass ), it does. These kinds of base images generally perform pretty well in this regard as they are widely used and therefore under a lot of scrutiny.

But, what about simple simple “Hello World” Python application built using Python 3 Debian Buster image? Let’s look at the Dockerfile first:

Nothing that screams “vulnerable” and “insecure”, right? So, let’s built it and analyze it:

I first built the image using the above debian.Dockerfile and simple hello.py . I then pushed it to Docker Hub, from where it was added to Anchore and finally analyzed. Now we can check out the results:

Well, well, well… Not so great anymore. All we did, was switch to official Python Debian image and we suddenly have more than 1000 vulnerabilities including some Low and Medium severity ones compared to just a few Negligible ones with plain Debian image. On top of that, after running evaluation of the image, we can see that it failed ( Status: fail ).

So, even though we used official and seemingly secure image and while creating very simple application with no obvious vulnerabilities, we still got a lot of security problems. So, how can we improve this?

Finding Superior Image

When it comes to security, the best choice for a base image is scratch - that is - empty container. It is however not very practical to build our own base images from nothing and it might bring more security issues considering that most of us are not security experts.

Second best choice after scratch is - in my opinion - Distroless, which is set of images made by Google, that were created with intent to be secure. These images contain the bare minimum that's needed for your app, meaning that there are no shells, package managers or any other tools that would bloat the image and create signal noise for security scanners (like CVE) making it harder to establish compliance. I already went into more detail about Distroless in my previous blog post here, so in case you want to know more, go check it out.

Alright, so Distroless is now our choice for secure image, but before we analyze anything, let’s first look at the new Dockerfile :

Not much has changed there compared to Debian version. We really just switched the Runner image to gcr.io/distroless/python3 . Now, we can go ahead and build it, push it and add it to Anchore Engine:

Finally, time to see whether the Distroless performed any better than Debian version:

And it sure did! Compared to first example we have only 53 vulnerabilities and just 2 Low severity ones. Also, looking at the policy evaluation - this image passed, while the Debian one failed!

So, we can safely conclude that Distroless is superior choice when it comes to security of our containerized applications. There are however, a few more things we can check and improve — for example, we can try using different evaluation policies:

The above command lists all available policies that we can use to check our images. By default, it uses anchore_default_bundle which works just fine, but in case we want to see the check performed using different whitelist and rules, then we can try, for example anchore_cis_1.13.0_base :

From the above you can see that we first looked at description of this specific policy by running policy hub get . After that we installed it and ran policy list command which shows that we now have 2 policies available, with the new one being inactive. So, we activate it with the last command ( policy activate ).

Before we run the check with the new policy, let’s first talk about what it includes. Considering that it’s Docker CIS 1.13.0 check, it will focus on guidance provided by this document. Just a few examples of what kind of issues it looks for:

ADD command used in place of COPY

command used in place of Non-whitelisted opened ports

Missing HEALTCHECK

Using non-trusted base images

Using root as effective user

For a full list of rules in this policy you can run anchore-cli --json policy hub get anchore_cis_1.13.0_base . Now, that we know what it will look for, it's time to run it:

There are quite a few warnings in the output coming from some of the installed packages, but ones that are specific to this policy are the ones listed above. These are issues/vulnerabilities that make the image fail the policy evaluation and should be analyzed. One of these (second) is caused by usage of Distroless image as it is not whitelisted image, so that one can be ignored. As for the other two — these are real issues that should be fixed, but both of them have straightforward, simple solution.

This shows us, that it’s a good idea to choose specific policy based on our requirements or even use multiple ones to catch as many issues as possible.

Types/Levels of Vulnerabilities

Not all vulnerabilities are equal though and some of them don’t even apply to our applications/containers/environments. Therefore, we should always not only look at their severity, but also the factors from which the severity is calculated. That includes attack vector, attack complexity, confidentiality impact, integrity impact, etc. These factors then create final severity score generated using CVSS Calculator. The specific brackets are — None , Low , Medium , High and Critical . More info about them can be found at NIST website: https://nvd.nist.gov/vuln-metrics/cvss.

Fixing Vulnerabilities

Vulnerabilities in the above example were easy to fix, but that’s not always the case. You might run into vulnerabilities with high severity score or ones that are problematic for your specific use case. When it comes to problems with base images or packages included in them, you shouldn’t be the one fixing them, as that’s responsibility of developers and/or publishers of said tool. That said though, you should prevent or at least mitigate exploitability of existing and future vulnerabilities by removing attack vectors, for example by using Distroless, which doesn’t have any shell. On top of that, it’s a good idea to run vulnerability checks periodically using build/deployment pipelines to find out as soon as possible when vulnerabilities get introduced, ideally using multiple scanning tools.

Anchore vs. Clair

Speaking about running multiple scanning tools… What about Clair? How does it compare to Anchore? This is how we can run it against our example Debian and Distroless images:

Looking at output of above commands, it looks pretty similar to what we’ve got from Anchore — lots of issues for Debian image and nothing for Distroless. In the output of first scan, there is huge list of vulnerabilities which I omitted, but I checked all the Low and Medium severity ones and they are present both in Anchore and Clair scan results, but that might not always be the case. So, I would recommend running multiple scanning tools, ideally ones that use different sources for vulnerability metadata.

Some other tools you might want to have a look at include Docker Bench for Security or vulnerability scan included in IBM Container Registry.

Conclusion

With security related stuff, it’s always preferable to be proactive and try to avoid vulnerabilities before they become an actual problem and tools shown in this article can greatly help with that. I think using and implementing this tooling is easy enough, so that anybody can make them part of daily workflow or ideally, part of their automatic pipelines/jobs. Security of applications we create is — to a certain extent — responsibility of every developer, not just security experts or pentesters, so we should all take part in making them sufficiently secure, maybe just by running vulnerability scan from time-to-time. 😉