Exploitation
When Parameterized Queries Won’t Help
The usual recommendation for vulnerabilities that enable us to manipulate database queries (SQL injection) is to use a method such as prepared statements (parameterized queries) to query the database. Correct use of prepared statements prevents SQL injection attacks by ensuring that user input is properly handled by the database management system.
This post is about an edge case where a ‘safe’ database query was manipulated to attack an application.
Input Filtering
I’d been tasked with performing a very high level black box security assessment against a large list of web applications, from the perspective of an unauthenticated external attacker. None of the application index pages exposed functionality beyond login pages and the occasional password reset function, so given the tight time frame I kicked off various scanners to check for low hanging fruit, hidden files or directories, or anything that might reveal useful information.
In scope were several Microsoft IIS servers so I used the excellent IIS-Shortname-Scanner by Soroush Dalili (@irsdl). Combined with a bit of guesswork I was able to locate and download a .NET DLL file from a URL of the form ‘/obj/Release/ApplicationName.dll’.
Decompiling this DLL file revealed some of the server-side code for one of the applications. Reviewing the code, I spotted something strange in the handler for the password reset submit button:
1 2 3 4 5 6 7 8 9 10 11 |
if(this.txtUsername.Text == "" || this.txtUsername.Text == "%") { this.txtResponse.Text = Errors.InvalidUsername; return; } if(this.txtEmail.Text == "" || this.txtEmail.Text == "%") { this.txtResponse.Text = Errors.InvalidEmailAddress; return; } string username = this.txtUsername.Text.Replace("%", ""); string email = this.txtEmail.Text.Replace("%", ""); User u = UserManager.find(username, email); |
If the supplied username was an empty string or a single percentage symbol then it was rejected, likewise with the supplied email address. Immediately after rejecting inputs consisting of a single percentage symbol, the application removed any percentage symbols from the inputs and continued processing the password reset request. At the least this introduced an inconsistency where an input consisting of multiple percentage symbols would lead to an empty string passing into the password reset function, despite the earlier attempt to reject empty strings.
Why the percentage symbol? Unfortunately the UserManager class was in another DLL file that I didn’t have access to so I was unable to review that code for an answer.
It only takes a little SQL knowledge to know that the percentage symbol can be used as a wildcard in a LIKE clause to match zero or more characters. If we consider this then the strange code above looks like it could be the result of two different issues regarding this wildcard being reported and fixed separately, potentially by two different developers who didn’t review the surrounding code!
The underscore character is also an SQL LIKE wildcard that matches exactly one character. If the input is used with the LIKE operator then strings of underscores can be used to identify the lengths of known username and email address values (and combinations of lengths). I threw a quick test together using Burp Suite’s Intruder in cluster bomb mode to test whether a LIKE operator was in use.
Bingo! If a valid pair of inputs was supplied to the application then it responded asking for an answer to a security question, otherwise it responded with an error stating that the details were not recognised. Using this I was able to create a list of all valid combinations of username length and email address length.
User Enumeration
A complete list of users could now be discovered one character at a time by iterating over printable characters in each position of each pair of input masks and submitting them to the application to check for valid or invalid results. I threw together a Python script to do this, the core of which can be seen below:
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 |
#Use a LIKE wildcard mask to brute force valid usernames def bruteUsers(wildcardMask): #Create a queue of masks to brute force, starting with the given mask maskQueue = [wildcardMask] #Loop until there are no more masks to process while len(maskQueue) > 0: #Pop the last mask off of the queue for processing mask = maskQueue.pop() #Iterate over the available character set for usernames for char in list("abcdefghijklmnopqrstuvwxyz0123456789-/\\."): #Replace the first underscore in the mask with the current character to generate a new mask to test curMask = mask.replace("_", char, 1) #Test whether the mask gives a valid response if isMaskValid(curMask): #Valid mask, print it out print "[+] " + curMask #If the mask contains more wildcards then add it back into the queue if "_" in curMask: maskQueue.append(curMask) else: print "[+] Username found: " + curMask |
This method was combined with multi-threading to speed things up and the result looked something like this:
Passwords etc!
Using this script I extracted a list of over 700 user names from the application database. Based on the password policy that was helpfully detailed on the login form I launched another Burp Suite Intruder attack using this user list and the trusty “Password1” and gained access to several user accounts.
By this point I was nearing the end of the assessment time frame and none of the user accounts I had access to were administrative users. One user account allowed me to create new user accounts but did not allow me to assign the super user role to them. Fortunately the same user account did enable me to create new user roles, assign all permissions to the new role, then assign that role to a new user.
At 16:50 on Friday, the final day of the security assessment, I got to do my root dance as I logged in as a new system administrator to gather evidence for my report!
I’m not sure how common this kind of issue is, but I’m considering writing a Burp Suite Extension to implement this attack. Let me know in the comments or on Twitter if you think this would be useful!
Discussion