How to Secure Docker Containers: Preventing Shell Access and Protecting Sensitive Data


3 views

When distributing pre-built Docker containers, preventing unauthorized shell access becomes crucial for protecting sensitive information like configuration files, API keys, or serial numbers. The standard java:7 base image (Ubuntu-based) includes multiple shell options (bash, dash, sh), creating potential security vulnerabilities.

Malicious users typically attempt container access through:

docker exec -it [container] bash
docker attach [container] 
docker run -ti --entrypoint=/bin/bash [image]

Method 1: Remove Shell Binaries

Modify your Dockerfile to eliminate shells during build:

FROM java:7
RUN rm -f /bin/bash /bin/sh /bin/dash \
    && rm -f /usr/bin/bash /usr/bin/sh /usr/bin/dash

Method 2: Use Minimal Base Images

FROM gcr.io/distroless/java:11
COPY app.jar /app/
CMD ["/app/app.jar"]

1. Read-only Filesystem:

docker run --read-only -d mycontainer

2. User Namespace Isolation:

# Add to /etc/docker/daemon.json
{
  "userns-remap": "default"
}

3. Seccomp Profiles:

docker run --security-opt seccomp=no-exec.json mycontainer

Test your hardened container with:

docker export [container] | gzip -c > test.tar.gz
gzip -dc test.tar.gz | docker import - test_image
docker run -ti --entrypoint=/bin/bash test_image

This should fail with "No such file or directory" for shell access attempts.

For sensitive data like serial numbers:

# Use Docker secrets (Swarm mode)
echo "serial123" | docker secret create serial_number -
docker service create --secret serial_number mycontainer

When deploying Docker containers in production, preventing unauthorized shell access becomes crucial for protecting sensitive data like configuration files, API keys, or serial numbers. The standard java:7 base image (Ubuntu-based) includes multiple shell binaries (bash, sh, dash), creating potential entry points for attackers.

To comprehensively secure your container, we need to address three main entry methods:

# Method 1: Prevent docker exec
FROM java:7
RUN rm -f /bin/bash /bin/sh /bin/dash

# Method 2: Block attach via custom ENTRYPOINT
ENTRYPOINT ["java", "-jar", "your_app.jar"]
CMD ["--no-shell"]

A robust solution combines Docker configuration with filesystem hardening:

# Dockerfile hardening example
FROM java:7

# Remove shells and related utilities
RUN rm -rf /bin/*sh /bin/bash /usr/bin/ssh* \
    && apt-get purge -y --auto-remove openssh-server*

# Create non-root user
RUN useradd -ms /bin/false appuser
USER appuser

# Secure ENTRYPOINT with exec form
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]

Even with these measures, we should add runtime protection:

# Run container with security flags
docker run -d \
  --read-only \
  --security-opt=no-new-privileges \
  --cap-drop=ALL \
  your_container_image

Test the hardened container using the verification steps:

# Attempt shell access (should all fail)
docker exec -it secured_container sh
docker attach secured_container
docker run -ti --entrypoint=/bin/bash secured_container

For maximum security, implement a custom seccomp profile:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "syscalls": [
    {
      "names": [
        "execve",
        "fork",
        "clone",
        "kill"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

Apply it during container execution:

docker run --security-opt seccomp=profile.json your_image

For configuration files containing serial numbers:

# Use tmpfs for sensitive data
docker run -d --tmpfs /etc/config:rw,noexec,nosuid,size=1m your_image

# Alternative: encrypted volumes
docker volume create --driver crypt --name secure_config