Quick Guide To SQL Injection Attacks and Defenses - English
Quick Guide To SQL Injection Attacks and Defenses - English
By r3dm0v3
Table of contents
1-Description
2-Vulnerable Code
3-Exploit
4-How to Prevent
Least Privilege
PHP Configuration
5-Fixed Code
6-References
1-Description
A SQL injection attack consists of insertion or "injection" of a SQL query via the input data from the
client to the application. A successful SQL injection exploit can read sensitive data from the
database, modify database data (Insert/Update/Delete), execute administration operations on the
database (such as shutdown the DBMS), recover the content of a given file present on the DBMS file
system and in some cases issue commands to the operating system. SQL injection attacks are a type
of injection attack, in which SQL commands are injected into data-plane input in order to effect the
execution of predefined SQL commands.
SQL injection is a code injection technique that exploits a security vulnerability occurring in the
database layer of an application.
The vulnerability is present when user input is either incorrectly filtered for string literal escape
characters embedded in SQL statements or user input is not strongly typed and thereby
unexpectedly executed. It is an instance of a more general class of vulnerabilities that can occur
whenever one programming or scripting language is embedded inside another.
keyword 'or'.
/wasc.asp, line 69
Every code that uses user inputs to generate SQL queries without sanitization is vulnerable to sql
injections.
PHP)
<?
$q=mysql_query($query);
?>
asp)
v_cat = request("category")
set rs=conn.execute(sqlstr)
asp)
Dim SQL As String = "SELECT Count(*) FROM Users WHERE UserName = '" & _
username.text & "' AND Password = '" & password.text & "'"
Dim thisCommand As SQLCommand = New SQLCommand(SQL, Connection)
Asp)
dim conn, cmd, recordset
'Create Connection
Set conn=server.createObject("ADODB.Connection")
conn.open "DNS=LOCAL"
'Create Command
With cmd
.activeconnection=conn
End With
'....
'....
'Do clean up
recordset.Close
conn.Close
aspx)
protected void Button1_Click(object sender, EventArgs e)
{
int result = 0;
conn.Open();
result = (int)cmd.ExecuteScalar();
if (result > 0)
Response.Redirect("LoggedIn.aspx");
else
aspx)
string connect = "MyConnString";
conn.Open();
//Process results
PHP)
<?
$q=mysql_query($query);
?>
Java)
String DRIVER = "com.ora.jdbc.Driver";
Class.forName(DRIVER);
//Make connection to DB
String sel = "SELECT User_id, Username FROM USERS WHERE Username = '"
+Username + "' AND Password = '" + Password + "'";
if (resultSet.next()) {
iUserID = resultSet.getInt(1);
sLoggedUser = resultSet.getString(2);
if (iUserID >= 0) {
} else {
Java)
String query = "SELECT account_balance FROM user_data WHERE user_name = "
+ request.getParameter("customerName");
try {
SQL Injection is very common with PHP and ASP applications due to the prevalence of older
functional interfaces. Due to the nature of programmatic interfaces available, Java EE and ASP.NET
applications are less likely to have easily exploited SQL injections.
The severity of SQL Injection attacks is limited by the attacker’s skill and imagination, and to a lesser
extent, defense in depth countermeasures, such as low privilege connections to the database server
and so on. In general, consider SQL Injection a high impact severity.
Note: This article is not going to explain full exploitation of SQL injection bugs because exploiting sql
injection bugs is very various and this article is based on detecting and fixing sql injection bugs. There
is some examples bellow to understand sql injection attacks.
The attacker attempts to elicit exception conditions and anomalous behavior from the Web
application by manipulating the identified inputs - using special characters, white space, SQL
keywords, oversized requests, and so forth. Any unexpected reaction from the Web application is
noted and investigated. This may take the form of scripting error messages (possibly with snippets of
code), server errors (HTTP 500), or half-loaded pages.
Attackers often try following inputs to determine if web application has sql injection bug or not.
'
or 1=1
or 1=1--
" or 1=1--
or 1=1--
' or 'a'='a
" or "a"="a
') or ('a'='a
<%
cn.connectiontimeout = 20;
var username;
var password;
username = Request.form("username");
password = Request.form("password");
var sql = "select * from users where username = '" + username + "' and
password = '" + password + "'";
rso.open( sql, cn );
if (rso.EOF)
rso.close();
%>
Access Dennied!
%>
Response.end
return;
else
Session("username") = rso("username");
%>
Welcome
<% Response.write(rso("Username"));
Response.end
%>
The critical point here is the part of code which creates the 'query string' :
var sql = "select * from users where username = '" + username + "' and
password = '" + password + "'";
Username: John
Password: 1234
Username: John
select * from users where username = 'John' and password = 'i_dont_know' or 'x'='x'
So 'where clause' is true for every row of table and user can login without knowing password!
Password:
..The 'users' table will be deleted, denying access to the application for all users. The '--' character
sequence is the 'single line comment' sequence in Transact-SQL, and the ';' character denotes the
end of one query and the beginning of another. The '--' at the end of the username field is required
in order for this particular query to terminate without error.
The attacker could log on as any user, given that they know the users name, using the following
input:
Username: admin'--
The attacker could log in as the first user in the 'users' table, with the following input:
…and, strangely, the attacker can log in as an entirely fictional user with the following input:
The reason this works is that the application believes that the 'constant' row that the attacker
specified was part of the recordset retrieved from the database.
Error Based Injections (SQL Server)
This kind of attack is based on 'error message' received from server. Fortunately for the attacker, if
error messages are returned from the application (the default ASP behavior) the attacker can
determine the entire structure of the database, and read any value that can be read by the account
the ASP application is using to connect to the SQL Server.
For example attacker can find sql server version by injecting following expression:
Which produce the following error that shows sql server version:
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value
'Microsoft SQL Server 2000 - 8.00.194 (Intel X86) Aug 6 2000 00:57:48 Copyright (c) 1988-2000
Microsoft Corporation Enterprise Edition on Windows NT 5.0 (Build 2195: Service Pack 2) ' to a
column of data type int.
/process_login.asp, line 11
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'users.id' is invalid in the select list because
it is not contained in an aggregate function and there is no GROUP BY clause.
/login.asp, line 11
So the attacker now knows the table name and column name of the first column in the query. They
can continue through the columns by introducing each field into a 'group by' clause, as follows:
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'users.username' is invalid in the select list
because it is not contained in either an aggregate function or the GROUP BY clause.
/process_login.asp, line 11
Notice that each SELECT statement within the UNION must have the same number of columns. The
columns must also have similar data types. Also, the columns in each SELECT statement must be in
the same order. Attacker can find columns count by trial and testing methods. (Using union or order
by)
Note: The UNION operator selects only distinct values by default. To allow duplicate values, use
UNION ALL
For example attacker can find sql server version by injecting following expression:
Which returns sql server version in username column and the login page code displays it. If the code
does not return any column of the result of query in the page, attacker cannot find information in
this way and should use blind injection methods.
Following example show how to create a table named foo which has a single column line by injecting
stacked query:
xp_cmdshell is a built-in extended stored procedure that allows the execution of arbitrary command
lines. For example:
* xp_regenumvalues
* xp_regread
* xp_regwrite
(in)visible content
Having a simple page, which displays article with given ID as the parameter, the attacker may
perform a couple of simple tests if a page is vulnerable to SQL Injection attack.
Example URL:
http://newspaper.com/items.php?id=2
If the web application is vulnerable to SQL Injection, then it probably will not return anything. To
make sure, the attacker will certainly inject a valid query:
If the content of the page is the same, then the attacker is able to distinguish when the query is True
or False.
What next? The only limitations are privileges set up by the database administrator, different SQL
dialects and finally the attacker's imagination.
Timing Attack
A Timing Attack depends upon injecting the following MySQL query:
Using some time-taking operation e.g. BENCHMARK(), will delay server responses if the expression is
True.
BENCHMARK(5000000,ENCODE('MSG','by 5 seconds'))
Depending on the database server performance and its load, it should take just a moment to finish
this operation. The important thing is, from the attacker's point of view, to specify high number of
BENCHMARK() function repetitions, which should affect the server response time in a noticeable
way.
If the server response was quite long we may expect that the first user password character with
user_id = 1 is character '2'.
Using this method for the rest of characters, it's possible to get to know entire password stored in
the database. This method works even when the attacker injects the SQL queries and the content of
the vulnerable page doesn't change.
Obviously, in this example the names of the tables and the number of columns was specified.
However, it's possible to guess them or check with a trial and error method or similar methods.
Other databases than MySQL also have implemented functions which allow them to use timing
attacks:
* PostgreSQL - pg_sleep()
4-How to prevent
Parameterized Queries (Prepared Statements)
The use of prepared statements (aka parameterized queries) is how all developers should first be
taught how to write database queries. They are simple to write, and easier to understand than
dynamic queries. Parameterized queries force the developer to first define all the SQL code, and
then pass in each parameter to the query later. This coding style allows the database to distinguish
between code and data, regardless of what user input is supplied.
Prepared statements ensure that an attacker is not able to change the intent of a query, even if SQL
commands are inserted by an attacker.
* .NET – use parameterized queries like SqlCommand() or OleDbCommand() with bind variables
* PHP – use PDO with strongly typed parameterized queries (using bindParam())
* Hibernate - use createQuery() with bind variables (called named parameters in Hibernate)
In rare circumstances, prepared statements can harm performance. When confronted with this
situation, it is best to escape all user supplied input using an escaping routine specific to your
database, rather than using a prepared statement. Another option which might solve your
performance issue is used a stored procedure instead.
*Note: 'Implemented safely' means the stored procedure does not include any unsafe dynamic SQL
generation. Developers do not usually generate dynamic SQL inside stored procedures. However, it
can be done, but should be avoided. If it can't be avoided, the stored procedure must use input
validation or proper escaping to make sure that all user supplied input to the stored procedure can't
be used to inject SQL code into the dynamically generated query.
Escaping all User Supplied Input
This third technique is to escape user input before putting it in a query. This may be an effective
technique to remediate SQL injection on existing applications, because you can apply it with almost
no effect on the structure of the code. If you are concerned that rewriting your dynamic queries as
prepared statements or stored procedures might break your application or adversely affect
performance, then this might be the best approach for you.
This technique works like this. Each DBMS supports a character escaping scheme were you can
escape special characters in order to indicate to the DBMS that the characters you are providing in
the query are intended to be data, and not code. If you then escape all user supplied input using the
proper escaping scheme for the database you are using, the DBMS will not confuse that input with
SQL code written by the developer, thus avoiding any possible SQL injection vulnerabilities.
To perform this escaping, you need to understand the escaping scheme supported by the DBMS you
are using and write an escaping routine. Make sure that you do not use weak escaping functions,
such as PHP’s addslashes() or character replacement functions like str_replace("'", "") (This just
converts single quotes to two single quotes). These are weak and have been successfully exploited
by attackers. You need to make sure that the escaping routine you use escapes ALL the dangerous
characters for the interpreter you are passing the data to.
White list validation is appropriate for all input fields provided by the user. White list validation
involves defining exactly what IS authorized, and by definition, everything else is not authorized. If
it's well structured data, like dates, social security numbers, zip codes, e-mail addresses, etc. then
the developer should be able to define a very strong validation pattern, usually based on regular
expressions, for validating such input. If the input field comes from a fixed set of options, like a drop
down list or radio buttons, then the input needs to match exactly one of the values offered to the
user in the first place. The most difficult fields to validate are so called 'free text' fields, like blog
entries. However, even those types of fields can be validated to some degree, you can at least
exclude all non-printable characters, and define a maximum size for the input field.
Be aware that "Input Validating" doesn't mean merely "remove the quotes" because even "regular"
characters can be troublesome.
Additional Defenses (Configurations)
Least Privilege
Web applications should not use one connection for all transactions to the database. Because if a
SQL Injection bug has been exploited, it can grant most access to the attacker. So it is better that
web applications use the right connection for the user such using a connection with read permission
for not logged in user which has only access to specified tables and not to the others.
The effect here is that even a "successful" SQL injection attack is going to have much more limited
success. Because attacker is able to do the UPDATE request that ultimately grants access.
For instance, putting the machine in a DMZ with extremely limited pinholes "inside" the network
means that even getting complete control of the web server doesn't automatically grant full access
to everything else. This won't stop everything, of course, but it makes it a lot harder.
This information is useful to developers, but it should be restricted - if possible - to just internal
users.
In asp.net you can turn off error reporting by setting CustomErrors in web.config file. Below is an
example:
<customErrors mode="RemoteOnly">
In PHP you can do this by using error_reporting(0); on each code page or with php.ini configurations.
PHP Configuration
PHP Configuration has a direct bearing on the severity of attacks. Although no particular CVE entries
are found against configuration, poor configuration choices maximize the attacker’s advantage and
damage they can cause poorly configured systems. What is worse, many “security” options in PHP
are set incorrectly by default and give a false sense of security.
It is surprising that there is no agreed "secure" PHP configuration, and even more surprising that this
is not how PHP is configured by default. There are arguments for and against the most common
security options:
* safe_mode and open_basedir (disabled by default, should be enabled and correctly configured.
Be aware that safe_mode really isn't safe and can be worse than useless)
5-Fixed Code
Parameterized Queries
Java)
String custname = request.getParameter("customerName"); // This should
REALLY be validated too
pstmt.setString( 1, custname);
Java)
String firstname = req.getParameter("firstname");
String query = "SELECT id, firstname, lastname FROM authors WHERE forename
= ? and surname = ?";
pstmt.setString( 1, firstname );
pstmt.setString( 2, lastname );
try
C#.net)
String query =
try {
OleDbCommand command = new OleDbCommand(query, connection);
command.Parameters.Add(new OleDbParameter("customerName",
CustomerName Name.Text));
// …
// error handling
Perl)
$sth = $dbh->prepare("SELECT email, userid FROM members WHERE email = ?;");
$sth->execute($email);
safeHQLQuery.setParameter("productid", userSuppliedParameter);
PHP)
In PHP version 5 and above, there are multiple choices for using parameterized statements. The PDO
database layer is one of them:
$stmt->bindParam(':username', $user);
$stmt->bindParam(':password', $pass);
$stmt->execute();
Java)
This is an example using Java and the JDBC API:
prep.setString(1, username);
prep.setString(2, password);
prep.executeQuery();
PHP)
There are also vendor-specific methods. For example in MySQL 4.1 and above with the mysqli
extension.
$stmt = $db -> prepare("SELECT priv FROM testUsers WHERE username=? AND
password=?");
ColdFusion
In ColdFusion, the CFQUERYPARAM statement is useful in conjunction with the CFQUERY statement
to nullify the effect of SQL code passed within the CFQUERYPARAM value as part of the SQL clause.
SELECT *
FROM COMMENTS
</cfquery>
VB.NET)
Dim thisCommand As SQLCommand = New SQLCommand("SELECT Count(*) " & _
ASP)
<%
option explicit
'Create Connection
set conn=server.createObject("ADODB.Connection")
conn.open "DNS=LOCAL"
'Create Command
With cmd
.activeconnection=conn
End With
'....
'....
'Do clean up
recordset.Close
conn.Close
%>
C#.net)
protected void Page_Load(object sender, EventArgs e)
var connect =
ConfigurationManager.ConnectionStrings["NorthWind"].ToString();
cmd.Parameters.Add("@ProductID", SqlDbType.Int);
cmd.Parameters["@ProductID"].Value =
Convert.ToInt32(Request["ProductID"]);
conn.Open();
//Process results
C#.net)
protected void Page_Load(object sender, EventArgs e)
var connect =
ConfigurationManager.ConnectionStrings["NorthWind"].ToString();
cmd.Parameters.AddWithValue("@ProductID",
Convert.ToInt32(Request["ProductID"]);
conn.Open();
//Process results
try {
CallableStatement cs = connection.prepareCall("{call
sp_getAccountBalance(?)}");
cs.setString(1, custname);
VB.NET)
Try
command.CommandType = CommandType.StoredProcedure
command.Parameters.Add(new SqlParameter("@CustomerName",
CustomerName.Text))
Dim reader As SqlDataReader = command.ExecuteReader()
‘ …
Catch se As SqlException
‘ error handling
End Try
VB.net)
Dim thisCommand As SQLCommand = New SqlCommand ("proc_CheckLogon",
Connection)
thisCommand.CommandType = CommandType.StoredProcedure
C#.net)
var connect =
ConfigurationManager.ConnectionStrings["NorthWind"].ToString();
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@ProductID", SqlDbType.Int).Value =
Convert.ToInt32(Request["ProductID"]);
conn.Open();
//Process results
}
}
$Password=$_POST['pass'];
$tablename=$_GET['tb'];
In general:
* If you have to use dynamic table names, do not accept them from the user if at all possible.
* If you have to allow dynamic user choice of table names, ONLY use white lists of acceptable
characters for table names and enforce table name length limits. In particular, many database
systems have a wide range of unacceptable characters and these change between major releases.
PHP)
$Username=$_POST['uname'];
$Password=$_POST['pass'];
ASP)
v_cat = request("category")
set rs=conn.execute(sqlstr)
aspx)
protected void Button1_Click(object sender, EventArgs e)
string query = "Select Count(*) From Users Where Username = '" + strUname
+ "' And Password = '" + strPass + "'";
int result = 0;
conn.Open();
result = (int)cmd.ExecuteScalar();
if (result > 0)
Response.Redirect("LoggedIn.aspx");
}
else
}
6-References
http://www.owasp.org/index.php/Testing_for_SQL_Injection_%28OWASP-DV-005%29
http://www.cgisecurity.com/questions/sql.shtml
http://www.webappsec.org/projects/glossary/#SQLInjection
http://unixwiz.net/techtips/sql-injection.html
http://www.owasp.org/index.php/Blind_SQL_Injection
http://www.owasp.org/index.php/Guide_to_SQL_Injection
http://www.mikesdotnetting.com/Article/113/Preventing-SQL-Injection-in-ASP.NET
http://www.owasp.org/index.php/Reviewing_Code_for_SQL_Injection
http://www.nextgenss.com/papers/advanced_sql_injection.pdf
http://www.securiteam.com/securityreviews/5DP0N1P76E.html
http://en.wikipedia.org/wiki/SQL_injection
http://www.owasp.org/index.php/SQL_Injection
http://www.w3schools.com/sql/
http://ferruh.mavituna.com/sql-injection-cheatsheet-oku/