0xfat
These CTF write-ups contain spoilers
Written: 2021/10/10 // Updated: 2021/10/15
0xf.at is a hackit style password-riddle site. The GitHub Project has setup instructions to configure it locally using Docker. The framework also allows a user to create their own levels and add them to the overall project.
The challenges primarily take up a similar format, some challenge notes along with an input box to provide the answer in. At the time of writing all the solitions have been in the page’s source code.
Level 1
Challenge notes
Easy beginnings
The above is pretty straight forward. The if
statement checks whether the value of the el
variable is a certain string and verifies whether it’s the password.
Flag
tooeasy
Level 2
Challenge notes
Not that hard either..
In this scenario it looks like we are given the password as a variable (pw
) and checking the if
statement, we are having to unescape it. A method to do this would be to fire up the browser’s Console window and go through the steps:
Flag
921aa
Level 3
Challenge notes
How about: No sourcecode?
This challenge tried to trick us as if there was no source code. Instead we had to scroll down around 4400 lines to the bottom. The challenge itself is similar as before. We are this time putting together the password from a few different elements:
We first want to figure out the value of the unescape("r%20i%20g%20h%20t%20")
element:
After that there was a bit of a trick by adding two empty strings to the pattern: +""+
adds nothing and can in a sense be ingored.
Flag
r i g h t pw6943ad11f
Level 4
Challenge notes
What does .length mean?
Analyzing the code, we are presented with a variable, var pwinfo = "3523 f1b04eb e2ddc14 f50 fe 1135d3a55400c503ae 1120c 51404eba50a5b45ea3d";
. Looking at if
statement that checks the input validity, the password matches the length of this variable. We can again solve this using the Browser Console.
Flag
var pwinfo = "3523 f1b04eb e2ddc14 f50 fe 1135d3a55400c503ae 1120c 51404eba50a5b45ea3d";
pwinfo.length;
73
Level 5
Challenge notes
Have you heard of ASCII?
We can see that the input value is compared to the following: (atoi("o")+73)
. Further along in the code, we see a function called atoi
which takes the argument a
. The function returns the charCodeAt()
JavaScript method, which in a nutshell gives us the unicode value of a string. Additional information and context can be found on the w3schools.com website.
We can rely on our trusty Console to solve this challenge again, pasting in the atoi()
function and calling it to find the value of the letter o.
Flag
atoi("o") + 73;
184
Level 6
Challenge notes
For this challenge I included a screenshot of the challenge notes as this will be key. The source code has two functions in there. checkPW()
, which will verify our input is correct as well as rgb2hex()
which takes the argument rgb
. This function essentially returns a hex string generated from the RGB colors of our input.
Looking at the checkPW()
function, the if
statement is gathering the word from the page “though”, which on our screenshot is green (rgb(153, 255, 51)
). The check also gathers the color style that the word is using. Our trusty Console helps us out to get the answer.
Flag
rgb2hex(document.getElementById("though").style.color)
"#99ff33"
Level 7
Challenge notes
Jerry fcked up, he forgot the password for this level but he mumbled something about a robots.txt file and something about a hint..
The challenge notes indicate that we should be looking in a file called robots.txt
for a hint to our next level. Checking the file, we find the following information:
The key here is the Disallow
line, whereby it asks crawlers not to index the page which is the solution to our current level. Visiting the page yields us the flag.
Flag
I don't know how you found it but you've found it! The password for level 7 is:
jerryIsDaBoss5c
Level 8
Challenge notes
Nice, someone already entered the password but they made a small mistake. The FIRST occurring 0 (zero) should actually be a small “o”. Can you fix it?
On this challenge, the password is already given to us within the input box, however it’s obfuscated. Selecting to Inspect the page, we can modify the behavior of the input box from type password
to text
:
The instructions tell us to grab the already existing password and convert the first occurring zero (0) to a lowercase “o”.
Flag
635egxjz4v26ghxtzb4ouwh52pbpness
Level 9
Challenge notes
Let’s think this through
This is a pretty straight forward challenge, however as the notes suggest we just need to think it through. The first part is solving the equations. There is a little trick in there, since we have four variables: foo
, bar
, moo
and rar
, however as we can see within the challenge password verifying if
statement, we actually need the value of moo
.
We can step through these easily using the console:
We now know that the value of the moo
variable is 8. The verification step checks whether our input’s lenght is equal to the value of the moo
variable: ` if(el.value.length == moo)`. Since it’s checking the length of the input, we can use any string that matches the length criteria.
Flag
password
Level 10
Challenge notes
Try not to be fooled
This one is more of a tricky one than a complicated challenge. Within the checkPW()
function we are given a variable "+CodeCode+"
and further along in the code, when checking if the input value matches our password, we can see the comparison: if(el.value == ""+CodeCode+"")
.
One might be fooled into thinking we are trying to compare to the value of the "+CodeCode+"
variable, however if we take a look at the behavior, we are actually searching for the variable CodeCode
. The ""
add an empty string and the +
in this case works to concatenate the empty strings with the word CodeCode
. Searching the code, outside of the checkPW()
function we can find this variable.
Flag
var CodeCode = "moo451";
</pre>
Level 11
Challenge notes
The password of this level is calculated by the following function
This one gives us a simple PHP script that checks whether the password provided is equal to date("d.m.Y")
. Since PHP is installed by default on OSX, I was able to fire it up and run that method to get the flag.
Flag
10.10.2021
Level 12
Challenge notes
The password is the sum of all numbers from 1 to 477
For this challenge we are asked to calculate the sum of all numbers between 1 and 477. A friend of mine and I came up with a solution each when solving this challenge.
Solution 1
I went down the route of a loop to calculate the sum, whereby i
is our sum and x
is our iterator, which tracks where in the loop we are as well as adding the value of that number to i
:
To get the flag, we just need to print the value of i
.
Solution 2
On a similar note, my friend used Python along with the mathematical formula for the sum of the first n natural numbers:
Flag
114003
Level 13
Challenge notes
The password of this level is calculated by the following function
This is another challenge where it’s more about being able to read the code and think logically. The PHP function we are given essentially checks that the length of the $username
is equal to the variable $password
. From this, we know that the function will take a username, calculate its length and check if the entered password is equal to that length.
Flag
Username: admin
Password: 5
Level 14
Challenge notes
The following function defines the login process
For this challenge we are given another snippet of PHP. We can see from the $users
variable, that the information is gathered from a file found at /data/login_info.json
. The implode() function is a bit of a red-herring here as it’s not exactly joining array elements beyond printing out the file.
From the rest of the code, we can see that it’s looking for the account_status
to be active
. When we look at the file, only one user is active and the user data holds the necessary pieces to the flag.
Flag
{
"id": 4,
"guid": "bc3c1364-4b24-4f60-8fe4-7628e72391ed",
"password": "Vencom",
"age": 40,
"account_status": "active",
"name": "Paige Youmans",
"gender": "female",
"phone": "857-579-3847",
"email": "[email protected]",
"address": "14622, Flint, Harrison Street",
"registered": "2007-07-15T09:55:40 -02:00"
},
GUID: bc3c1364-4b24-4f60-8fe4-7628e72391ed
Password: Vencom
Level 15
Challenge notes
You have to decode the following encrypted password. We don’t know how to decrypt it but you can play around with the algorithm that was used to encode it. Maybe you’ll figure it out
On this challenge we are given a string, which is meant to be encoded - npveei
. We are then provided with two input boxes, one to provide arbitary data to encrypt using the same method npveei
was generated with and the other to submit our password.
Testing the encryption with abcdef
we can start to observe a behavior. The encrypted version of the string is acegik
. From this it looks like each character is shifted by its position:
We can use this knowledge to reverse engineer our encrypted string. While this can be done programmatically, I found it quicker to solve it by pulling up the English alphabet in a numbered chart and calculated it.
Flag
notbad
Level 16
Challenge notes
The password of this level is calculated by the following function
This was an overall very straight forward challenge. The password we enter into the input box gets Base64 encoded (base64_encode($password)
)and is matched against a fixed value (ODNhMjNmYjU4MmUxMDU5ODhkMjI2YmVjMw==
). To complete this challenge, we can decode this value.
Flag
83a23fb582e105988d226bec3
Level 17
Challenge notes
Now let’s play: regEx Find out what password will make the preg_match function return 1
The key here is to understand what the regex is doing. Having used regex in the past, it appears to primarily be a validation for an email address. I tested the following string to complete the level:
Flag
Level 18
Challenge notes
The following password is encoded in morse code. Each character is seperated by a blank. After 60 seconds this page will submit/refresh automatically. So.. be faster than that.
On this challenge we are presented with a string, which appears to be morse code. I tried a variety of morsecode converters, however for whatever reason they did not work on this string. Much like Level 15, I opted to manually convert the values using this International Morse Code sheet.
I was able to manually convert the values within the provided 60 seconds. For the above example the flag was:
Flag
..... => 5
-.. => D
..--- => 2
-.. => D
---.. => 8
....- => 4
-.. => D
----. => 9
Flag: 5D2D84D9
Level 19
Challenge notes
This was a bit more challenging. We are given a keypad and instructions to convert the characters on the keypad to numbers. We are only given 15 seconds to solve this and the string given is 32 characters long, therefore we have to look at this programmatically. I used Python to build out a dictionary where I calculated the sum of each character. The script than takes the string we are given to calculate the sum of and creates a list of its characters.
We can then iteratre through the list of characters and match them against the dictionary keys to get the sum (value) of it. If the character is not in our dictionary, we can assume it’s a number and we can use the number to add to the sum.
Running the above script and supplying the string provided by the challenge gets us the flag.
Flag
python level20.py 53bc79918220509808a10046f70c67e4
158
Level 20
Challege notes
The password of this level is an MD5 encrypted string which was calculated by combining two random words (without spaces) from »this wordlist« (144kb). Can you find out which two words were used?
At this stage the challenges are becoming more and more reliant on programming. This challenge has us download a word list that contains 68847 words. Two random words are taken and their MD5 hash is generated. To solve the challenge, we are asked to find the two random words that were encrypted.
I decided to use Python to figure this out. On an initial research, I found an article. We can use the hashlib
to encrypt our combined words to get their MD5 hash. We can then run through the list combining the words from our wordlist, calculate their MD5 hash and compare if this matches the one we are provided.
I am personally not familiar with threading, therefore my script is relatively slow in that respect and this could be considerably sped up with multi-threading, however that wasn’t my aim. Another option is GoLang is meant to have decent multi-threading capabilities.
I essentially came up with two ideas and it was the second idea that worked but either is an option. One thing to consider, I use the 0xf.at
Docker container and each time the container is restarted, a new hash is generated.
Solution one
The first solution is to create an application that will run through the word list, combining the two wods and compare the generated hash to the one 0xf.at
provided. The script can then alert us to which two words successfully matched the hash.
Solution two
The second idea is to create a Rainbow Table, which is essentially a pre-compiled table of cryptographic hashes and their plaintext value that can be searched. The idea still works the same, create a table with the combined words and their MD5 hash and once done, search through it for the given hash.
I ran into some limitations with this one. I built the script out on my RaspberryPi and left it running overnight in a screen
. By the morning my rainbow table was sitting at 45GB with 939,645,348 lines generated. The first record being aaaa: 74b87337454200d4d33f80c4663dc5e5
and the table only getting to bucktoothbanquets: 93e073c8fb9fd8439fe9bcc28d4b6b41
.
This script essentially saves the result of the combined words and their hash to a file.
I ended up rebooting my Docker
container running 0xf.at
and searching through the existing table, I was able to find the result.
Flag
grep -m 1 08b2e0dfcae2a7597fc59b11428179a9 rainbowtable.txt
beckoninghandiworks: 08b2e0dfcae2a7597fc59b11428179a9
Level 21
Challenge notes
The text below are semicolon seperated and scrambled words from »THIS DICTIONARY« (134 KB). Can you unscramble them in 30 Seconds or less?
I approached Python again to automate out this task. We are again given a wordlist to download and instructions to solve the channel. In this case we are given a string of jumbled words separated by semicolons. When unscrambled, these words match words within the provided word list.
I went through a lot of variations of this script. I first tried to generate each possible combination of a word and compare it to existing words in the list, however this was taking forever.
I then tried to get the length of the longest and shortest words in the provided string and filter out words that did not meet the criteria when reading in the word list, however in the end this removed roughly 3000 words of again 68847 words.
I tried permutating functions, libraries and such but again was taking forever. The winning idea was to read in the provided string, split it at the semicolons and break down each word into characters. Using set()
I could ensure we filtered it down to unique characters and I could then use sort()
to ensure they were in order. This method was applied to both the word from the provided string and the one from the word list. If the letters matched, the length of the word would be considered to ensure it matched the length of the word from 0xf.at
.
Unfortunately this can mean that multiple words are matched that contain the same number of the same characters so it’s currently a few tries before I get a successful string. I’ll update this article if I figure out a better way to solve this challenge.
Flag
python unscramble.py "arccaresi;nliosgh;nllicyoca;esfbiauite;dsroweb;gljoeg;aategidvnl;rntmdaoen;nasnmtaieenis;nactselua"
cercarias;longish;conically;beautifies;browsed;joggle;galivanted;adornment;inanimateness;canulates
Level 22
Challenge notes
You have 10 Seconds to mirror the text below after the last character
Example: Text: abcdefg1234567890 Solution: abcdefg1234567890987654321gfedcba
For this challenge we are provided with a random string that we are asked to reverse and supply the combined value of the two as the password. What I found based on the example is the last character of the string is skipped, ie in the above example instead of ...78900987...
we have ...7890987...
.
This just required an additional step to remove the last character from the string before reversing it:
Flag
python level22.py e86e6429f0ea5c3b91bf3b683202bedf
e86e6429f0ea5c3b91bf3b683202bedfdeb202386b3fb19b3c5ae0f9246e68e