DEV Community

Cover image for Container Security Scanning: Vulnerabilities, Risks and Tooling
Tiexin Guo for GitGuardian

Posted on • Originally published at blog.gitguardian.com

Container Security Scanning: Vulnerabilities, Risks and Tooling

Over the past decade, the rise of microservice architectures, the DevOps culture, and popular tools like Docker and Kubernetes have made deploying applications as containers standard practice in the industry, making container security a top priority.

In this article, we will take a deep look at container security: what are the common container vulnerabilities, how to mitigate risks by container security scanning, and what popular container security tools and solutions are available to integrate into the SDLC in a DevSecOps way. Without further ado, let's get started.

Container Vulnerabilities

Containers are created using container images, which act as templates. If the images have vulnerabilities, the containers will introduce those vulnerabilities into the production environment.

Container images have a collection of files in them: source code, but also configurations, binaries, and other dependencies, all of which could introduce vulnerabilities. So, what are the common container vulnerabilities? Here are a few:

  • Misconfigurations and hard-coded secrets.
  • Vulnerabilities in the application code.
  • Vulnerabilities brought by insecure libraries or other dependencies that are imported into the images (for example, malicious code could be introduced by a software supply chain attack).

Container Security Scanning

Fortunately, most container vulnerabilities can be mitigated relatively easily via container security scanning, a process that analyzes images and containers for security issues. Continuous container security scanning, or continuous container security, if you will, is a critical part of DevSecOps.

How does container security scanning work? Typically, there are two different methods:

  • by analyzing files, packages, and dependencies. For example, by looking at insecure coding practices or misconfiguration, search for patterns that look like hard-coded secrets, and scan the application's dependencies (including third-party libraries and frameworks) against databases of known vulnerabilities.
  • by analyzing containers' activity. Unexpected network traffic or a a shell spawned in a container with an attached terminal are both suspicious activities that could cause security concerns.

Next, let's examine these container vulnerabilities—secrets, misconfiguration, vulnerabilities from code, and dependencies—and how different approaches and tools can help mitigate these risks.

Hard-Coded Secrets and Misconfigurations in Docker Images

Misconfigurations and hard-coded secrets reduce security by making the application and potentially the entire infrastructure vulnerable to easy exploits. Attackers can gain access to critical systems and may even escape from within containers to the host machine.

So, at a bare minimum, we need to check for misconfigurations and ensure no hard-coded secrets are present in Docker images.

💡

*Hard-coded secrets are fundamentally different from other vulnerabilities*: any exposed secret can lead to a security breach, regardless of whether the container is unused or the image is old. A Docker image is constructed from stacked layers that form its current state. This layered structure is prone to leaks because a layer can hide secrets from previous layers, making them invisible in the final state but still present within the image. So using a scanner that can thoroughly examine each layer is crucial for identifying and mitigating these hidden secrets.

Now, let's have a look at a few popular tools that can detect hard-coded secrets and misconfiguration.

ggshield

ggshield (requires a GitGuardian account) is a CLI to find more than 350+ types of hardcoded secrets (specific and generic ones) and 70+ Infrastructure-as-Code security misconfigurations.

Simply install it with your package manager (for example, for macOS, you can install ggshield using Homebrew: brew install gitguardian/tap/ggshield), and then the ggshield secret scan docker command is ready to be used to scan local docker images for secrets present in the image's creation process (Dockerfile and build arguments), and in the image's layers' filesystem.

Here's a demo of how it works:

ggshield provides many more functionalities than simply scanning for secrets. For more details, refer to the cheat sheet below (click to get the PDF) or the official documentation here

SecretScanner

Another option is SecretScanner from Deepfence. It is a standalone, open-source tool that retrieves and searches container and host filesystems, matching the contents against approximately 140 secret types (specific only).

Using SecretScanner is simple: pull the docker image for it by running docker pull quay.io/deepfenceio/deepfence_secret_scanner_ce:2.2.0, and we can simply do a docker run to scan container images.

