Skip to main content

Module 09 — Credential Sniffing (Attack / Understand / Fix)

Objective

Capture plaintext credentials from HTTP traffic on the internal network using tcpdump, understand why SSL/TLS matters, then secure external access with HTTPS via Tailscale Funnel.

By the end of this module you will have:

OutcomeDetail
CapturedPlaintext passwords and customer PII from network traffic
UnderstoodWhy HTTP exposes everything to anyone on the same network
FixedExternal access secured with HTTPS via Tailscale Funnel
AcknowledgedInternal traffic gap and the plan to address it in Module 11

Prerequisites

  • Module 08 complete (SQL injection fixed)
  • App running and accessible via Nginx at http://192.168.56.13
  • Tailscale Funnel configured (from Module 07)
  • SSH access to web-server and app-server

1. Introduction — From application to network

In Module 08 you attacked the application code. Now you are going to attack the network.

All traffic between your browser, Nginx, and the Go backend is unencrypted HTTP. This means anyone who can see network traffic can read everything — including passwords, session cookies, and customer data.

You do not need to find a bug in the code. You just need to be on the same network and run a packet capture tool.


2. CTF Challenge

Challenge: SSH into web-server. Using only command-line tools, capture the admin password by watching network traffic while someone logs in. You have 20 minutes.

Rules:

  • You can only use tools available on the web-server (or that you install)
  • You need two terminal windows: one to capture, one to trigger the login
  • Your goal: see the plaintext password in the captured output

If you get stuck, reveal the hints below one at a time:

Hint 1

The tool tcpdump can capture network packets. Install it with sudo apt install -y tcpdump.

Hint 2

HTTP traffic on the Host-Only network uses interface enp0s8. You can verify with ip addr.

Hint 3

Try filtering for port 80 and look for POST data: sudo tcpdump -i enp0s8 -A port 80

Once you have tried the challenge (or after 20 minutes), continue to the guided attacks below.


3. Guided Attack — Sniff HTTP traffic with tcpdump

You need two terminal windows open simultaneously.

Terminal 1 — Start capture on web-server

ssh web-server
sudo apt install -y tcpdump
sudo tcpdump -i enp0s8 -A port 80 2>/dev/null | grep -E "password|username|Password|Username"

This command:

  • -i enp0s8 — listens on the Host-Only network interface
  • -A — prints packet contents in ASCII (human-readable)
  • port 80 — filters for HTTP traffic only
  • grep — shows only lines containing credential-related keywords

Leave this running.

Terminal 2 — Login to the app (from your Mac)

curl -X POST http://192.168.56.13/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'

Expected output in Terminal 1

Switch back to Terminal 1. You should see output like:

{"username":"admin","password":"admin123"}

The JSON body containing the admin password appears in plaintext in the capture. Anyone running tcpdump on this network just stole the admin credentials.

Press Ctrl+C to stop the capture.


4. Guided Attack — Sniff traffic between Nginx and Go backend

The Nginx reverse proxy on web-server forwards requests to the Go backend on app-server over HTTP. That hop is also unencrypted.

Terminal 1 — Capture on app-server (port 8080)

ssh app-server
sudo apt install -y tcpdump
sudo tcpdump -i enp0s8 -A port 8080 2>/dev/null | grep -E "password|username"

Terminal 2 — Login again via Nginx

curl -X POST http://192.168.56.13/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'

The same credentials appear in the capture on app-server. The password is visible on both network hops.

Press Ctrl+C to stop the capture.

Bonus — Capture customer PII

Start a new capture filtering for customer data:

Terminal 1 — Capture customer data on app-server

sudo tcpdump -i enp0s8 -A port 8080 2>/dev/null | grep -E "name|email|phone"

Terminal 2 — List customers

curl -b cookies.txt http://192.168.56.13/api/customers

All customer data — names, email addresses, phone numbers, addresses — appears in plaintext in the capture. Every piece of PII your application handles is visible to anyone sniffing the network.


5. Why This Works

HTTP is plaintext

HTTP sends everything as plain text. There is zero encryption. The protocol was designed in 1991 when the internet was a trusted academic network. It was never designed to protect data in transit.

The attack surface

Anyone on the same network segment can capture traffic. On the Host-Only network (192.168.56.0/24), all three VMs share the same Layer 2 segment. Any VM can see traffic destined for any other VM.

Real-world equivalents:

  • Shared WiFi at a coffee shop or hotel
  • A compromised network switch with port mirroring
  • A man-in-the-middle attack via ARP spoofing
  • A rogue device on a corporate network

What is exposed

