Docker For Security Researchers xyz: y

Part y : Docker in action

Zyad Elsayed
10 min readSep 29, 2024

To review the first part

Dockerfile

A Dockerfile is a text document that contains a series of instructions to automate the process of building a Docker image. Each instruction creates a layer in the image, ensuring consistency, reproducibility, and efficient builds through caching.

Creating a Dockerfile for any application involves automating the steps required to set up the application environment, install dependencies, and run the application. Therefore, you need to know exactly what is required for the application to run properly.

For example, if you need to run a Node.js application, you should know the following before creating the Dockerfile:

  • The required Node.js version.
  • Application dependencies listed in package.json.
  • Commands to build and run the application.
  • Any environment variables needed.
  • Ports that the application listens on.
  • Any interaction with other containers or databases.
  • Whether it needs a network card or a volume.

Depending on the application you are building, you may choose to use either a public image or your own private image. For most services and technologies, public images are available on Docker Hub.

Instruction used in Dokerfile

╔═════════════════════════╦═════════════════════════════════════════════════════════════╗
║ Instruction ║ Description ║
╠═════════════════════════╬═════════════════════════════════════════════════════════════╣
║ FROM ║ Create a new build stage from a base image. ║
║ WORKDIR ║ Change working directory. ║
║ COPY ║ Copy files and directories. ║
║ RUN ║ Execute build commands. ║
║ CMD ║ Specify default commands. ║
║ EXPOSE ║ Describe which ports your application is listening on. ║
║ ENV ║ Set environment variables. ║
║ ENTRYPOINT ║ Specify default executable. ║
║ VOLUME ║ Create volume mounts. ║
║ ARG ║ Use build-time variables. ║
║ LABEL ║ Add metadata to an image. ║
║ USER ║ Set user and group ID. ║
║ HEALTHCHECK ║ Check a container's health on startup. ║
║ STOPSIGNAL ║ Specify the system call signal for exiting a container. ║
║ SHELL ║ Set the default shell of an image. ║
║ ONBUILD ║ Specify instructions for when the image is used in a build. ║
║ ADD ║ Add local or remote files and directories. ║
║ MAINTAINER (deprecated) ║ Specify the author of an image. ║
╚═════════════════════════╩═════════════════════════════════════════════════════════════╝

As the best way to learn something is to try it and practice with it, we will focus on direct scenarios of Dockerfile content.

Different scenarios

Remember from part x that Every docker image is built on top of another existing image.

  1. Running a simple node application
# Useing the official Node.js image from the Docker Hub: node:<version>
FROM node:alpine

# Creating the application directory
RUN mkdir -p /app

# changing the working directory
WORKDIR /app

# Copy package.json and package-lock.json files
COPY package*.json ./

# Install application dependencies
RUN npm install

# Copy the rest of the application code
COPY . .

# Expose the application port
EXPOSE 3000

# Command to run the Node.js application
CMD ["node", "app.js"]

You can select which node version you want from here

Explanation:

  • FROM node: Uses a lightweight Node.js image based on Alpine Linux.
  • RUN mkdir -p /app: Creates the application directory.
  • WORKDIR /app: changing the working directory to /app “cd /app”.
  • COPY package.json ./: Copies package.json and package-lock.json to the working directory.
  • RUN npm install: Installs the Node.js dependencies.
  • COPY . . : Copies the rest of the application code to the working directory.
  • EXPOSE 3000: Exposes port 3000.
  • CMD [“node”, “app.js”]: Sets the command to run the Node.js application.
    we use CMD not RUN to specify the command that will run as the starting point of the application.

RUN is used to execute commands during the build process of a Docker image, while CMD is used to specify the default command to run when a Docker container is started from the image.

2. A simple Flask application that runs on port 5000.

# Use the official Python image from the Docker Hub
FROM python

# Copy the requirements file and install dependencies
COPY . /usr/src/app/

# Set the working directory
WORKDIR /usr/src/app

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Expose the application port
EXPOSE 5000

# Command to run the Flask application
CMD ["python", "app.py"]
  • IF you haven’t specify the version,Docker considers it the latest version.
  • You can simply add a / after COPY . /usr/src/app/ to create the directory.

3. Java Spring Boot Application

# Use the official Maven image to build the application
FROM maven:3.8.1-openjdk-11 AS build

# Set the working directory
WORKDIR /usr/src/app

# Copy the pom.xml and install dependencies
COPY pom.xml ./
RUN mvn dependency:go-offline

# Copy the rest of the application code and package it
COPY src ./src
RUN mvn package

# Use the official OpenJDK image to run the application
FROM openjdk:11-jre-slim

# Set the working directory
WORKDIR /usr/src/app