See it in action below:

trivy

Yet another option is trivy , which is a versatile security scanner that can scan for both secrets (but only specific ones) and misconfiguration in container images, file systems, remote git repositories, and more (will touch on this a bit more in the next section). For now, let's focus on its capabilities of secrets and misconfiguration detection.

It can be installed using your package manager (for example, for macOS, you can also use Homebrew to install it: brew install trivy), and you can use it to scan both Docker images and local file systems.

For example, to scan secrets in an image, run trivy image --scanners secret IMAGE_NAME. Below is an example with a clean result:

Trivy also provides built-in checks to detect misconfigurations in popular Infrastructure as Code files, such as Docker, Kubernetes, Terraform, CloudFormation, and more. You can even define your own custom checks.

Here's an example to scan a Dockerfile locally: Given the Dockerfile below (building a simple Golang app with Alpine as the base image):

FROM alpine
WORKDIR $GOPATH/src/github.com/ironcore864/go-hello-http
COPY . .
RUN apk add git
RUN go get ./... && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o hello
CMD ["./hello"]
EXPOSE 8080/tcp
Enter fullscreen mode Exit fullscreen mode

Run the following command to check for misconfigurations (as we can see, 4 are found):

Docker Image Scanning for CVEs

The previous section ensures that there are no hard-coded secrets and that the configurations are spot-on. Next, let's move on to the other files in the Docker image: OS packages, language-specific packages, dependencies, etc.

The aforementioned comprehensive scanner trivy is one of the most popular open-source security scanners. It can also detect known vulnerabilities according to the versions of installed packages. This means that everything inside the image, whether called by your code or not, is scanned.

For example, let's write a simple Python Flask application and build a Docker image out of it, then scan it with trivy.

A very simple Flask hello world app is created:

Then, we add some dependencies in requirements.txt:

requests==2.19.1
cryptography==3.3.2
flask
Enter fullscreen mode Exit fullscreen mode

As you noticed, some packages are added, but not even used by our Flask app code, simply as a test.

Then we create a Dockerfile to build it:

If you wish, you can clone the repository here to get the whole code above.

We build and tag it: docker build . -t ironcore864/python-insecure-app:0.0.1, then scan it with trivy:

As you can see, multiple vulnerabilities are found, some of which are from the OS packages and some from the dependencies we added to it. Even though we do not call those packages directly with our code, since image scanning works by scanning the OS packages and dependencies—what's in the image—it finds them all.

Besides trivy, there are a few other tools worth mentioning:

  • clair: an open-source application for parsing image contents and reporting vulnerabilities affecting the contents, done via static analysis, not at runtime.
  • grype: a vulnerability scanner for container images and filesystems. It works with syft, the powerful SBOM (software bill of materials) tool for container images and filesystems. For more information about SBOMs:

Why you need an SBOM (Software Bill Of Materials)

SCA: Software Composition Analysis

Next, let's move on to a slightly different approach to vulnerability scanning: Software Composition Analysis (SCA), which automatically scans and detects vulnerabilities in components and third-party libraries.

While this might sound similar to Docker image scanning, there are key differences. SCA focuses on identifying vulnerabilities in the software dependencies and libraries used within an application, ensuring that all components are secure. In contrast, as we have seen, Docker image scanning examines the entire container image, including the operating system, application code, and configurations.

Let's have a look at one of the SCA tools snyk to get a hands-on feeling about SCA. For macOS users, you can install snyk by running:

If we scan the same app from the previous section:

We should get results similar to:

As we can see, the issues detected by the Docker image scanning in the previous section were also detected by image scanning. Note, however, that image scanning found more issues.

Both Docker image scanning and SCA work by referring to known vulnerability databases, which explains the similar results.

However, whereas Docker image scanning scans everything present inside an image, including OS packages, SCA is only concerned about the dependencies declared in our project (in our case, defined in the requirements.txt), which is a more specific scope.

