Authentication Lab Walkthroughs

The Authentication Lab is a selection of challenges all related to authentication or authorisation. They are all taken, in some way, from real world examples I've come across during tests or have been suggested by other testers.

You can play the latest version of all the challenges in my Authentication Lab or grab the code from GitHub and build your own lab. The lab is written in Golang and should be fairly easy to install and get running.

The rest of this site contains further information and walkthroughs of each of the challenges in the lab, I recommend giving the challenges a go then coming back here if you are having problems or to see whether your solution matches mine.

IP Based Auth Bypass

Find the deliberate IP based authentication bypass.

Go to the challenge

Show Walkthrough

I've seen this on a real test, the developer/admin didn't want to have to login each time he wanted to use the site so setup a page which checked the source IP of the request and, if it matched his fixed IP, then it ran the login process and authenticated him. What he didn't realise was that the code he used to check the IP also checked the "X-Forwarded-For" header when working out the source IP and so all it took to login as him was to add the header and make the request - Instant site admin!

Curl lets you add an extra header using the "-H" parameter, to add the "X-Forwarded-For" header with the IP 192.168.0.1 you would do -H "X-Forwarded-For: 192.168.0.1".

The clue given says the DHCP range for the network is 192.168.0.100-200, this suggests the network uses the subnet 192.168.0.0/24 so lets write a Bash loop to step through each IP and see what we get.

for ip in {1..255}
do
  curl https://authlab.digi.ninja/Bypass -H "X-Forwarded-For: 192.168.0.$ip" -o $ip.out
done

This will give you 255 files, named from 1.out to 255.out, to manually inspect to spot any differences. A quick win is often had by checking file sizes, here is a snippet of running ls-l *out on the output directory.

-rw-r--r-- 1 robin robin 926 Apr 19 15:00 147.out
-rw-r--r-- 1 robin robin 926 Apr 19 15:00 148.out
-rw-r--r-- 1 robin robin 926 Apr 19 15:00 149.out
-rw-r--r-- 1 robin robin 872 Apr 19 15:00 14.out
-rw-r--r-- 1 robin robin 926 Apr 19 15:00 150.out
-rw-r--r-- 1 robin robin 926 Apr 19 15:00 151.out
-rw-r--r-- 1 robin robin 926 Apr 19 15:00 152.out

Notice 14.out is smaller than the others? Lets see what makes it different to its neighbours.

$ diff 14.out 15.out
32c32,35
<     Welcome Alex, you are now logged in.
---
>     Invalid IP, bypass not allowed.
>   </p>
>   <p>
>     Your IP is: 192.168.0.15, 90.254.173.85, ::1

There we have it, 192.168.0.14 is Alex's magic IP address and, if as we set our "X-Forwarded-For" header to that, we can automatically log in as him from anywhere we want to.

There are lots of other ways this could be solved, some much more efficiently, especially if you knew what the successful login page looked like, but it works and shows you don't always need to use advanced tools, simple command line scripting will help get the job done.

Timing Attacks

Sometimes even the best login system can leak information through side-channels.

Go to the challenge

Show Walkthrough

I've not come across this in the real world associated with authentication but there are plenty of examples of this type of side-channel attack from other researchers. The vulnerability is caused by the system taking a different amount of time to process requests based on the input, in this case, invalid usernames are rejected quickly and so there is a short response time, valid usernames get more processing and so take longer to return.

Before any testing starts, I'm going to stress this challenge is to find valid usernames, the system does not allow complete logins for any user, so once you've found the usernames, please do not try to brute force passwords, all you'll do is waste my bandwidth and yours.

As the hint says, there are four valid users and their names can be found in the two lists of names linked to. (Boys, Girls)

First we will do a bit of command line Kung Fu to get our user name list. This could probably be optimised down further but it works and demonstrates my thinking when processing the page.

curl -s https://www.babble.com/pregnancy/1000-most-popular-boy-names/ | grep "<li>" | sed 's/[<\/]*<\/.*//' | sed 's/.*>//' > /tmp/boys
curl -s https://www.babble.com/pregnancy/1000-most-popular-girl-names/ | grep "<li>" | sed 's/[<\/]*<\/.*//' | sed 's/.*>//' > /tmp/girls

Now we have the list, let's use Burp Intruder to throw it against the site to see if we can identify the valid users. This will work with both paid and free versions of Burp, the free version may just take a little longer to run.

Assuming your browser is set up to run through Burp, do a login with a random username and then find it in the proxy history.

