Running containers as a privileged user weakens their runtime security, allowing any user whose code runs on the container to perform
administrative actions.
In Linux containers, the privileged user is usually named root. In Windows containers, the equivalent is
ContainerAdministrator.
A malicious user can run code on a system either thanks to actions that could be deemed legitimate - depending on internal business logic or operational management shells - or thanks to malicious actions. For example, with arbitrary code execution after exploiting a service that the container hosts.
Suppose the container is not hardened to prevent using a shell, interpreter, or Linux capabilities. In this case, the malicious user can read and exfiltrate any file (including Docker volumes), open new network connections, install malicious software, or, worse, break out of the container’s isolation context by exploiting other components.
This means giving the possibility to attackers to steal important infrastructure files, intellectual property, or personal data.
Depending on the infrastructure’s resilience, attackers may then extend their attack to other services, such as Kubernetes clusters or cloud providers, in order to maximize their reach.
This container:
There is a risk if you answered yes to any of those questions.
In the Dockerfile:
USER statement.
postgresql or
zookeeper. It is recommended to use these users instead of root. ContainerUser is available for this purpose. Or, at launch time:
user argument when calling Docker or in the docker-compose file. If this image is already explicitly set to launch with a non-privileged user, you can add it to the safe images list rule property of your SonarQube instance, without the tag.
For any image that does not provide a user by default, regardless of their underlying operating system:
# Sensitive FROM alpine ENTRYPOINT ["id"]
For multi-stage builds, the last stage is non-compliant if it does not contain the USER instruction with a non-root user:
FROM alpine AS builder COPY Makefile ./src / RUN make build USER nonroot # Sensitive, previous user settings are dropped FROM alpine AS runtime COPY --from=builder bin/production /app ENTRYPOINT ["/app/production"]
For Linux-based images:
FROM alpine
RUN addgroup -S nonroot \
&& adduser -S nonroot -G nonroot
USER nonroot
ENTRYPOINT ["id"]
For Windows-based images, you can use ContainerUser or create a new user:
FROM mcr.microsoft.com/windows/servercore:ltsc2019 RUN net user /add nonroot USER nonroot
If the scratch Dockerfile untars a Linux distribution, the "Linux image" solution should be applied. Else, you have a choice between
using a pre-written /etc/passwd file (regardless of the host operating system) or using a multi-stage build.
FROM scratch COPY etc_passwd /etc/passwd # contains "nonroot:x:1337:1337:nonroot:/nonroot:/usr/sbin/nologin" USER nonroot COPY production_binary /app ENTRYPOINT ["/app/production_binary"]
or you can use a multi-stage build:
FROM alpine:latest as security_provider
RUN addgroup -S nonroot \
&& adduser -S nonroot -G nonroot
FROM scratch as production
COPY --from=security_provider /etc/passwd /etc/passwd
USER nonroot
COPY production_binary /app
ENTRYPOINT ["/app/production_binary"]
For multi-stage builds:
FROM alpine as builder
COPY Makefile ./src /
RUN make build
FROM alpine as runtime
RUN addgroup -S nonroot \
&& adduser -S nonroot -G nonroot
COPY --from=builder bin/production /app
USER nonroot
ENTRYPOINT ["/app/production"]