TAMU CTF 2019

TamuCTF 2019 had some pretty good challenges which actually forced you to learn things. It was a 9 days long CTF, and I personally felt it somewhat boring too as all the challenges were disclosed in the beginning. I also at some point found it fun to solve some challenges from SeasidesCTF 2019 and I left Tamu for 2-3 days.

This Post includes the writeup to the following Challenges.

Web

Science 1
Buckets
Login App
1337 Secur1ty

Secure Coding

SQL
PWN
Science 2
Login App 2

Reversing

KeyGenMe
NoCCBytes
Cr4ckZ33C0d3

Ok lets get our hands dirty !! in the Binary World. XD



Web

Science1

After navigating to the web address, I observe the hint (Flask as a Service).
The first thing to check with sites made with Flask is SSTI(Server Side Template Injection).
You can read more about it here.

Here I got to know that the inputs are reflecting in the output.

Once XSS also came to mind and it was also vulnerable to it but as mentioned, lets exploit it using Flask.
I quickly intercepted the POST Request and tried

{{config}}

in chem1 input field. And to my surprise it was vulnerable.

Then I tried some payloads from this site

{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}

It shows us the /etc/passwd dir.

Now its a clear path if you know the right directory where the flag is stored.

After some brute-forcing I found the directory as /opt/tamuctf/.


Buckets

This challenge was very easy who knew something about how aws works.
If you have solved flaws.cloud or read some of it’s walkthroughs, then this challenge is a cakewalk.

So just tinkering around a bit…

We can’t leave the source alone .. haha

So both the Source and original page gives us a hint for DOGS

We have to simply modify the url given to us for the challenge to get a tree of keys.

And if we follow our hint, flag can’t be missed.


LoginApp

FYI : This was hell of a challenge for me.
So, we head over to the given link, and it is a simple login page.

After going through the source, I also found a Register-Form which was hidden.

We can modify the display statement to the following using Dev Tools in our browser :

display: block;

We see a beatiful Create and Login form side by side.

Then I used Burpsuite to intercept the CREATE request and saw that it was pointing to nothing. And also there was no source code available for the Register form submit button to POST some JSON data as in the case of Login Button. And then I closed this challenge for like 2 days. Fortunately, when I began to solve it with a fresh mind I was able to succeed though.

Now I began trying some SQL Payloads at first and then I was lucky that I tried NoSQL at last though.

I tried some payloads from here on the login page.

The following worked for me :

{"username": {"$ne": "foo"}, "password": {"$ne": "bar"} }

And I was just mad now to know that it really worked and we were logged in as a user named bob.

But out motive is to find the flag.. ryt.
So I simply changed the Username Value to the following:

"username": {"$ne": "bob"},

and BoomYa!!


1337Secur1ty

Spoiler: This challenge was pretty ez pz if you used sqlmap like me. And also if you have found the right path. XD

The link takes us to a Company’s (1337 Secur1ty) website where we are presented with a login page and a registration at first.. After registering with some random id, I got in and began tinkering with how did the site work, what features does it provide, etc.

There was an Employees Section which displayed the registered users.

Also the most interesting was the Message Section.

I tried simple XSS first.

But with no luck, it was not vulnerable, as the challenge difficulty level specified hard. ;D

But at the same time i observed that the id can be changed in the URL and this led me to think that SQLi might be the right thing to try here.

Also when I checked the cookies, there was a cookie named as secret.
And obviously the userid. Till now we know that the admin has the userid = 1 by referring to the Employees Section.

I fired up my favorite tool ie. SqlMap and used :

sqlmap -u http://web6.tamuctf.com/message?id=6  --dbs

To guess the back-end DBMS and database names.

Afterwards we can check for columns.

sqlmap -u http://web6.tamuctf.com/message?id=6  --dump

And at last we can dump the value of secret along with its UserID.

sqlmap -u http://web6.tamuctf.com/message?id=6  -D 1337_Secur1ty -T Users -C UserID,Secret --dump

Now, we have got everything.
We just need to change our Secret and UserID cookie in BurpSuite Repeater.

Aww our flag was waiting for us …

Finally solved !!

BTW we can also use EditThisCookie Extension in our browser to change cookie and or We can forward the request as well with request to see the Rendered Page.



Secure Coding

Universal Instruction

