Kahibaro
Discord Login Register

11.4 Reducing Attack Surface

Understanding the Attack Surface in Containers

In the context of Docker, the attack surface is every place an attacker might interact with or exploit your container. This includes the software inside the image, the Docker configuration that runs it, open network ports, file system layout, and how secrets and credentials are handled.

The goal of reducing the attack surface is to give an attacker as few opportunities as possible. You aim to run only what you need, expose only what is necessary, and keep everything small, simple, and up to date.

Reducing the attack surface means removing or disabling everything that is not strictly required for your container to do its job.

Minimal Base Images

One of the most effective ways to reduce the attack surface is to start from a smaller base image. A larger base image often includes extra tools, libraries, and services that you do not need, each of which can contain vulnerabilities.

Common strategies include switching from a full distribution image such as ubuntu to a smaller one such as alpine, or using language specific minimal images such as python:3-alpine or node:slim.

You can go further and use so called distroless or scratch based images. These images provide only the bare minimum to run your application and no extra shell or package manager. With fewer binaries and libraries available, there are fewer things an attacker can misuse inside the container.

Prefer minimal, purpose built base images instead of general purpose full distribution images whenever possible.

Removing Unnecessary Tools and Packages

Even with a minimal base image, extra tools are often installed during the build process. Development utilities, build toolchains, shells, and package managers increase the number of installed packages and therefore the number of potential vulnerabilities.

During image construction, you might need tools like compilers or debuggers. Once the build is finished, these tools are rarely required at runtime. By removing them before finalizing the image, or by using multi stage builds, you reduce what ends up in the final runtime image.

Typical examples of tools that should not be in a production runtime image include gcc, make, debuggers, text editors, and shell based utilities beyond what your application strictly needs.

Do not ship build tools, debuggers, or editors in production images. Keep only what the application requires at runtime.

Limiting Exposed Ports and Network Access

Each open port is a potential entry point. While Docker networking is covered elsewhere, there are specific attack surface concerns to keep in mind at the container level.

Only expose ports that your application must listen on. If a component inside the container listens on an internal port that should not be reachable from outside, avoid publishing it to the host. When you configure port mappings, carefully choose which ports to publish and to which network interfaces.

Inside the container itself, you should also avoid running additional network daemons such as SSH servers that are not essential for the service. Using Docker capabilities to get a shell inside a container for debugging is preferable to running a permanent SSH service in production containers.

Expose only the ports that are strictly necessary and avoid running extra network services like SSH in production containers.

Reducing File System Footprint

Files and directories inside the container can reveal information useful to an attacker or provide a place to drop malicious artifacts. A lean file system, with only the required application files and minimal configuration, reduces this risk.

This includes removing example configurations, sample applications, documentation, unused scripts, temporary build artifacts, and caches before finalizing the image. It also means not copying entire project directories when you only need a subset of files to run the application.

Limiting writable locations has a similar benefit. If you constrain where the application can write data, you reduce opportunities for code injection, tampering, or persistence within the container. Using volumes only where needed and avoiding world writable directories unless required will help.

Avoid copying unnecessary files into the image and keep writable directories as few and as restricted as possible.

Disabling Unneeded Linux Capabilities

Containers share the host kernel and operate with a set of Linux capabilities. By default, Docker already drops some powerful capabilities, but many remain enabled for compatibility reasons. Each capability provides specific powers in the kernel, so any that your container does not need should be removed.

For instance, if your application does not need to change network settings or manage other processes, you can drop capabilities related to these actions. Likewise, you should avoid adding extra capabilities unless you clearly understand why they are required.

Carefully choosing capabilities at run time makes it harder for an attacker to escalate privileges or interfere with the host from inside the container.

Drop every Linux capability that your container does not require and avoid adding new ones unless absolutely necessary.

Restricting System Calls with Seccomp Profiles

System calls, often abbreviated as syscalls, are the main interface between user space applications and the kernel. If an attacker can trigger dangerous syscalls from inside a container, they may be able to escape or damage the host.

Seccomp profiles describe which syscalls are allowed and which are blocked. Docker provides a default profile that already limits some risky calls, but you can customize a stricter profile tailored to your application. A narrower set of allowed syscalls reduces the attack surface because even if an attacker compromises the application, many kernel level actions will be blocked.

Using seccomp is about aligning the system calls with the exact needs of your workload. The fewer you allow, while still permitting normal operation, the smaller the space of potential kernel exploits.

Use seccomp profiles to allow only the system calls your application truly needs, blocking all others.

Keeping Dependencies and Images Updated

Outdated dependencies and base images are a frequent source of vulnerabilities. Even a minimal image with few components can become risky over time if it is never updated.

Regularly rebuilding images with updated base layers and patch versions of libraries reduces the number of known vulnerabilities inside the container. Combined with scanning tools, which are discussed elsewhere in security topics, you can identify outdated or vulnerable components and remove them by upgrading or replacing packages.

Updating should be controlled and tested, but it must also be timely. Long periods without rebuilds increase the likelihood of exploitable issues in your images.

Rebuild and update images regularly so that known vulnerabilities in base images and dependencies are removed promptly.

Separating Responsibilities into Multiple Containers

Running many unrelated processes in a single container increases its complexity and attack surface. Each process and supporting library can introduce new vulnerabilities, and compromised components can be used to interfere with others inside the same container.

Instead, separate concerns so that each container handles a focused responsibility, such as a web server, an application process, or a background worker. This separation reduces the code and tools present in each container and limits the impact if one is compromised.

By adhering to this single responsibility approach, you transform large, complex containers into simpler and more constrained units that are easier to secure and monitor.

Design containers so that each one has a single, focused purpose, which reduces internal complexity and limits damage from a compromise.

Views: 9

Comments

Please login to add a comment.

Don't have an account? Register now!