In this post, we’re going to be looking at Level 16 of the Natas wargame, hosted by Over The Wire.
What’s Going On?
Upon logging in, we are presented with a text field, submit button and the usual link to the PHP source code of the level. Additionally, we’re given a bit of text that says this level now filters even more characters. Seeing this screen, we should be reminded of Level 10 which took a value from us, and used grep to find it in a dictionary.txt file. However, to verify, it’s look at the PHP source code.
Once we look at the code, we can see this is basically the same level except for a few small differences. The main difference is that now more characters are filtered from the user input. This includes single quotes, double quotes, back ticks, ampersand, pipe and semi-colon. This is all in an attempt to prevent shell-injection, as our user supplied value is passed to the grep command. However, this filtering doesn’t filter all necessary shell characters! Specifically, shell allows for commands to be executed in the following fashion:
$(command) will execute command
So, let’s look at how we can use the above execution format to beat this level.
Since we cannot put double quotes in our input, we can’t simply close the search quotes and specify another file to list as well, like we did in level 10. Instead, we must supply some value within the grep search quotes, and get some result. So, let’s think. If we could get one character at a time from the password file and grep it against dictionary.txt, we could discern, based on the grep results, what character was searched for. If we could do this for every character of the password, we could discern the password. So, how can we do this? Well, luckily linux provides us with the cut command. We can use cut to select a specific character position from a string as follows:
cut -c1-1 < /etc/natas_webpass/natas17
The above command would return the 1st character from the natas17 password file. Now, let’s remember we need to get that above command executed, not simply used as a search string itself, so we must wrap it in $() like:
$(cut -c1-1 < /etc/natas_webpass/natas17)
Submitting the above as our query returns a blank page. This is possibly good or possibly bad. Remember, passwords sometimes have numbers and the dictionary file doesn’t have any numbers. So to make sure it’s working correctly, let’s submit the same command but for the 2nd character:
$(cut -c2-2 < /etc/natas_webpass/natas17)
Now we get a list result. This tells us two things. First, it tells us our submission is working as planned. Second it tells us a list of words all sharing one same character. In this instance it’s the letter “h”.
Great! Now we can repeat this for every character position and get an idea of the password. We will know where numbers reside, and we will figure out the letter of every other character! However, we aren’t done yet. We still need to figure out which number is in which numeric positions, as well as if characters are upper or lower case (since grep uses the -i flag for case-insensitive search).
Now that we have a good idea on most of the password, let’s take a finer approach to nailing down the specifics (numbers and capitalization).
The linux shell provides many tools, including conditional logic through if/then/else statements. If we can make use of this, we could compare the characters of the password to a specific value, and if they are equal return one value, if they’re not, return a different value. We can use this logic to verify which characters are uppercase or lowercase as well as check what numbers are in the numeric positions of the password. First, let’s look at the if/then/else structure:
$(if [ $(cut -c32-32 < /etc/natas_webpass/natas17) = x ]
The above compares the 32nd character to lowercase x, and if it’s equal returns x, otherwise returns 0. This is a good submission to this level as we will see a list of words containing x if the 32nd character is equal to little x, otherwise we will get no list (since there are no numbers in dictionary.txt). One thing to note about the statement above is that when placed on new lines, there is no delimiter needed between the if condition, then, and else (If placed on the same line however, the required delimiter would be the semi-colon, which is filtered in this level). But, how can we place new lines in a single line text field? Well, we can’t. Though, what we can do is specify the new line character through URL encoding. Just like how the space character is encoded as %20, the new line character has ASCII hex value “0a” and can be encoded in the URL as %0a. So, let’s look at the URL request for the above query:
The above returns the list of words with x in them. Thus, we can discern that the 32nd character of the password is in fact lowercase x. To verify that this is case sensitive, change the lowercase x to uppercase and see that nothing is returned by grep. Now, we simply need to test all characters in this same fashion for case, as well as the numeric spots for what number they contain. Done by hand, this is easily finished within 15 minutes, or a script could be written using the above query only to brute force the password without even requiring step 1.
Input sanitization or filtering works great, when it checks for all possible attacks. However, when even the smallest vector is left open, it will be found and it will be exploited. This level goes to show that injection isn’t limited to SQL, or databases, or even the shell which was used here. Injection is the result of control channels being mixed with data channels. When user data can contain control characters and they are allowed to do their controlling work, there will always be problems!