To solve this challenge you must first fork the challenge and then modify the files in this repository and attempt to fix the vulnerability that you found.
Everytime you make a commit your files are tested on the backend system. The results can be found under CI/CD->Jobs and then the last test ran.
If you pass all of the tests the flag will be printed at the bottom of the CI/CD display. Otherwise you will either get an error or statement saying what happened.

SQL

Now that you have broken the SQL Injection challenge it’s your turn to fix it!

Unsecure Code(login.php)

Replace :

    $user = $_POST['username'];
    $pass = $_POST['password'];

With :

    $user = filter_var($_POST['username'], FILTER_SANITIZE_STRING);
    $pass = filter_var($_POST['password'], FILTER_SANITIZE_STRING);

Reference


PWN

Now that you have broken a PWN challenge it’s your turn to fix it!

Unsecure Code(PWN.c)

Replace :

    gets(buf);

With :

    fgets(buf,128,stdin);

Reference


Science2

Now that you have broken the Jinja2 Template Injection challenge it’s your turn to fix it!

Unsecure Code(views.py)

Replace :

        chem1 = request.form['chem1']
        chem2 = request.form['chem2']

With:

        chem1 = request.form['chem1'].replace("{{","\'{{\'").replace("}}","\'}}\'")
        chem2 = request.form['chem2'].replace("{{","\'{{\'").replace("}}","\'}}\'")
	

Reference


LoginApp2

Now that you have broken the NoSQL Injection challenge it’s your turn to fix it!

Unsecure Code(server.js)

Replace :

        var query = {
            username: sanitize(req.body.username),
            password: sanitize(req.body.password)
        }

        c.collection('users').findOne(query, function (err, user) {
            if(user == null) {
                res.send(JSON.stringify("Login Failed"))
            }
            else {
                resp = "Welcome: " + user['username'] + "!";
                res.send(JSON.stringify(resp));
            }
        });

With :

        var username = req.body.username;
        var password = req.body.password;
        
        c.collection('users').findOne({'username': { $in: [username] },'password': { $in: [password] }}, function (err, user) {
            if(err){
                res.send(JSON.stringify("Login Failed"));
            }else if(user){
                resp = "Welcome: " + user['username'] + "!";
                res.send(JSON.stringify(resp));
            }else {
                res.send(JSON.stringify("Login Failed"));
            }
        }); 

Reference



Reversing

KeyGenMe

Download File

About This Challenge I’d only tell that I learnt the most from it tho.

Going through basic assembly of the main function I found that it was calling another function named as verify_key.

And later focusing on verify_key, I observe that it was calling enc() and then comparing our input string to some weird encrypted text.

Pseudocode (enc())

_BYTE *__fastcall enc(const char *a1)
{
  unsigned __int8 v2; 
  int i; 
  int v4; 
  _BYTE *v5; 
  v5 = malloc(0x40uLL);
  v4 = strlen(a1);
  v2 = 72;
  for ( i = 0; i < v4; ++i )
  {
    v5[i] = ((a1[i] + 12) * v2 + 17) % 70 + 48;
    v2 = v5[i];
  }
  return v5;
}

So Now we have the encrypted version of our key. So we can simply brute-force for chars in the range (33, 127). Refer to asciitable.

v2 = 72
s = '' 
for encC in "[OIonU2_<__nK<Ks":
	for orgC in map(chr, xrange(33, 127)):
		if ord(encC) == (((ord(orgC) + 12) * v2 + 17) % 70 + 48):
			s += orgC
			v2 = ord(encC)
			break
print s

We get

$*Z2S"+')""+'+$(

And we r finally done here.


noCCBytes

Download File

This challenge required us to set the right breakpoint but I wonder why it had somewhat less solves though.

So We have to bypass the password.

So I fire up GDB and load the executable.
And the main function was calling a function named passCheck()

You can see it’s exact name using info function.

After disassembling passCheck() I observed that it had a strcmp instruction.

Now moving on, I simply set a breakpoint on the strcmp instruction we found.

b *passCheck(char*)+120

And after runnning the executable again, We can see our password residing in memory.

Now we can head over to input our password using the netcat service and get the flag.


Cr4ckZ33C0d3

Download File

This challenge was the hardest in the reversing category acc. to me.

At First I took the help of IDA Pro to get some Pseudocode.
I found that the main function is calling a verify_key function which later calls various check() functions and and checks if we have supplied the correct input by comparing it to some basic calculations.
I’ve basically broke down various check levels for you to take a look.

