In GUID We Trust
GUIDs (often called UUIDs) are widely used in modern web applications. However, seemingly very few penetration testers and bug bounty hunters are aware of the different versions of GUIDs and the security issues associated with using the wrong one.
In this blog post I'll walk through an account takeover issue from a recent penetration test where GUIDs were used as password reset tokens:
If you've already spotted the issue and think you know how to exploit it, then you may want to skip to the CTF section at the end, and have a look at the tool I've released. Otherwise, read on.
GUID Versions
There are two characters in every GUID which indicate some information about the GUID:
The first of these is the version number, which can be found directly after the second hyphen. For example, the GUID shown above is a version 4 GUID.
There are five possible values for this version detailed in the RFC:
Version 0
Only seen in the nil GUID ("00000000-0000-0000-0000-000000000000").
Version 1
The GUID is generated in a predictable manner based on:
- The current time
- A randomly generated "clock sequence" which remains constant between GUIDs during the uptime of the generating system
- A "node ID", which is generated based on the system's MAC address if it is available
Version 3
The GUID is generated using an MD5 hash of a provided name and namespace.
Version 4
The GUID is randomly generated.
Version 5
The GUID is generated using a SHA1 hash of a provided name and namespace.
If you only take one thing away from this blog post, it should be to always look at a GUIDs version and become mildly concerned when it isn't 4. Once you start doing this, you can become very quick at spotting the version, and it almost becomes subconscious.
While these versions are defined in the standard, some applications will just encode 128 bits of random data as hex and add hyphens in the right places to generate GUIDs rather than following the RFC. All rules about how the GUIDs are generated will go out the window in these situations, but you should be able to spot them easily as the version number will change between GUIDs.
Attacking Password Reset Functionality
If you look at the link from the start of this post you might now be able to spot the issue:
This link contains a version 1 GUID, which is generated using predictable data. To attack this functionality, we want to issue a password reset request for another user, and then predict the GUID that was included in the link emailed to that user. For this, we need to know the approximate time the GUID was generated, as well as the node ID and clock sequence of the generating system. I've released a small tool to print information about version 1 GUIDs and generate them to help with this process.
We begin attacking this functionality by issuing a password reset request for an account we own, and then inspecting the GUID sent to us in the password reset link:
The timestamp is represented as the number of 100-nanosecond intervals since midnight UTC on 15 October 1582 (the date of Gregorian reform to the Christian calendar according to the RFC), because this is clearly how any rational person represents time. It's uncommon for systems to use this level of precision when generating GUIDS however, so usually the last four digits of the timestamp are zero, indicating that we're measuring time to the nearest millisecond. It's important to take note of this level of precision, as it drastically affects the number of GUIDs we need to guess.
We now generate a password reset request for our victim's account and take note of the server time when this was generated. If the server returns the "Date" header, then this is easy. Otherwise, we'll have to work it out based on your local time, and the timestamp encoded in the GUIDs sent in your password reset links.
guidtool will extract the node ID and clock sequence from a sample GUID you provide, so we just need to give it a sample GUID from one of our password reset links, the estimated time the GUID was generated to the nearest second, and the precision we want in our timestamps:
The "-p" parameter provides the precision using the number of 100-nanosecond intervals between timestamps. The easy way to work out the value of this is to copy the number of zeros you see at the end of the timestamp when you ran "guidtool -i <guid>" with a one before them.
The output here is every possible GUID the server could have generated during the one second before and one second after our specified time . If our estimate of when the victim's password reset link was generated is within one second of the actual value, then their token will be in this output.
All we need to do now is to submit the password reset using every GUID from this output, and when we use the one that's in the victim's password reset link, we will change their password and gain access to their account.
The issue of predictable tokens is of course not limited to password reset functionality, though I hope this example effectively shows the danger of using an insecure GUID version in applications.
CTF
I've created a little CTF challenge to show this issue, which can be found here:
There are two flags – the first should be straightforward to find once you've read and understood this blog post, while the second is designed to be a harder follow-up for when you've found the first flag.
One Last Thing: Race Conditions and Version 1 GUIDs
Most GUID libraries will try not to generate the same version 1 GUID twice. If the library generates timestamps to the nearest millisecond and is asked to generate two GUIDs within the same millisecond, it will typically add 1 to the timestamp of the second GUID:
This may come in useful in some situations when attacking version 1 GUIDs. You can try generate a GUID you can see and a GUID you can't see within the same millisecond. If the GUID you can see has a 1 added to the timestamp, it is very likely that the GUID you can't see will be the same, but without the 1 added to the timestamp.
I haven't come across a scenario where this approach is required, but if you find one I'd be interested to hear about it. You can find me at @_danielthatcher on Twitter if you'd like to share.