# Copy the packaged jar file from the build stage
COPY --from=build /usr/src/app/target/*.jar app.jar

# Expose the application port
EXPOSE 8080

# Command to run the Spring Boot application
CMD ["java", "-jar", "app.jar"]

4. Ruby on Rails Application

# Use the official Ruby image from the Docker Hub
FROM ruby:2.7

# Install Node.js and Yarn
RUN apt-get update -qq && apt-get install -y nodejs yarn

# Set the working directory
WORKDIR /usr/src/app

# Copy the Gemfile and Gemfile.lock and install dependencies
COPY Gemfile Gemfile.lock ./
RUN bundle install

# Copy the rest of the application code
COPY . .

# Precompile assets (if any)
RUN bundle exec rake assets:precompile

# Expose the application port
EXPOSE 3000

# Command to run the Rails application
CMD ["rails", "server", "-b", "0.0.0.0"]

CMD [“rails”, “server”, “-b”, “0.0.0.0”]: Specifies the command to run the Rails application. This starts the Rails server with the binding set to 0.0.0.0, making it accessible from outside the Docker container.

5. PHP Laravel Application

# Use the official PHP image with Apache from the Docker Hub
FROM php:7.4-apache

# Set the working directory
WORKDIR /var/www/html

# Copy the application code
COPY . .

# Install Composer
RUN apt-get update && apt-get install -y \
libzip-dev \
unzip \
&& docker-php-ext-install zip \
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Install application dependencies
RUN composer install

# Expose the application port
EXPOSE 8000

# Command to run the Laravel application
CMD ["php", "artisan", "serve", "--host", "0.0.0.0", "--port", "8000"]

6. ASP.NET Core Application

# Use the official .NET SDK image to build the application
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build

# Set build-time arguments
ARG BUILD_CONFIGURATION=Release

# Set environment variables
ENV ASPNETCORE_ENVIRONMENT=Production

# Set the working directory
WORKDIR /src

# Copy the csproj file and restore dependencies
COPY *.csproj ./
RUN dotnet restore

# Copy the rest of the application code and build it
COPY . .
RUN dotnet publish -c $BUILD_CONFIGURATION -o /app

# Use the official .NET runtime image to run the application
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS runtime

# Set the working directory
WORKDIR /app

# Copy the published application from the build stage
COPY --from=build /app .

# Expose the application port
EXPOSE 80

# Healthcheck to verify container health
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl --silent --fail http://localhost/ || exit 1

# Command to run the ASP.NET Core application
CMD ["dotnet", "MyApp.dll"]

7. Go Web Application

# Use the official Go image to build the application
FROM golang:1.16 AS build

# Set build-time arguments
ARG APP_NAME=myapp

# Set the working directory
WORKDIR /usr/src/app

# Copy the application code
COPY . .

# Build the application
RUN go build -o app -ldflags "-X main.version=$(date +'%Y%m%d%H%M%S') -X main.appName=$APP_NAME"

# Use a minimal base image to run the application
FROM scratch

# Set environment variables
ENV APP_NAME=myapp

# Copy build artifacts from the build stage
COPY --from=build /usr/src/app/app /app

# Expose the application port
EXPOSE 8080

# Label for metadata
LABEL maintainer="Zyad c3rb3rus@gmail.com"

# Command to run the Go web application
ENTRYPOINT ["/app"]

8. Django Application

# Use the official Python image from the Docker Hub
FROM python:3.9

# Set build-time arguments
ARG DJANGO_SETTINGS_MODULE=mysite.settings.production

# Set the working directory
WORKDIR /usr/src/app

# Copy the requirements file and install dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code
COPY . .

# Expose the application port
EXPOSE 8000

# Label for metadata
LABEL description="Django application for mysite"

# Command to run the Django application
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

9. React Application

# Use the official Node.js image to build the application
FROM node:14 AS build

# Set build-time arguments
ARG NODE_ENV=production

# Set the working directory
WORKDIR /usr/src/app

# Copy package.json and package-lock.json and install dependencies
COPY package*.json ./
RUN npm install --production=$NODE_ENV

# Copy the rest of the application code and build it
COPY . .
RUN npm run build

# Use the official Nginx image to serve the static files
FROM nginx:alpine

# Set environment variable
ENV NGINX_PORT=80

# Copy the build output from the build stage to Nginx
COPY --from=build /usr/src/app/build /usr/share/nginx/html

# Expose the application port
EXPOSE $NGINX_PORT

# Healthcheck to verify container health
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -q --spider http://localhost:$NGINX_PORT/ || exit 1

# Command to run Nginx
CMD ["nginx", "-g", "daemon off;"]

Running applications as a non-root user in Docker containers is a good security practice to minimize the potential impact of security vulnerabilities.

# Use an official Ubuntu base image
FROM ubuntu:20.04

# Label for metadata
LABEL maintainer="c3rb3rus@RedRightHand.com"
LABEL version="1.0"
LABEL description="secure Ubntu Image"

# Set environment variables
ENV PORT=8080

# Create a non-root user and group
RUN groupadd -r appuser && useradd -r -g appuser -d /home/appuser -s /sbin/nologin -c "Docker image user" appuser && \
mkdir -p /home/appuser && chown -R appuser:appuser /home/appuser

# Set the working directory
WORKDIR /app

# Copy files to the working directory
COPY --chown=appuser:appuser . /app

# Install necessary dependencies
RUN apt-get update && apt-get install -y curl python3 python3-pip && \
apt-get clean && rm -rf /var/lib/apt/lists/*

# Switch to non-root user
USER appuser

# Expose the application port
EXPOSE 8080

# Create a volume
VOLUME ["/data"]

# Define the default command
CMD ["python3", "app.py"]

# Set an entry point
ENTRYPOINT ["python3", "app.py"]

# Add an on-build instruction
ONBUILD RUN echo "Building on top of this image"

Some Containerized Security Tools

I have a problem before with gg_dorking dependances by Eslam Akl so I made a Dockerfile for it

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "gg_dorking.py"]

You can create a Docker image to deploy scripts without running them locally:

FROM python:3.9-slim

# Install required dependencies
RUN apt-get update && apt-get install -y \
git \
&& apt-get clean

# Clone the CVE-2021-1675 repository
RUN git clone https://github.com/cube0x0/CVE-2021-1675.git /opt/CVE-2021-1675

# Clone and install the specific impacket version
RUN git clone https://github.com/cube0x0/impacket /opt/impacket \
&& cd /opt/impacket \
&& python3 setup.py install

# Set the working directory to the CVE-2021-1675 folder
WORKDIR /opt/CVE-2021-1675

# Start an interactive shell by default
CMD ["/bin/bash"]

You can use Docker to isolate a toolkit that conflicts with other tools on your local machine (e.g., Impacket)

# Dockerfile for isolating Impacket
FROM python:3.8-alpine as compile
WORKDIR /opt

RUN apk add --no-cache git gcc musl-dev python3-dev libffi-dev openssl-dev cargo
RUN python3 -m pip install virtualenv
RUN virtualenv -p python venv

ENV PATH="/opt/venv/bin:$PATH"

RUN git clone --depth 1 https://github.com/fortra/impacket.git
RUN python3 -m pip install impacket/

FROM python:3.8-alpine
COPY --from=compile /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
ENTRYPOINT ["/bin/sh"]

You can search on GitHub using this dork or find containerized security tools and see how they were implemented. So, try to read these Dockerfiles

https://github.com/AlexisAhmed/BugBountyToolkit/blob/master/Dockerfile
https://github.com/PeterMosmans/tools-image/blob/main/Dockerfile
https://github.com/hawkeyesec/scanner-cli/blob/master/Dockerfile
https://github.com/raesene/dockerized-security-tools/blob/master/burp/Dockerfile

Software Development WORKFLOW

The development workflow begins on a local machine where a simple application is built using typical frontend, backend, and database technologies. Once development is complete, the code is committed to a Git repository, triggering a CI pipeline. This pipeline builds the application’s container image and pushes it to a Docker repository. From there, the application can be pulled and run on a development server, enabling other developers or QA testers to access and test the application.

Before pushing the image to the repository in the inner-loop development workflow for Docker, there is an iterative cycle. This process involves coding the application locally and writing Dockerfiles to specify the environment and dependencies. Docker images are then built from these Dockerfiles and tested locally, with multiple iterations to refine the application code and Dockerfiles. After successful local testing, Docker Compose can be introduced to define and manage multi-container applications, streamlining the process of running and testing the application in a cohesive, containerized setup.

Docker-compose

Docker Compose simplifies the management of multi-container applications. With Docker Compose, you can describe the configuration of your application services, networks, and volumes in a YAML file. This enables you to create and start all the services from your configuration with a single command.

Example docker-compose.yml file for a simple web application with a frontend, backend, and database

docker-compose.yml

version: '3.8'

services:
frontend:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./frontend:/usr/share/nginx/html
networks:
- app-network

backend:
image: node:14
volumes:
- ./backend:/app
working_dir: /app
command: npm start
networks:
- app-network

database:
image: postgres:latest
environment:
POSTGRES_USER: exampleuser
POSTGRES_PASSWORD: examplepass
POSTGRES_DB: exampledb
volumes:
- db-data:/var/lib/postgresql/data
networks:
- app-network

networks:
app-network:
driver: bridge

volumes:
db-data:

To deploy a full project check

Wait for the coming parts 🐳

--

--