bool chk1 = a1[5] == 45 && a1[11] == 45 && a1[17] == 45 && a1[23] == 45;

bool chk2 = (unsigned int)(a1[1] - 48) <= 9
      && (unsigned int)(a1[4] - 48) <= 9
      && (unsigned int)(a1[6] - 48) <= 9
      && (unsigned int)(a1[9] - 48) <= 9
      && (unsigned int)(a1[15] - 48) <= 9
      && (unsigned int)(a1[18] - 48) <= 9
      && (unsigned int)(a1[22] - 48) <= 9
      && (unsigned int)(a1[27] - 48) <= 9
      && (unsigned int)(a1[28] - 48) <= 9;

bool chk3 = a1[4] - 48 == 2 * (a1[1] - 48) + 1 && a1[4] - 48 > 7 && a1[9] == a1[4] - (a1[1] - 48) + 2;

bool chk4 = (a1[27] + a1[28]) % 13 == 8;

bool chk5 = (a1[27] + a1[22]) % 22 == 18;

bool chk6 = (a1[18] + a1[22]) % 11 == 5;

bool chk7 = (a1[22] + a1[28] + a1[18]) % 26 == 4;

bool chk8 = (a1[1] + a1[4] * a1[6]) % 41 == 5;

unsigned int v1 = (unsigned int)((a1[15] - a1[28]) >> 31) >> 30;
bool chk9 = ((v1 + a1[15] - a1[28]) & 3) - v1 == 1;

unsigned int v2 = (unsigned int)((a1[22] + a1[4]) >> 31) >> 30;
bool chk10 = ((v2 + a1[22] + a1[4]) & 3) - v2 == 3;

bool chk11 = a1[20] == 66 && a1[21] == 66;

bool chk12 = (a1[6] + a1[15] * a1[9]) % 10 == 1;

unsigned int v3 = (unsigned int)((a1[15] + a1[4] + a1[27] - 18) >> 31) >> 28;
bool chk13 = ((v3 + a1[15] + a1[4] + a1[27] - 18) & 0xF) - v3 == 8;

bool v4 = a1[28] < a1[9];
bool chk14 = ((v4 + a1[28] - a1[9]) & 1) - v4 == 1;

bool chk15 = a1[0] == 77;

After realizing that this could be brute-forced, I pulled out Sublime to code some mellow python.
And also, some characters don’t need to be guessed as there were some if statements which were just comparing the ascii value straightaway.

From the pseudocode we can observe that the chk2 checks whether the specified value is particularly an integer value or not.
So I fixed the range from (48,58) in decimal .. You can check the ascii table here

key = [120] * 29

key[5] = 45
key[11] = 45 
key[17] = 45
key[23] = 45
key[0] = 77
key[20] = 66
key[21] = 66

charRange = map(int, xrange(48, 58))

for a in charRange:
    for b in charRange:
        for c in charRange:
            for d in charRange:
                if (b - 48 == 2 * (a - 48) + 1 and b - 48 > 7 and c == b - (a - 48) + 2):
                    key[1] = a  
                    key[4] = b
                    key[9] = c
                if (c + d) % 13 == 8 and (c + b) % 22 == 18 and (a + b) % 11 == 5 and (b + d + a) % 26 == 4:
                    key[18] = a
                    key[22] = b
                    key[27] = c
                    key[28] = d
                v3 = ((a + key[4] + key[27] - 18) >> 31) >> 28;
                if ((v3 + a + key[4] + key[27] - 18) & 0xF) - v3 == 8:
                    key[15] = a
                if (key[1] + key[4] * a) % 41 == 5:
                    key[6] = a

print ''.join(chr(i) for i in key)

If you ran, this script you may have got :

M4xx9-8xx7x-xxx9x-6xBB2-xxx88

So There are some values which are not really checked, and I’ve assummed them to be x.

Now we can head over to netcat to get our flag… And Finally ..

Seriously this challenge required good logic skills. GIGS

At last there were many gigems …
And I loved ‘em all

Thanks for Reading this writeup..
Subscribe to my Newsletter for more updates regarding CTFs.
Also Feedback is always appreciated.
It won’t take much time though.

And Keep Escalating the Privileges Happy Hacking !!