Burp Proxy History showing the POST request with the login credentials in it

Send the request to Intruder with Ctrl I or from the context menu and highlight the username as that is the only field we are going to be attacking.

Burp Intruder Positions tab with the username POST parameter selected

In the Payloads tab, load the lists we just created, this should give you 2,000 requests, if you've got more, check you only have one parameter highlighted on the Payload Positions page, if you've got less, something when wrong creating your name lists.

Burp Intruder Payloads tab showing 2000 names loaded

Now, start the attack and let it run through to the end.

The difference we are looking for is the time taken to get the response. This is not shown by default in the results table so needs adding from the Columns menu. There are two timing columns, "Response received" and "Response completed". The first is how long till the response starts coming back to the client, the second is the total time for the complete response to be received. It is worth looking at both these figures as either could show a difference.

The Columns menu from the Burp Intruder Attack window

With the columns added and a sort in place, four results stand out as taking significantly longer than the others with one result (Marlee) taking a little longer. As we were told there were four valid users, it is fairly obvious that we have our results.

The Intruder Attack window sorted by Response Received column showing 4 names at over 3000 miliseconds compared to 251 and below for the other results

To confirm this, I would send each of those results, along with the Marlee outlier, to the Repeater and run each one manually to double check the timings and to make sure the results are repeatable. Re-running the request for Marlee showed that the response time dropped back down in line with the other slower requests which ruled it out as being a possible valid username.

The way I've ran this test is a bit rough and ready but we can get away with it as the differences in the timings are deliberately exaggerated. If this was done on a real site, the differences may not be as obvious and some may be false positives caused by issues not related to the processing of the specific request, for example, if the server load is increased by the request for Zoe, it may still be high as the request for Sarah comes in and so affect her timing. Ideally you want to be running this single threaded over a consistent internet connection when the site is known to be running at a steady load, all this will help to reduce any outside influence on the timings to make the as accurate as possible.

Client Side Auth

Never trust anything coming from the user, especially authentication.

Go to the challenge

Show Walkthrough

This is a variation of a technique I saw on a test where the site had no dynamic server side code but wanted a way to give certain users access to a small amount of private information.

In this challenge, the authentication is done solely in JavaScript. How do we know this? Lets look at how the page works and see what's there. After a quick look around, this JavaScript include file stands out:

The client side auth JavaScript library open in the browser debugger

A function called login which does something with a username and password, definitely looks like client side authentication to me. Treating the function as pseudocode, this is how I would read through it:

  • Get the username and password from the page
  • Concatenate them together with a colon between them
  • Define a couple of strings with interesting names
  • XOR the concatenated string with a secret and check the result
  • If the result is true, MD5 the password and then redirect the user to a new URL based on that value

It would be nice to jump directly to the part of the code which generates the final URL and just attack that, but doing that would require us to make requests to the target site, which is noisy, let's try to keep all this client side for now.

In a bit more depth, the code starts by extracting the username and password from the DOM and then concatenating them together. Next it XORs them with a "secret" key to produce an "encrypted" string. This string is then compared to the expected value to confirm whether the entered values are correct. As XOR is commutative, XORing the expected value with the secret key should give us the username and password combination.

It would not be hard to simply take the two values out of the file and put them through an online XOR app or to write a couple of lines of code in your preferred language to do the work, but as the site implements its own MD5 function I would rather use that, just in case it has any bugs which means it doesn't do a true XOR.

From the screenshot above, the three lines you are interested in are the definition of the encrypted and secret strings and the line that shows you how to run the xorString function. Here I simply copy and paste the two definitions into the console and then pass them to xorString.

Running the XOR in the browser console

Looking at the result, it is fairly obvious that the call worked as what was returned is a readable string and is in the format we expect based on the concatenation we saw earlier. All that is left is to check the credentials work by logging in with them.

A successful log as the user Sid

Yay, success!

In the real example I saw, the site used two custom written hashing algorithms rather than an XOR, this meant it was not possible to simply reverse the "encrypted" string. What I ended up doing was to take the hashing functions and writing a brute force script in NodeJS using them. This was made slightly easier as the site only had two usernames (which I knew) and I assumed (correctly) that the password would be a relatively simple one. Because all this was done client side, i.e. on my machine, the first the client knew about it was when successfully logged in with the correct details.

Regardless of what algorithms are used, as all the authentication is done on the client side, we can usually work out what is going on and either bypass it or brute force it, without the victim knowing anything malicious is happening till it is too late.

