TUCTF 2019: Web Challenges

I played in the TUCTF event with my team this weekend.  Thanks to the organizers for another great event!  It was one of my favorites last year as well.

This post has walkthroughs for the 8 web challenges, plus RNGeesus.

Open Door

Here’s our challenge description:

This challenge is an open door; show us you know how to find the key.

If we open up the website at http://chal.tuctf.com:30002/, we see a welcome message, photo, and lots of Papyrus (ugh).

If we open up dev tools and look at the HTML for the page, we see that the flag has been included as a comment.

Takeaway:  always check View Page Source, or the elements view in Dev Tools.

Test Test Test

Here’s our challenge description:

There are so many tests going on right now, why don’t take a deep breath and list them out before you forget one?

If we open up this site (at chal.tuctf.com:30004), we see some text and a picture.

There’s nothing too special in the HTML for the page.  The only thing that stands out is that the image is located in img/TEST.jpg.

If we visit the img/ directory, we see the TEST.jpg file as expected, plus a TODO.txt file.

The TODO.txt file says:

So, let’s check out http://chal.tuctf.com:30004/flag.php.  If you tried this in a browser, you get redirected back to the index page.  So, let’s try using curl instead:

$ curl http://chal.tuctf.com:30004/flag.php
<link href="test.css" rel="stylesheet" type="text/css">TODO: put more stuff here before the test<br>Then print the flag TUCTF{d0nt_l34v3_y0ur_d1r3ct0ry_h4n61n6}<meta http-equiv='refresh' content='0;url=index.html'>

And there’s our flag!

_Takeaway:  look at any directories you can find referenced in the HTML, and also try using curl or Burp Suite if you’re unable to view what you want using just a browser.  _

Router Where Art Thou?

Here’s our challenge description:

Interesting login page, think you can crack it?

If we open up the challenge page (chal.tuctf.com:30006), we see a login page for a Fortinet router at http://chal.tuctf.com:30006/0.html.

When I started this challenge, it opened to /2.html (it seems that later, it was changed to open to 0.html).  In any case, SQLi didn’t work, so I opened up dev tools and looked at the source.

There are a number of included files like login.js that can’t be found.  There’s also a Forninet icon file, which is how I knew the router page was Fortinet (I save my brain space for other useless trivia instead).

I Googled for “fortinet router default username and password” and the answer was a username of “-” and password of “-".  Those creds didn’t work, though.

Next, I noticed the page name (0.html), and tried switching it to 1.html.  This page shows a different router (SonicWall).

A search for “Sonicwall router default username and password” also returns “-” and “-".  But it doesn’t work on this page.

I increased the page number once more to 2.html and saw a Palo Alto router page.

One more Google search (“Palo Alto router default username and password”) leads me to “admin” / “admin” credentials.  Aaaaaand it works:

Takeaway:  it’s always worth trying the default creds!

And Now, For Something Completely Different

Here’s our challenge description:

We all know Black Friday is the time for shopping. Can you find us a flag on this online store?

If we check out the website, we see a bunch of items for sale:

I couldn’t register and I also couldn’t get past the login page, so I started looking around for other options.  In the HTML for page, I noticed this comment:

If we go to /welcome/test, we see:

I originally thought this was an XSS challenge because you can swap out “test” with an XSS payload and it will trigger an alert.  But, no.  While messing around with different options, I managed to cause an error.  The error message details caught my eye:

I did a Google search for known vulnerabilities in the tornado framework and found this blog post.  The TL;DR is that tornado is a python framework, and the particular version that we’re working with (or maybe all versions?) have a Server Side Template Injection vulnerability, which allows us to pass in python code to be executed.

Let’s take advantage of this with a simple test payload:  {{ 2 * 2 }}.  We need to URL encode this first, and our resulting URL will be http://chal.tuctf.com:30007/welcome/%7B%7B2%20+%202%7D%7D.  We expect this to print “Welcome 4”, which it does.

Next, let’s figure out how to list files in the web directory.  The blog post shows how to import a library such as os.   Our payload will be: {% import os %}{{ os.listdir('.') }}.  URL encoded, this is http://chal.tuctf.com:30007/welcome/%7B%%20import%20os%20%%7D%7B%7B%20os.listdir('.')%20%7D%7D.