Data typeExample from capture
Login credentials{"username":"admin","password":"admin123"}
Session cookiesCookie: session=abc123...
Customer names"name":"John Smith"
Email addresses"email":"john@example.com"
Phone numbers"phone":"555-0100"
Addresses"address":"123 Main St"

The Tailscale Funnel gap

Even with Tailscale Funnel from Module 07, the funnel only encrypts traffic from the browser to the web-server. Internal traffic between VMs is still plaintext:

Browser --[HTTPS]--> Tailscale Funnel --[encrypted]--> web-server --[HTTP]--> app-server
^^^^^^^^^^^^^^^^^^^^^^^
This part is plaintext

6. The Fix — HTTPS via Tailscale Funnel

You cannot easily add TLS certificates to the internal network in a training environment. Instead, you will secure external access by using Tailscale Funnel, and acknowledge the internal gap.

Step A — Tailscale Funnel serves HTTPS by default

Tailscale Funnel provides HTTPS automatically — no manual SSL configuration needed. When you enable Funnel on the web-server, it:

  1. Obtains and manages TLS certificates automatically
  2. Always serves HTTPS — HTTP requests are redirected to HTTPS
  3. Handles certificate renewal without intervention

No dashboard configuration is required. Funnel is secure by default.

Now all public access to your app is encrypted from the browser to the web-server via Tailscale.

Step B — Understand the remaining gap

After Step A, the encryption looks like this:

SegmentEncrypted?How
Browser to Tailscale FunnelYesHTTPS (TLS certificate from Tailscale)
Tailscale Funnel to web-serverYesEncrypted via Tailscale
Nginx to Go backend (web-server to app-server)NoStill plaintext HTTP

The internal traffic between Nginx and the Go backend is still unencrypted. In production, you would fix this with:

  • Internal TLS between services — each service gets its own certificate
  • A service mesh (like Istio or Linkerd) — handles mutual TLS automatically
  • Network segmentation — restrict who can see internal traffic

For this training, you will address the internal gap in Module 11 by using firewall rules to restrict which machines can communicate with each other. This does not encrypt the traffic, but it limits who can sniff it.

Step C — Note on Nginx configuration

Tailscale Funnel connects to localhost:80 on the web-server, so external traffic already arrives through the encrypted funnel. In Module 11 (firewalls), you will add rules so that Nginx on port 80 only accepts connections from localhost (the funnel) and rejects direct access from other machines on the network.


7. Verify

Verify external HTTPS works

Open your Tailscale Funnel URL in a browser. Confirm:

  • The URL starts with https://
  • The browser shows a lock icon (valid TLS certificate)
  • Accessing http:// redirects to https://

Verify tcpdump no longer captures credentials from external access

Terminal 1 — Start capture on web-server:

ssh web-server
sudo tcpdump -i enp0s8 -A port 80 2>/dev/null | grep -E "password|username"

Terminal 2 — Login via the Tailscale Funnel HTTPS URL:

curl -X POST https://YOUR-TUNNEL-DOMAIN/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'

Expected: No plaintext credentials appear in the tcpdump output on enp0s8. The traffic from the browser arrives through Tailscale Funnel, which terminates on localhost — it never crosses the Host-Only network interface.

Acknowledge the internal gap

Run the capture on app-server (port 8080) and login via the HTTPS URL. You will still see plaintext on the internal hop between Nginx and the Go backend. This is expected. The firewall rules in Module 11 will restrict access to this internal traffic.


Troubleshooting

ProblemSolution
tcpdump: permission deniedUse sudo. tcpdump requires root privileges to capture packets.
No packets capturedWrong interface. Run ip addr to check — look for the interface with a 192.168.56.x address.
Tailscale Funnel not serving HTTPSVerify Funnel is running with tailscale funnel status. Restart if needed.
Still seeing plaintext on internal networkExpected. The fix in this module only covers external traffic. Internal traffic is addressed in Module 11.
tcpdump: command not foundInstall it: sudo apt install -y tcpdump

Summary

What you didWhat you learned
Captured login credentials with tcpdumpHTTP is plaintext — anyone on the network can read everything
Captured customer PII from API responsesIt is not just passwords — all data is exposed
Sniffed traffic between Nginx and backendEvery internal hop without TLS is a risk
Enabled HTTPS via Tailscale FunnelExternal traffic is now encrypted end-to-edge
Identified the internal traffic gapDefense in depth requires multiple layers (TLS + firewalls)

The one rule to remember: Never send sensitive data over an unencrypted connection. If the network is not encrypted, assume someone is watching.