Leaky JWT

See how easy it is to leak information in a JWT.

Go to the challenge

Show Walkthrough

This is the JWT that is given in the challenge:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6ImFkbWluIiwicGFzc3dvcmQiOiIyYWM5Y2I3ZGMwMmIzYzAwODNlYjcwODk4ZTU0OWI2MyIsInVzZXJuYW1lIjoiam9lIn0.6j3NrK-0C7K8gmaWeB9CCyZuQKfvVEAl4KhitRN2p5k

It may look like a random, unreadable, string of characters, but it is actually a fully documented structure which is very easy to decode once you know the layout and encoding. I'm not going to go in to too much depth on how JWTs work but I will explain the basics to help solve this challenge.

A JWT is formed of three parts, a header, payload and signature. The first two are written in JSON and Base64 encoded, the payload is signed using one of a number of techniques. All three parts are then stuck together using a "." as the separator.

To view the content of a JWT, it is a simple process of splitting it down into the three parts and then Base64 decoding the first two. Something to watch for here, is that the encoded strings are not padded with ='s as normal Base64 strings would be, if you pass them to an app, such as the Linux command line tool base64, it will probably give you the original text but may also give a warning about the input being malformed. Don't worry about this, it is done to save space and libraries which handle JWTs know how to handle this correctly.

Now we know the basics, lets give it a go. Splitting it down we get:

  • Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  • Payload: eyJsZXZlbCI6ImFkbWluIiwicGFzc3dvcmQiOiIyYWM5Y2I3ZGMwMmIzYzAwODNlYjcwODk4ZTU0OWI2MyIsInVzZXJuYW1lIjoiam9lIn0
  • Signature: 6j3NrK-0C7K8gmaWeB9CCyZuQKfvVEAl4KhitRN2p5k

Next I'll use the following one liner to decode the segments:

echo -n <segment here> | base64 -d | jq

Decoding the header gives:

{
  "alg": "HS256",
  "typ": "JWT"
}

This confirms that the token is a JWT and specifies that the HMAC SHA256 algorithm was used to generate the signature.

Decoding the payload gives:

{
  "level": "admin",
  "password": "2ac9cb7dc02b3c0083eb70898e549b63",
  "username": "joe"
}

That password field looks interesting, about the right length to be an MD5. Lets ask the most powerful password cracking rig around, Google.

Google search results for the MD5 found in the payload

Looks like we've got ourselves Joe's credentials and Joe is an admin. Lets give them a try.

A successful login using Joe's credentials

Success, we are now logged in as Joe.

The conclusion here, as a developer, be careful what you put in your tokens, they may look like unintelligible blobs once generated but they can be decoded, and a little slip can open a huge security hole. As a tester, it is always worth checking what the developer has put in the token. I like JOSEPH, the JSON Web Token Attacker extension for Burp Suite. It spots tokens in requests and adds a tab for easy decoding.

There is a lot more to JWT than I've described here, for example JWTs can be encrypted as well as hashed, this changes the algorithm used which is then reflected in the header. Auth0 have done a very good introduction to JWT in their blog post Introduction to JSON Web Tokens or, for more technical details, see RFC 7519. To save effort on manually decoding a JWT, the Auth0 site also has an online tool to do it all for you, JWT Debugger.

CVE-2019-7644 - JWT Signature Disclosure

Sometimes all it takes is a little information disclosure to break an authentication system.

Go to the challenge

Show Walkthrough

Rather than try to explain this I'll simply point you at this vulnerability disclosure from Auth0 that I've replicated here. Go read that then come back to learn how to exploit it.

Now we understand the vulnerability, lets look at the exploit. First, lets try logging in using the token given. Copy the token, put it in the input box and click Login.

A successful login as Sid, a normal user

As you can see, we've logged in as Sid who is a normal user. The challenge is to elevate our privileges and become and administrator, so lets decode the token and see what it contains. Here I've used this JWT Debugger as it make it nice and easy to see what contains and make changes.

The decoding of the original JWT

As you can see, the payload shows the username as "sid" and gives his level as "user". If you modify the level to "admin", the encoded value will be automatically updated for you.

A new JWT generated with the level updated to admin

Lets take this new token and try to log in with it.

A failed login due to invalid signature showing the correct signature