This confirms that flag.txt is in this directory.  All that’s left to do is read the value out:  {% import os %}{{ open('flag.txt').read() }}.  The resulting URL encoded payload is http://chal.tuctf.com:30007/welcome/%7B%25%20import%20os%20%25%7D%7B%7B%20open('flag.txt').read()%20%7D%7D.

_Takeaway:  if you can trigger an error or otherwise determine the technology or framework used, look for known vulnerabilities.  _

Login to Access

This one took me an embarrassingly long time to figure out 😭… but I learned quite a bit.  Here’s our challenge description:

If at first you don’t login, maybe back up.

The website (http://chal.tuctf.com:30001/) shows you a basic login form, with more Papyrus.

Both fields are vulnerable to SQL injection.  If you enter in ' OR 1=1 -- , it will forward you to the “actual” login page.

But, even though both login forms post to login.php, SQLi does not work on the second form.  The hint tells us to “back up”, which the CTF mods later said was a noun, not a verb.

The answer it turns out is a .bak file, which my teammate figured out.  If you visit http://chal.tuctf.com:30001/login.php.bak, you’ll get a download of the backup PHP login.php file:

  	$username = $_POST["username"];
  	$password = $_POST["password"];
	$flag = "TUCTF{b4ckup5_0f_php?_1t5_m0r3_c0mm0n_th4n_y0u_th1nk}";
  	$query = "SELECT * FROM users WHERE user='$username' AND password='$password'";

	if (true){
		echo '<link href="login.css" rel="stylesheet" type="text/css">';
                echo "<h1>Login failed.</h1>";
  	else {
		$result = mysqli_query($conn,$query);
    		$row = mysqli_fetch_array($result,MYSQLI_NUM);

    		if ($row) {
			echo "<title>You found it!</title>";
                	echo '<link href="login.css" rel="stylesheet" type="text/css">';
                	echo "<h1>$flag</h1>";
		else {
			echo '<link href="login.css" rel="stylesheet" type="text/css">';
      			echo "<h1>Login failed.</h1>";


Which includes our flag.

Takeaway:  don’t forget about .bak files when working with PHP!

The Droid You’re Looking For

Here’s our challenge description:

Sometimes you need a little Star Wars in your life to come across the flag.

if we open up the website (http://chal.tuctf.com:30003/) we see another page displayed with Papyrus font.  It references different types of browsers, which is our first hint at a user-agent related challenge.

There’s nothing too interesting in the HTML, so I looked for /robots.txt.   This is probably the first time I’ve seen a stylized robots.txt page:

This page also talks about Google and “agency”.  And, the challenge is about droids.  So, we need some kind of user-agent change to see this page.  The answer turns out to be Googlebot, which is Google’s webcrawler user-agent value.

That means we’ll need to make a request with a modified user-agent header.

$ curl 'http://chal.tuctf.com:30003/robots.txt' -H 'User-Agent: Googlebot'

This lets us read the contents of robots.txt, as seen by Googlebot.  The result includes googleagentflagfoundhere.html.

If we visit the page, we get our flag:

_Takeaway:  pay attention to user-agent header values!  _

Cute Animals Company

Our challenge description is:

Look at this cute website! Why don’t you find some cute animals that are hidden from view?

Let’s look at the home page… awwwww.

There’s nothing at robots.txt, nothing too interesting in the HTML, and if we try to click “Admin Login”, we get redirected back to index.html.

If we look at the cookies (Dev Tools > Application), there’s an “allowed” cookie that looks suspiciously like a (URL-encoded) base64 value.

If we take ZmFsc2U%3D and swap out the %3D (url encoded) for “=”, we can base64 decode it to “false”.  Let’s base64-encode “true” (dHJ1ZQ==), URL-encode it (dHJ1ZQ%3D%3D), manually set that as the value for the “allowed” cookie, and visit http://chal.tuctf.com:30000/login.php.

Now, we’re able to view the Admin Login page:

Guessing logins and/or trying SQLi didn’t work here, so I ended up looking at the cookies again.  There’s another URL-encoded, base64 value for the “SESSIONID” cookie.  At the time of writing this, it’s MTU3NTE3MjcxNQ%3D%3D.

This decodes to “1575172715”, which is the unix epoch value for the current time (and date).  I tried modifying this in order to get past the login page and never got it.  My teammate told me about a file that they found while running gobuster:  portal.php.  I’m not sure if this was intended to be found only after getting past the login screen or not.

In any case, the page looks like this:

It has a LFI vulnerability that lets you input a filename to get results back.  If we use file:///etc/passwd:

I actually tried to locate the webroot of the project before I realized that the test value of /etc/password included the flag.  Maybe I would have noticed sooner if it weren’t in Papyrus.

_Takeaway:  mmmmm cookies.  _

And Now, For Something Completely Similar

I didn’t actually solve this one during the competition (although not for lack of trying 😭).  The challenge description is:

We made another attempt to login securely. Is this one any better than the last?

When you open up the site, you see another login page:

If you try to do an ' OR 1=1 -- , you get this screen:

There are a number of other words that are blacklisted (select, union, and, admin, etc).  So, we need to figure out how to bypass the login without using any of these.  Swapping out OR for || is easy, but the other ones, not so much.  We’ll have to find other SQL keywords that work.

I tried an embarrassing number of things, including timing attacks, “LIKE” statements, and every SQLi evasion payload I could find.

I also tried an INSERT statement, albeit with improper syntax.  TIL about SQLfiddle, which is a great site to help you test out SQL statements.

The answer was to use an INSERT statement to create a new user, then login with that user.  Come up with a username/password combo (in this example, I used “me” and “me”), then translate those to hex (0x6d65).  Then put those in a stacked query where we close off the first part of the

' || 1;INSERT INTO users VALUES (0x6d65, 0x6d65) --

You supposedly needed to have completed Login To Access to finish this one, but you could probably guess the table name (“users”).

Enter that into the form (I’ve also typed “1” into the password field but I don’t think it’s needed) and hit submit.  You should get a “Login failed” message.

Then, go back to the login screen and login with “me” and “me” (or whatever credentials you used).

And there it is:

Bonus:  RNGeesus

This wasn’t a web challenge but I thought I’d include it anyway.

RNGeesus has a secret technique, can you guess it?

When we connect to the server (nc chal.tuctf.com 30300), we are greeted with this prompt:

$ nc chal.tuctf.com 30300
Good afternoon, my children...

I've overheard the prayers to RNGeesus, but he's got a terrible secret...

He's just using rand() calls on his new Mac!

If you can guess his next number, I'll even give you a flag.

You ready to guess his next move? Here's his current random number.

RNGesus gives the following number: 520932930

We need to figure out the next number in the sequence.  We have a few hints about rand(), and RNGesus is a reference to random number generators in video games.  If we can figure out what seed value is being used, we can probably figure out the whole sequence.

When I started this, I made a script to iterate through a big range of possible seed values, and then break out (and print the seed value to the screen) when the correct value was found.  “Correct” means that the first item generated by rand() with a given seed is equivalent to 520932930.  I also printed up the first 10 values in the sequence.

As it turns out, all of that was completely unnecessary.  The seed is 0, and we only needed the second value in the sequence, after 520932930.

So, my little C helper function (once I had determined the seed value to be 0) looked like this:

#include <stdlib.h>
#include <stdio.h>

int main() {

<span class="Apple-converted-space">  int i;
<span class="Apple-converted-space">  int next = 0;

<span class="Apple-converted-space">  srand(0);

<span class="Apple-converted-space">  for (i = 0; i < 10; i++) {
<span class="Apple-converted-space">    next = rand();
<span class="Apple-converted-space">    printf("Next %d\n", next);
<span class="Apple-converted-space">  }

I compiled and ran the code:

$ gcc rand.c -o rand
$ ./rand

The second value in the sequence is 28925691.  Let’s reconnect to the challenge and input the value:

Takeaway:  don’t seed your random number generator with an easily guessable value, thx.

Thanks again to the TUCTF organizers, I had a great time!  🙂