Remote code execution (RCE) due to root user in Docker container

Critical Risk infrastructure-security
dockercontainer-securityprivilege-escalationroot-userrcecontainer-escapedockerfile

What it is

Docker containers running as the root user create severe security vulnerabilities by providing unnecessary elevated privileges to application processes. If the application is compromised, attackers gain full container control with root access, enabling easier container escape attempts, filesystem manipulation, and potential host system compromise through volume mounts or exposed Docker socket.

# VULNERABLE: Node.js running as root
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
# VULNERABLE: No USER directive - runs as root
CMD ["npm", "start"]

# VULNERABLE: Python app with root access
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
# VULNERABLE: Application has root privileges
CMD ["python", "app.py"]

# VULNERABLE: Multi-stage with root in final
FROM golang:1.19 AS builder
WORKDIR /app
COPY . .
RUN go build -o main .

FROM alpine:latest
RUN apk add ca-certificates
COPY --from=builder /app/main /usr/local/bin/
# VULNERABLE: Final image runs as root
CMD ["main"]
# SECURE: Node.js with non-root user
FROM node:18-alpine
WORKDIR /app

# SECURE: Create dedicated user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001 -G nodejs

# Set file ownership
COPY --chown=nextjs:nodejs package*.json ./
RUN npm ci --only=production
COPY --chown=nextjs:nodejs . .

# SECURE: Switch to non-root user
USER nextjs

EXPOSE 3000
CMD ["npm", "start"]

# SECURE: Python with non-root user
FROM python:3.11-slim
WORKDIR /app

# SECURE: Create non-root user
RUN useradd --create-home --shell /bin/bash appuser

COPY requirements.txt .
RUN pip install -r requirements.txt

# Set file ownership
COPY --chown=appuser:appuser . .

# SECURE: Switch to non-root user
USER appuser

EXPOSE 8000
CMD ["python", "app.py"]

# SECURE: Multi-stage with non-root final
FROM golang:1.19-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o main .

FROM alpine:latest
RUN apk add ca-certificates

# SECURE: Create non-root user
RUN adduser -D -s /bin/sh appuser

WORKDIR /home/appuser
COPY --from=builder --chown=appuser:appuser /app/main .

# SECURE: Switch to non-root user
USER appuser

EXPOSE 8080
CMD ["./main"]

💡 Why This Fix Works

The vulnerable configurations omit the USER directive, causing containers to run as root (UID 0) by default. This gives compromised applications full administrative privileges within the container. The secure versions create dedicated non-root users with adduser/useradd commands, set proper file ownership using --chown flags, and switch to the non-root user with the USER directive before running the application, implementing least-privilege principles.

Why it happens

Docker containers run as root user (UID 0) by default when no USER directive is specified in the Dockerfile. This gives all application processes full root privileges within the container, violating the principle of least privilege.

Root causes

Default Root User Execution

Docker containers run as root user (UID 0) by default when no USER directive is specified in the Dockerfile. This gives all application processes full root privileges within the container, violating the principle of least privilege.

Avoiding Permission Complexity

Developers run containers as root to bypass file permission issues and simplify development workflows. Using root eliminates permission denied errors but creates severe security vulnerabilities by granting unnecessary elevated privileges.

Missing USER Directive Awareness

Dockerfiles lack USER directives due to unfamiliarity with container security best practices. Developers may not realize containers default to root or understand the security implications of root execution.

Convenience Over Security

Organizations prioritize rapid deployment and operational convenience over implementing least-privilege security controls. Root execution is allowed because it's simpler than properly configuring user permissions and file ownership.

Incomplete User Configuration

Dockerfiles don't include proper user creation (adduser/useradd) and file ownership configuration (chown). Without these steps, applications cannot run successfully as non-root users, forcing root execution.

Fixes

1

Create Dedicated Non-Root User

Add RUN commands in Dockerfile to create a dedicated application user using adduser (Alpine) or useradd (Debian/Ubuntu). Assign a specific UID and GID (e.g., 1001) for consistency and ensure the user has no login shell or unnecessary privileges.

2

Switch to Non-Root with USER Directive

Add USER directive in Dockerfile before CMD/ENTRYPOINT to switch execution context from root to the non-root user. Place USER after all operations requiring root privileges (package installation, file copying) are complete.

3

Set Proper File Ownership

Use --chown flag in COPY and ADD instructions to set file ownership to the non-root user during image build (e.g., COPY --chown=appuser:appuser). This ensures application files are owned by the runtime user, preventing permission issues.

4

Configure Non-Privileged Ports

Configure applications to listen on ports above 1024 (e.g., 3000, 8080) which don't require root privileges to bind. Avoid ports 1-1024 which require elevated privileges, or use capabilities if absolutely necessary.

5

Apply Least-Privilege Principles

Implement comprehensive least-privilege container configuration including read-only root filesystems, dropped capabilities, and security contexts. Combine non-root users with other security hardening measures for defense-in-depth.

Detect This Vulnerability in Your Code

Sourcery automatically identifies remote code execution (rce) due to root user in docker container and many other security issues in your codebase.