At the end of the day, vulnerable dependencies (in our case, requests and cryptography) are reported by both Docker image scanning and SCA, but there are key differences:

  • When the scan happens in the DevOps cycle: Docker image scanning scans built images. So the scan can only happen once the image is ready, in other words, towards the end of the development cycle (typically in your CI pipeline, just before deploying it in a test environment). SCA, on the other hand, scans the source code, which means SCA is implemented in the early stages of the cycle. The bottom line is that SCA allows us to discover issues earlier.
  • What is the real scope of the scan? As we mentioned, SCA is only concerned with how the running code could be compromised, nothing more. But this code will run in a container, which presents its own attack surface. Ultimately, the container image is the blueprint for what will run in the production environment, so it's crucial to detect both the vulnerabilities affecting the container AND the application.

Combining the above pros and cons of both Docker image scanning and SCA, we can see that they provide complementary values at different stages. They both have their place in your automated DevSecOps workflow.

Besides snyk, there is another open-source project osv-scanner that's worth a look at: It's an SCA vulnerability scanner written in Go, which uses the data provided by osv.dev

For more information on osv-scanner (and open-source security in general), read this blog:

Open-Source Software Security

Container Runtime Security

So far, we've covered both Docker image scanning and SCA, which happened at the code/image level, with which we can discover the known vulnerabilities and fix them before our apps get deployed. Next, let's cover another type of container security scanning: container runtime security.

Container runtime security, as the name suggests, happens at the runtime level: it takes proactive measures and controls to protect containerized applications during the runtime phase, detecting unexpected behaviour, configuration changes, and attacks in near real-time.

This sounds theoretical, so let's take a look at one concrete example with Falco, an open-source tool designed to detect and alert on abnormal behaviour and potential security threats in real-time.

To quickly try Falco out, prepare a K8s cluster and install it with helm:

Note: Be sure to change the last parameter from changeme to your name.

For a more detailed installation guide, see the official documentation here.

Once Falco is up and running, we can simulate suspicious actions to see if the so-called "runtime security" platform can capture them. For As a first example, let's start a container and get a shell. In the real world, an attacker with shell access means the container is already compromised, as the attacker could try all kinds of things.

Start a container by running: kubectl run alpine --image alpine -- sh -c "sleep infinity", then execute the uptimecommandinside it:

kubectl exec -it alpine – sh -c "uptime"

(well, this is a harmless command, but you get the gist).

If we check Falco's output, we should get an alert:

Falco's base rules alert you if someone runs an interactive shell into a running container. For more standard rules, see the official documentation. Also, you can write and customize Falco rules by yourself to secure your environment.

Summary

In this blog, we've seen that there are several types of container vulnerabilities: hard-coded secrets, misconfigurations, vulnerabilities in the application code, and vulnerabilities in OS packages and dependencies.

Luckily, most of them can be detected by tools fairly easily:

  • ggshieldSecretScanner to scan for secrets
  • trivy to scan for misconfigurations
  • trivyclair and grype to scan Docker images for known CVEs
  • SCA tools like snyk and osv-scanner to analyze software composition

Besides scanning code and images, we also can enhance our production environment by using container runtime security platforms such as Falco, which detects unexpected behaviour and attacks in real-time with predefined and customizable rules.

As a closing thought, it's worth pointing out that container security scanning has limitations: It's not great for detecting unknown vulnerabilities. If some vulnerabilities are not yet publicly disclosed, or if a new type of attacking behavior just emerged and it's not in the runtime security platform's rule list, they can't be detected.

So, although container security scanning is necessary to secure production environments, it's important to remember that it is one layer of security and not an end-game solution to mitigate all types of threats.

As a follow-up reading, you can refer to the Docker security cheat sheet, to help you understand how to run containers with the right security guardrails from the onset:

Docker Security Best Practices: Cheat Sheet

I hope you enjoyed this article. See you in the next piece!

Top comments (0)