Null Ahmedabad CTF - Prove Yourself As 1337

Submitted by TiLAK on Sun, 10/06/2019 - 18:33
null Ahmedabad CTF Writeup

The challenge goes like this:

image 0


It’s not always about tools. Script kiddies stay safe. A variable can lead to source code of index.php if set, don’t waste time because parameter discovery is the only way out.

I tried to prove myself as 1337 by passing 1337 as username & password, but it didn’t work. (P.S. I didn’t expect it to work either ;) )

“ A variable can lead to source code of index.php if set, don’t waste time because parameter discovery is the only way out ”

So, let’s have a look at the variables that are passed in the URL

We have username & password in plain text, two hashes & source parameter with value 0

Yeah!! We found that variable. Let’s set it to 1 & see the result.

image 1

Bingo... We now have access to source code of the file.


Let’s understand what it is doing...

  1. <!--?php
  3. if(isset($_GET['source'])){
  4. if($_GET['source'] == 1)
  5. show_source('index.php');
  6. }
  7. require_once('flag.php');
  8. $switch = 1;
  9. $user = '';
  10. $pass = '';
  11. $final_flag = '';
  14. if(isset($_GET['random1']) and isset($_GET['random2'])){
  16. if($_GET['random1'] !== $_GET['random2'] and md5($salt.$_GET['random1']) === md5($salt.$_GET['random2']))
  17. {
  18. $final_flag .= $flag1;
  19. }
  20. }
  22. if(!empty ($_GET['username']) and !empty ($_GET['password'])){
  23. $user = $_GET['username'];
  24. $pass = $_GET['password'];
  25. $concat = $user.$pass;
  27. if($concat == md5($concat))
  28. {
  29. $final_flag .= $flag2;
  30. $switch = 0;
  31. }
  34. $res = parse_str($_SERVER['QUERY_STRING']);
  36. if(!empty($res['username']) and !empty($res['password']) or $switch)
  37. $hashed = md5($user.$pass);
  39. if($hashed === 'ba6e12df1edab45f11f70b547dba9959'){
  40. $final_flag .= $flag3;
  41. }
  43. }
  45. ?-->

Lines 3 to 5, checking the source parameter & if it is set, print the source
Line 7, Include flag.php which contains our secret flag or parts of flag
Line 8 to 10, some initialization of variables
Line 11, our finalflag variable initialization
Lines 14 to 20, Some logic to append First part of our flag to finalflag
Lines 22 to 43, Some logic to append Second & Third part of our flag to finalflag

Now we know our flag consists of 3 parts. Final flag is just string concatenation of all 3 parts.

So, I decided to extract the code for each flag & put it in separate files (flag1.php, flag2.php & flag3.php) and then running them locally to ease attacking. Once attack is successful in local, I can try that in live URL. (Also, by doing this I did favour to CTF Team by saving some bandwidth :D )
Now, Let’s try to get individual parts

Flag 1

image 2

Line 14 validates that both random1 & random2 parameters are present. Satisfying that condition is easy. We just have to always pass those parameters.
Line 16 is tricky. It checks that both parameters are not same yet hash of (some unknown salt + parameters) is same.

image 3

First thing that comes to mind is hash collision. But there is also one input ie. salt (variable) which is not in our control. So even if we pass strings that can generate collision, appending it with salt will mess up things. It would be incredibly difficult to exploit this scenario. (And if I manage to get hash collision here, I deserve a medal :) ) So, there has to be something else as well. 

Next thing that can be attacked is string concatenation. There is known Issue in PHP where concatenating non-string objects with string produces weird results. While concatenating Array with String, Array object just returns string “Array”. I exploited this bug to get desired results. 


Passing array instead of string in random1 & random2 parameters gave me the desired output


image 4

I tried same trick in live URL and …

image 5


We got first part of our flag!!!
1 down. 2 to go...

Flag 2

This is little tricky. 

image 6

To satisfy condition on Line 27, we need to find some string whose md5 hash is same as the original string. This is weird. We know that collision in md5 is possible, but here it is different. I did some google but couldn’t find string that can satisfy this condition. While digging more, I found that in php when strict comparison === is not used, it is possible to find two different strings whose hash when compared with == returns true. (Remember: This is not collision. It is issue in php). The logic behind that is if hash starts with 0e, php will interpret it as scientific notation because of Type Juggling and anything raised to zero is zero. So, comparison of 0e12345 & 0e6789 using == will return true. It will be interpreted as 0 x 1012345 & 0 x 106789 which is obviously 0 & hence equal.

Now we know that we need to find some string starting with 0e whose hash also starts with 0e. So I quickly wrote small php script to find such string.

image 7

After executing for few minutes, I got the required string.

image 8
I used that in my local file & it worked...

image 9
I tried same trick in live URL and …

image 10


We got second part of our flag as well

Flag 3

For third part, we need to set value of $hashed variable to ba6e12df1edab45f11f70b547dba9959 which is generated using md5. I tried to reveres hash, but couldn’t find any string. So I started thinking for other ways.

image 11

On line 34, there one more php function parse_str() is called  within same if block.
PHP documentation for the function highlights the issue that when it is called without result parameter, it will behave similar to register_globals & set a global variable.
That’s what is done here.
So, I added one more parameter in url with name hashed & it worked exactly as expected.

image 12

Tried that in live URL & …

image 13

I got the final flag!!![]=1&random2[]=2&hashed=ba6e12df1edab45f11f70b547dba9959

And that’s how an Excellent Challenge requiring 3 different techniques got completed….

Kudos to CTF organisers for creating such an interesting challenge.