As expected, the login has failed due to the signature not matching what is expected. Normally you would only see the "login failed" error, but in this instance, the site also leaks the signature that it was expecting, "FO8ZDKSDNVnt_VB3f35_ofEMGFDTrv0dVo8hjGXDtn8".

If we swap the new signature for the incorrect one, our token now looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6ImFkbWluIiwidXNlciI6InNpZCJ9.FO8ZDKSDNVnt_VB3f35_ofEMGFDTrv0dVo8hjGXDtn8

And if we try to login with this token:

A successful login as an admin using the new signature

We are now successfully logged in as Sid but have managed to increase our access from normal user to administrator.

This was a silly mistake by Auth0 but to their credit, they held their hands up and accepted it. It shows how debug code, if left in place on production systems, can easily lead to serious issues. Developers, do not write off the low level "verbose error message" issues as being nothing to worry about, sometimes they are. Testers, read everything that comes back from a site, sometimes a simple error message can make a huge difference.

Bonus points for anyone who wondered about modifying the token to include XSS. I tried it, and, from a few quick experiments, the templating engine appears to correctly encode things so it does not work

User Agents

This challenge is similar to the Client Side challenge where the server assumes that the client can be trusted and bases its authentication decisions on what the client tells it.

Go to the challenge

Show Walkthrough

This is another real life situation I came across, a client who used desktop apps to talk to a web app relied solely on the user agent string for authentication once the user had authenticated themselves to the desktop app itself. Based on this, it was fairly trivial to switch my user agent and get full access to various areas of the site.

To see what is going on here, and to work out what we need to access the site, lets take a look at the ping script provided:

#!/usr/bin/env bash

echo "Pinging server to check it is up."
echo

ret=`curl -s -A "authlab desktop app" https://authlab.digi.ninja/UserAgentPing`

if [[ $ret =~ "Pong" ]]; then
	echo "The server is up"
else
	echo "The server is not up"
fi

The script uses curl to hit the URL, stores the result in a variable called ret and then checks that variable to see if it contains the word "Pong". If it does, the server is up and listening, if not, it assumes it is down.

For those who aren't experts at curling things, this is a snippet from the man page:

-A, --user-agent 
	(HTTP) Specify the User-Agent string to send to the HTTP server. To encode blanks in the string, surround the string with single quote marks.
-s, --silent
	Silent or quiet mode. Don't show progress meter or error messages.

The -A flag is used to tell curl to use the user agent string specified rather than its default value and -s says not to print the progress bar. From these, I doubt the progress bar change will make any difference so lets focus on the user agent string.

First, lets run the curl command in isolation:

curl -s -A "authlab desktop app" https://authlab.digi.ninja/UserAgentPing

Pong

That seems to run as we would expect, we get the "Pong" back. Now lets try it without forcing the user agent string:

curl -s https://authlab.digi.ninja/UserAgentPing

Not Authenticated

So, with the default curl user agent string (which on my current box is "curl/7.58.0"), the site says that the user is not authenticated. How about trying with a random string to see what happens:

curl -s -A "random user agent string" https://authlab.digi.ninja/UserAgentPing

Not Authenticated

Still not authenticated, so it looks like the app is looking for the specific string "authlab desktop app".

The challenge says we have to access the challenge page using the correct user agent to view the secret message. There are a few ways this can be done, through curl, through a user agent changing extension, or by setting a proxy up to transparently change the string of every request that passes through it. As I've already got an extension installed, I'll use that option here, I simply tell it to use the required string:

Setting up a user agent switcher extension in the browser

Refreshing the page once it is setup shows us the secret message:

The page showing the secret message 'Badger loves mashed potatoes'

That is all there is to it. In the real world example, I now had full access to whole areas of the web app which were supposed to require authentication, all because I knew the secret phrase.

To developers, please, do not rely on things like this as authentication mechanisms, they aren't. If you are building a non-web based application which talks to a web API, use correct session or identity management. To testers, if there is more than one way to access data, it is always worth looking how the different areas handle authentication, authorisation and session management. Do not assume that the same developer has worked on all the areas, and that if one area is written well, all the others will be.

One final tip, if you are using an extension to change the user agent, remember to change it back afterwards. There are some sites, specifically thinking about Google here, that change their styling and functionality based on the user agent. If you set it to some random value, you might find the services offered degraded which can be quite annoying until you work out what is causing it.

Project Categories

Support The Site

I don't get paid for any of the projects on this site so if you'd like to support my work you can do so by using the affiliate links below where I either get account credits or cash back. Usually only pennies, but they all add up.