Vous êtes sur la page 1sur 23

The Target Intranet

This appeared to be an entirely custom application, and we had no


prior knowledge of the application nor access to the source code: this
was a "blind" attack. A bit of poking showed that this server ran
Microsoft's IIS 6 along with ASP.NET, and this suggested that the
database was Microsoft's SQL server: we believe that these techniques
can apply to nearly any web application backed by any SQL server.
The login page had a traditional username-and-password form, but
also an email-me-my-password link; the latter proved to be the
downfall of the whole system.
When entering an email address, the system presumably looked in the
user database for that email address, and mailed something to that
address. Since my email address is not found, it wasn't going to send
me anything.
So the first test in any SQL-ish form is to enter a single quote as part
of the data: the intention is to see if they construct an SQL string
literally without sanitizing. When submitting the form with a quote in
the email address, we get a 500 error (server failure), and this
suggests that the "broken" input is actually being parsed literally.
Bingo.
We speculate that the underlying SQL code looks something like this:
SELECT f i e l d l i s t
FROM tab le
WHERE f i e l d= $ ' EMAIL ';
Here, $EMAIL is the address submitted on the form by the user, and
the larger query provides the quotation marks that set it off as a literal
string. We don't know the specific names of the fields or table
involved, but we do know their nature, and we'll make some good
guesses later.
When we enter steve@unixwiz.net' - note the closing quote mark -
this yields constructed SQL:
SELECT f i e l d l i s t
FROM tab le
WHERE f i e l d= 'steve@unixwiz .net ' ';
when this is executed, the SQL parser find the extra quote mark and
aborts with a syntax error. How this manifests itself to the user
depends on the application's internal error-recovery procedures, but
it's usually different from "email address is unknown". This error
response is a dead giveaway that user input is not being sanitized
properly and that the application is ripe for exploitation.
Since the data we're filling in appears to be in the WHERE clause, let's
change the nature of that clause in an SQL legal way and see what
happens. By entering anything' OR 'x'='x, the resulting SQL is:
SELECT f i e l d l i s t
FROM tab le
WHERE f i e l d= a ' nything ' OR ' x '= 'x
';
Because the application is not really thinking about the query - merely
constructing a string - our use of quotes has turned a single-
component WHERE clause into a two-component one, and the 'x'='x'
clause is guaranteed to be true no matter what the first clause is
(there is a better approach for this "always true" part that we'll touch
on later).
But unlike the "real" query, which should return only a single item each
time, this version will essentially return every item in the members
database. The only way to find out what the application will do in this
circumstance is to try it. Doing so, we were greeted with:
Your login information has been mailed to
random.person@example.com.
Our best guess is that it's the first record returned by the query,
effectively an entry taken at random. This person really did get this
forgotten-password link via email, which will probably come as
surprise to him and may raise warning flags somewhere.
We now know that we're able to manipulate the query to our own
ends, though we still don't know much about the parts of it we cannot
see. But we have observed three different responses to our various
inputs:
• "Your login information has been mailed to email"
• "We don't recognize your email address"
• Server error

The first two are responses to well-formed SQL, while the latter is for
bad SQL: this distinction will be very useful when trying to guess the
structure of the query.

Schema field mapping

The first steps are to guess some field names: we're reasonably sure
that the query includes "email address" and "password", and there
may be things like "US Mail address" or "userid" or "phone number".
We'd dearly love to perform a SHOW TABLE, but in addition to not
knowing the name of the table, there is no obvious vehicle to get the
output of this command routed to us.
So we'll do it in steps. In each case, we'll show the whole query as we
know it, with our own snippets shown specially. We know that the tail
end of the query is a comparison with the email address, so let's guess
email as the name of the field:
SELECT f i e l d l i s t
FROM tab le
WHERE f i e l d= x ' ' AND emai l IS NULL; -';-
The intent is to use a proposed field name (email) in the constructed
query and find out if the SQL is valid or not. We don't care about
matching the email address (which is why we use a dummy 'x'), and
the -- marks the start of an SQL comment. This is an effective way to
"consume" the final quote provided by application and not worry about
matching them.
If we get a server error, it means our SQL is malformed and a syntax
error was thrown: it's most likely due to a bad field name. If we get
any kind of valid response, we guessed the name correctly. This is the
case whether we get the "email unknown" or "password was sent"
response.
Note, however, that we use the AND conjunction instead of OR: this is
intentional. In the SQL schema mapping phase, we're not really
concerned with guessing any particular email addresses, and we do
not want random users inundated with "here is your password" emails
from the application - this will surely raise suspicions to no good
purpose. By using the AND conjunction with an email address that
couldn't ever be valid, we're sure that the query will always return zero
rows and never generate a password-reminder email.
Submitting the above snippet indeed gave us the "email address
unknown" response, so now we know that the email address is stored
in a field email. If this hadn't worked, we'd have tried email_address
or mail or the like. This process will involve quite a lot of guessing.
Next we'll guess some other obvious names: password, user ID, name,
and the like. These are all done one at a time, and anything other than
"server failure" means we guessed the name correctly.
SELECT f i e l d l i s t
FROM tab le
WHERE emai l = 'x ' AND user id IS NULL; -';-
As a result of this process, we found several valid field names:
• email
• passwd
• login_id
• full_name

There are certainly more (and a good source of clues is the names of
the fields on forms), but a bit of digging did not discover any. But we
still don't know the name of the table that these fields are found in -
how to find out?

Finding the table name

The application's built-in query already has the table name built into it,
but we don't know what that name is: there are several approaches for
finding that (and other) table names. The one we took was to rely on a
subselect.
A standalone query of
SELECT COUNT(* ) FROM tabname
Returns the number of records in that table, and of course fails if the
table name is unknown. We can build this into our string to probe for
the table name:
SELECT email, passwd, login_id, full_name
FROM tab le
WHERE emai l = x' ' AND 1=(SELECT COUNT(*) FROM tabname );
--';
We don't care how many records are there, of course, only whether
the table name is valid or not. By iterating over several guesses, we
eventually determined that members was a valid table in the
database. But is it the table used in this query? For that we need yet
another test using table.field notation: it only works for tables that
are actually part of this query, not merely that the table exists.
SELECT email, passwd, login_id, full_name
FROM members
WHERE email = 'x' AND members.email IS NULL; --';
When this returned "Email unknown", it confirmed that our SQL was
well formed and that we had properly guessed the table name. This
will be important later, but we instead took a different approach in the
interim.

Finding some users

At this point we have a partial idea of the structure of the members


table, but we only know of one username: the random member who
got our initial "Here is your password" email. Recall that we never
received the message itself, only the address it was sent to. We'd like
to get some more names to work with, preferably those likely to have
access to more data.
The first place to start, of course, is the company's website to find who
is who: the "About us" or "Contact" pages often list who's running the
place. Many of these contain email addresses, but even those that
don't list them can give us some clues which allow us to find them with
our tool.
The idea is to submit a query that uses the LIKE clause, allowing us to
do partial matches of names or email addresses in the database, each
time triggering the "We sent your password" message and email.
Warning: though this reveals an email address each time we run it, it
also actually sends that email, which may raise suspicions. This
suggests that we take it easy.
We can do the query on email name or full name (or presumably other
information), each time putting in the % wildcards that LIKE
supports:
SELECT email, passwd, login_id, full_name
FROM members
WHERE email = 'x ' OR fu l l _name LIKE '%Bob%' ;
Keep in mind that even though there may be more than one "Bob", we
only get to see one of them: this suggests refining our LIKE clause
narrowly.
Ultimately, we may only need one valid email address to leverage our
way in.

Brute-force password guessing

One can certainly attempt brute-force guessing of passwords at the


main login page, but many systems make an effort to detect or even
prevent this. There could be logfiles, account lockouts, or other devices
that would substantially impede our efforts, but because of the non-
sanitized inputs, we have another avenue that is much less likely to be
so protected.
We'll instead do actual password testing in our snippet by including the
email name and password directly. In our example, we'll use our
victim, bob@example.com and try multiple passwords.
SELECT email, passwd, login_id, full_name
FROM members
WHERE email = 'bob@example.com' AND passwd = 'he l lo123 ';
This is clearly well-formed SQL, so we don't expect to see any server
errors, and we'll know we found the password when we receive the
"your password has been mailed to you" message. Our mark has now
been tipped off, but we do have his password.
This procedure can be automated with scripting in perl, and though we
were in the process of creating this script, we ended up going down
another road before actually trying it.

The database isn't readonly


So far, we have done nothing but query the database, and even
though a SELECT is readonly, that doesn't mean that SQL is. SQL
uses the semicolon for statement termination, and if the input is not
sanitized properly, there may be nothing that prevents us from
stringing our own unrelated command at the end of the query.
The most drastic example is:
SELECT email, passwd, login_id, full_name
FROM members
WHERE email = 'x ' ; DROP TABLE members; - - ' ; - - Boom!
The first part provides a dummy email address -- 'x' -- and we don't
care what this query returns: we're just getting it out of the way so we
can introduce an unrelated SQL command. This one attempts to drop
(delete) the entire members table, which really doesn't seem too
sporting.
This shows that not only can we run separate SQL commands, but we
can also modify the database. This is promising.

Adding a new member

Given that we know the partial structure of the members table, it


seems like a plausible approach to attempt adding a new record to
that table: if this works, we'll simply be able to login directly with our
newly-inserted credentials.
This, not surprisingly, takes a bit more SQL, and we've wrapped it over
several lines for ease of presentation, but our part is still one
contiguous string:
SELECT email, passwd, login_id, full_name
FROM members
WHERE ema i l = x' ' ;
INSERT INTO members
( 'emai l ' , 'passwd' , ' log in_ id ' , ' fu l l _name' )
VALUES ( ' s teve@unixwiz .net ' , 'he l lo ' , ' s teve ' , 'Steve
Fr ied l ' ) ;'-;-
Even if we have actually gotten our field and table names right, several
things could get in our way of a successful attack:
1. We might not have enough room in the web form to enter this
much text directly (though this can be worked around via
scripting, it's much less convenient).
2. The web application user might not have INSERT permission on
the members table.
3. There are undoubtedly other fields in the members table, and
some may require initial values, causing the INSERT to fail.
4. Even if we manage to insert a new record, the application itself
might not behave well due to the auto-inserted NULL fields that
we didn't provide values for.
5. A valid "member" might require not only a record in the
members table, but associated information in other tables (say,
"accessrights"), so adding to one table alone might not be
sufficient.

In the case at hand, we hit a roadblock on either #4 or #5 - we can't


really be sure -- because when going to the main login page and
entering in the above username + password, a server error was
returned. This suggests that fields we did not populate were vital, but
nevertheless not handled properly.
A possible approach here is attempting to guess the other fields, but
this promises to be a long and laborious process: though we may be
able to guess other "obvious" fields, it's very hard to imagine the
bigger-picture organization of this application.
We ended up going down a different road.

Mail me a password

We then realized that though we are not able to add a new record to
the members database, we can modify an existing one, and this
proved to be the approach that gained us entry.
From a previous step, we knew that bob@example.com had an
account on the system, and we used our SQL injection to update his
database record with our email address:
SELECT email, passwd, login_id, full_name
FROM members
WHERE ema i l = x' ' ;
UPDATE members
SET emai l = ' s teve@unixwiz .net '
WHERE emai l = 'bob@example.com ';
After running this, we of course received the "we didn't know your
email address", but this was expected due to the dummy email
address provided. The UPDATE wouldn't have registered with the
application, so it executed quietly.
We then used the regular "I lost my password" link - with the updated
email address - and a minute later received this email:
From: system@example.com
To: steve@unixwiz.net
Subject: Intranet login

This email is in response to your request for your Intranet log in


information.
Your User ID is: bob
Your password is: hello

Now it was now just a matter of following the standard login process to
access the system as a high-ranked MIS staffer, and this was far
superior to a perhaps-limited user that we might have created with our
INSERT approach.
We found the intranet site to be quite comprehensive, and it included -
among other things - a list of all the users. It's a fair bet that many
Intranet sites also have accounts on the corporate Windows network,
and perhaps some of them have used the same password in both
places. Since it's clear that we have an easy way to retrieve any
Intranet password, and since we had located an open PPTP VPN port
on the corporate firewall, it should be straightforward to attempt this
kind of access.
We had done a spot check on a few accounts without success, and we
can't really know whether it's "bad password" or "the Intranet account
name differs from the Windows account name". But we think that
automated tools could make some of this easier.

Other Approaches

In this particular engagement, we obtained enough access that we did


not feel the need to do much more, but other steps could have been
taken. We'll touch on the ones that we can think of now, though we
are quite certain that this is not comprehensive.
We are also aware that not all approaches work with all databases, and
we can touch on some of them here.
Use xp_cmdshell
Microsoft's SQL Server supports a stored procedure xp_cmdshell
that permits what amounts to arbitrary command execution, and
if this is permitted to the web user, complete compromise of the
webserver is inevitable.
What we had done so far was limited to the web application and
the underlying database, but if we can run commands, the
webserver itself cannot help but be compromised. Access to
xp_cmdshell is usually limited to administrative accounts, but
it's possible to grant it to lesser users.
Map out more database structure
Though this particular application provided such a rich post-login
environment that it didn't really seem necessary to dig further, in
other more limited environments this may not have been
sufficient.
Being able to systematically map out the available schema,
including tables and their field structure, can't help but provide
more avenues for compromise of the application.
One could probably gather more hints about the structure from
other aspects of the website (e.g., is there a "leave a comment"
page? Are there "support forums"?). Clearly, this is highly
dependent on the application and it relies very much on making
good guesses.

Mitigations

We believe that web application developers often simply do not think


about "surprise inputs", but security people do (including the bad
guys), so there are three broad approaches that can be applied here.
Sanitize the input
It's absolutely vital to sanitize user inputs to insure that they do
not contain dangerous codes, whether to the SQL server or to
HTML itself. One's first idea is to strip out "bad stuff", such as
quotes or semicolons or escapes, but this is a misguided
attempt. Though it's easy to point out some dangerous
characters, it's harder to point to all of them.
The language of the web is full of special characters and strange
markup (including alternate ways of representing the same
characters), and efforts to authoritatively identify all "bad stuff"
are unlikely to be successful.
Instead, rather than "remove known bad data", it's better to
"remove everything but known good data": this distinction is
crucial. Since - in our example - an email address can contain
only these characters:
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
0123456789
@.-_+

There is really no benefit in allowing characters that could not be


valid, and rejecting them early - presumably with an error
message - not only helps forestall SQL Injection, but also
catches mere typos early rather than stores them into the
database.
Sidebar on email addresses

It's important to note here that email addresses in particular are


troublesome to validate programmatically, because everybody
seems to have his own idea about what makes one "valid", and
it's a shame to exclude a good email address because it contains
a character you didn't think about.
The only real authority is RFC 2822 (which encompasses the
more familiar RFC822), and it includes a fairly expansive
definition of what's allowed. The truly pedantic may well wish to
accept email addresses with ampersands and asterisks (among
other things) as valid, but others - including this author - are
satisfied with a reasonable subset that includes "most" email
addresses.
Those taking a more restrictive approach ought to be fully aware
of the consequences of excluding these addresses, especially
considering that better techniques (prepare/execute, stored
procedures) obviate the security concerns which those "odd"
characters present.
Be aware that "sanitizing the input" doesn't mean merely
"remove the quotes", because even "regular" characters can be
troublesome. In an example where an integer ID value is being
compared against the user input (say, a numeric PIN):
SELECT f i e l d l i s t
FROM tab le
WHERE id = 23 OR 1=1 ; - - Boom! A lways matches !

In practice, however, this approach is highly limited because


there are so few fields for which it's possible to outright exclude
many of the dangerous characters. For "dates" or "email
addresses" or "integers" it may have merit, but for any kind of
real application, one simply cannot avoid the other mitigations.
Escape/Quotesafe the input
Even if one might be able to sanitize a phone number or email
address, one cannot take this approach with a "name" field lest
one wishes to exclude the likes of Bill O'Reilly from one's
application: a quote is simply a valid character for this field.
One includes an actual single quote in an SQL string by putting
two of them together, so this suggests the obvious - but wrong! -
technique of preprocessing every string to replicate the single
quotes:
SELECT f i e l d l i s t
FROM customers
WHERE name = 'Bi l l O' 'Re i l' ly
; - - works OK

However, this naïve approach can be beaten because most


databases support other string escape mechanisms. MySQL, for
instance, also permits \' to escape a quote, so after input of \';
DROP TABLE users; -- is "protected" by doubling the quotes,
we get:
SELECT f i e l d l i s t
FROM customers
WHERE name = '\ ' ' ; DROP TABLE users ; '- ;- - - Boom!

The expression '\'' is a complete string (containing just one


single quote), and the usual SQL shenanigans follow. It doesn't
stop with backslashes either: there is Unicode, other encodings,
and parsing oddities all hiding in the weeds to trip up the
application designer.
Getting quotes right is notoriously difficult, which is why many
database interface languages provide a function that does it for
you. When the same internal code is used for "string quoting"
and "string parsing", it's much more likely that the process will
be done properly and safely.
Some examples are the MySQL function
mysql_real_escape_string() and perl DBD method $dbh-
>quote($value).
These methods must be used.
Use bound parameters (the PREPARE statement)
Though quotesafing is a good mechanism, we're still in the area
of "considering user input as SQL", and a much better approach
exists: bound parameters, which are supported by essentially
all database programming interfaces. In this technique, an SQL
statement string is created with placeholders - a question mark
for each parameter - and it's compiled ("prepared", in SQL
parlance) into an internal form.
Later, this prepared query is "executed" with a list of
parameters:
Example in perl
$sth = $dbh->prepare("SELECT email, userid FROM members
WHERE email = ?; " ) ;

$sth->execute($emai l ) ;

Thanks to Stefan Wagner, this demonstrates bound parameters


in Java:
Insecure version
Statement s = connection.createStatement();
ResultSet rs = s.executeQuery("SELECT email FROM member
WHERE name = "
+ formFie ld) ; / / *boom*

Secure version
PreparedStatement ps = connection.prepareStatement(
"SELECT email FROM member WHERE name = ?" ) ;
ps.setString(1, formFie ld) ;
ResultSet rs = ps.executeQuery();

Here, $email is the data obtained from the user's form, and it is
passed as positional parameter #1 (the first question mark), and
at no point do the contents of this variable have anything to do
with SQL statement parsing. Quotes, semicolons, backslashes,
SQL comment notation - none of this has any impact, because
it's "just data". There simply is nothing to subvert, so the
application is be largely immune to SQL injection attacks.
There also may be some performance benefits if this prepared
query is reused multiple times (it only has to be parsed once),
but this is minor compared to the enormous security benefits.
This is probably the single most important step one can take to
secure a web application.
Limit databas
and segregate
In the case at hand, we observed just two interactions that are
made not in the context of a logged-in user: "log in" and "send
me password". The web application ought to use a database
connection with the most limited rights possible: query-only
access to the members table, and no access to any other table.
The effect here is that even a "successful" SQL injection attack is
going to have much more limited success. Here, we'd not have
been able to do the UPDATE request that ultimately granted us
access, so we'd have had to resort to other avenues.
Once the web application determined that a set of valid
credentials had been passed via the login form, it would then
switch that session to a database connection with more rights.
It should go almost without saying that sa rights should never
be used for any web-based application.
Use stored pro
When the database server supports them, use stored procedures
for performing access on the application's behalf, which can
eliminate SQL entirely (assuming the stored procedures
themselves are written properly).
By encapsulating the rules for a certain action - query, update,
delete, etc. - into a single procedure, it can be tested and
documented on a standalone basis and business rules enforced
(for instance, the "add new order" procedure might reject that
order if the customer were over his credit limit).
For simple queries this might be only a minor benefit, but as the
operations become more complicated (or are used in more than
one place), having a single definition for the operation means it's
going to be more robust and easier to maintain.
Note: it's always possible to write a stored procedure that itself
constructs a query dynamically: this provides no protection
against SQL Injection - it's only proper binding with
prepare/execute or direct SQL statements with bound variables
that provide this protection.
Isolate the we
Even having taken all these mitigation steps, it's nevertheless
still possible to miss something and leave the server open to
compromise. One ought to design the network infrastructure to
assume that the bad guy will have full administrator access to
the machine, and then attempt to limit how that can be
leveraged to compromise other things.
For instance, putting the machine in a DMZ with extremely
limited pinholes "inside" the network means that even getting
complete control of the webserver doesn't automatically grant
full access to everything else. This won't stop everything, of
course, but it makes it a lot harder.
Configure erro
The default error reporting for some frameworks includes
developer debugging information, and this cannot be shown to
outside users. Imagine how much easier a time it makes for an
attacker if the full query is shown, pointing to the syntax error
involved.
This information is useful to developers, but it should be
restricted - if possible - to just internal users.
Note that not all databases are configured the same way, and not all
even support the same dialect of SQL (the "S" stands for "Structured",
not "Standard"). For instance, most versions of MySQL do not support
subselects, nor do they usually allow multiple statements: these are
substantially complicating factors when attempting to penetrate a
network.
We'd like to emphasize that though we chose the "Forgotten password"
link to attack in this particular case, it wasn't really because this
particular web application feature is dangerous. It was simply one of
several available features that might have been vulnerable, and it
would be a mistake to focus on the "Forgotten password" aspect of the
presentation.
This Tech Tip has not been intended to provide comprehensive
coverage on SQL injection, or even a tutorial: it merely documents the
process that evolved over several hours during a contracted
engagement. We've seen other papers on SQL injection discuss the
technical background, but still only provide the "money shot" that
ultimately gained them access.
But that final statement required background knowledge to pull off,
and the process of gathering that information has merit too. One
doesn't always have access to source code for an application, and the
ability to attack a custom application blindly has some value.
Thanks to David Litchfield and Randal Schwartz for their technical
input to this paper, and to the great Chris Mospaw for graphic design
(© 2005 by Chris Mospaw, used with permission).

Other resources

• (more) Advanced SQL Injection, Chris Anley, Next Generation


Security Software.
• SQL Injection walkthrough, SecuriTeam
• GreenSQL, an open-source database firewall that tries to protect
against SQL injection errors
• "Exploits of a Mom" — Very good xkcd cartoon about SQL
injection
• SQL Injection Cheat Sheet — by Ferruh Mavituna

Last modified: Wed Oct 10 06:28:06 PDT 2007


One of the more devastating attacks on a web application is also one of the most
common: SQL injection. This technique allows an attacker to gain access to the database
that underlies many web sites and read and potentially modify data that is not meant to be
available to users of that site. This article provides an overview of how SQL injection
works and what can be done to avoid it.

A classic example of SQL injection starts with a query that looks something like:

SELECT id FROM users WHERE name='$name' AND pass='$pass';


This query might be used to authenticate users when they log in to a web site. If it returns
a row, the user id returned is considered to be authenticated and the application proceeds
to serve the correct page for that user. In this case, the $name and $pass variables would
come from a login form that might look something like:
<form method="post" action="login.php">
<input type="text" name="name">
<input type="password" name="pass">
<input type="submit" value="login">
</form>

If the login.php program in this example blindly sets the variables to the values that
come from the user, a malicious user can bypass the authentication. Consider the
following inputs:

$user = "' OR 1=1 ";


$pass = "' OR 1=1 LIMIT 1";
This results in a query that is completely different from what the web programmer
expected:
SELECT id FROM users WHERE name='' OR 1=1 AND pass='' OR 1=1 LIMIT
1;
This query will always return one row (unless the table is empty) and it is likely to be the
first entry in the table. For many applications, that entry is the administrative login; the
one with the most privileges.

This simple example barely scratches the surface of the kinds of attacks that can be made
using SQL injection. Depending on the DBMS, it may be possible to do multiple queries
via an injection by separating each with a semicolon:

SELECT id FROM users WHERE name='' AND pass=''; DROP TABLE users;
which is, of course, a rather destructive injection. MySQL does not allow multiple
queries in a statement, but PostgreSQL is susceptible to this technique.
Web site and/or database search functions are particularly dangerous because they display
their output; if a malicious user can inject any query they choose, they can capture the
entire contents of the database. The UNION keyword can turn a query such as:

SELECT city, state FROM users WHERE name LIKE '%$search%';


into:
SELECT city, state FROM users
WHERE name LIKE '%%' UNION
SELECT name, pass FROM users
WHERE name LIKE '%%';
And instead of just printing the city and state of users that match the input string, we are
also printing the username and password of every user in the system.

A certain amount of guessing column names and types is required if an attacker does not
have access to the database schema, but they are often not very hard to guess given some
understanding of the application. Some database systems, notably Microsoft SQL Server,
seem to deliberately shoot themselves in the foot by providing the schema for all tables in
a generally accessible database, thereby removing all the guesswork.

Injection also requires a certain amount of imagination to visualize the kinds of queries
that might be going on behind the input boxes of a web form. It requires quite a bit of
trial and error unless one has access to the source; this is why the majority of reported
SQL injections are in free software or open source web applications.

Note that it is not only web forms using the POST method that are vulnerable, many web
applications that use the GET method are vulnerable to injections via the URL:

http://vulnerablewebapp.com/login.php?\
name=%27%20OR%201%3D1%20&pass=%27%20OR%201%3D1%20LIMIT%201

Like many other web vulnerabilities, SQL injection stems from insufficient filtering of
user input. Unfortunately, it is sometimes difficult to determine what kinds of input
should be accepted (for example the password "' OR 1=1" would not necessarily seem
illegal) and using various filtering functions provided by the language may not actually
prevent injections. The PHP addslashes() function is often used to sanitize user input
because it will put a backslash in front of single quotes which will stop the kinds of
injections described above. Unfortunately, there are techniques to circumvent this
particular 'fix' as well.

Probably the simplest way to protect queries from SQL injection is by using prepared
statements with placeholders. Any reasonable database interface will provide a way to
use this functionality and in many cases, it is fairly portable between languages and DBM
systems.

Instead of directly interpolating string values into query strings, a query is prepared using
'?' as a placeholder for the variables as shown in the following pseudocode:

$sth = prepare("SELECT id FROM users WHERE name=? AND pass=?");


execute($sth, $name, $pass);
This has a number of advantages: the DBMS library is responsible for properly quoting
the values and because of the way the variables are bound to the query, they can never be
treated as anything other than data for the particular place they have in the prepared
statement. This effectively turns the injection attempt above into a query like:
SELECT id FROM users WHERE name='\' OR 1=1 ' AND pass='\' OR 1=1
LIMIT 1';
which is unlikely to authenticate.

Another way to defend against injections is by ensuring that all user input is passed
through a database specific quoting function before being used in a query:

$name = db_quote($name);
$pass = db_quote($pass);
SELECT id FROM users WHERE name=$name AND pass=$pass;
Depending on the language and database API, this method may also be fairly portable.

The final recommended technique is also the most complicated; but it can provide an
additional level of security if stored procedures are available for the DBMS. Stored
procedures are queries (and more complicated functions) that are created by the database
administrator and stored with the database. These procedures are then called by the
application code to do any queries that they require. The equivalent of the prepare
functionality is done on the procedures at the time they are stored and with proper coding,
this will prevent injections. One of the main advantages is that these procedures run with
the privileges of the user that stored them, instead of the user invoking them and this
allows the application to have a much more limited set of privileges than it would
normally require. The upshot is that it can protect the database from reading or writing
even if the application is subverted in some way.

SQL injections are clearly a serious security problem, but one that can be thwarted
relatively easily once one understands the problem and the ways to program around it.

(Log in to post comments)


SQL injection attacks
Posted Mar 30, 2006 2:28 UTC (Thu) by jwb (subscriber, #15467) [Link]

Another great article. One of the many horrors of MS-SQL is the incredible amount of
functionality
available to a SQL injection attacker. MS-SQL can be made to open a connection to any
other
database, even on other hosts or networks. A SQL injection attack against MS-SQL can
allow the
attacker to tell your database to connect to any random instance of SQL Server and
replicate itself.
This obviously takes all the guesswork out of trying to reverse engineer the schema. An
attacker
can rip off an entire MS-SQL instance with a single HTTP request.
Guess work?
Posted Mar 30, 2006 23:49 UTC (Thu) by GreyWizard (subscriber, #1026) [Link]

s/guess work/security through obscurity/

Database features do not excuse sloppy applications.


Guess work?
Posted Mar 31, 2006 6:39 UTC (Fri) by hppnq (guest, #14462) [Link]

Basically, you are suggesting that in order to kill a fly, you should use a proper gun,
practice at a firing range, isolate the fly in a safe environment, surgically optimize your
eye-hand coordination, calculate environmental influences, suppress urges to start
shooting at random, before taking a shot at removing the annoying intruder.

Sure, if a gun is my only tool, I'd go through all that. But I'd rather whack the bastard
with a newspaper.

Nonsense
Posted Mar 31, 2006 15:07 UTC (Fri) by GreyWizard (subscriber, #1026) [Link]

That is a perfectly ridiculous analogy. Whining that the database has too many features
that might be useful for someone exploiting SQL injection vulnerabilities in an unrelated
application is not so much swatting the fly as cursing the publisher for printing a
newspaper that's too hard to swing while the thing is still buzzing around your head.
Nonsense
Posted Mar 31, 2006 15:36 UTC (Fri) by hppnq (guest, #14462) [Link]

So what are you suggesting then? That we should all write perfect code? Yes, that would
solve the problem. Is it realistic? Not a chance in hell.

Most or all security implementations heavily depend on defining proper interfaces to


resources and making sure that access to resources is only possible through these
interfaces.

It follows quite simply that it's wise to start off with as little resources and interfaces as
possible if you care about security.

RTFA
Posted Mar 31, 2006 22:17 UTC (Fri) by GreyWizard (subscriber, #1026) [Link]

Contrary to your raving, filtering user input does not require perfect code. I suggest
reading the article to which this thread is attached. There you will find suggestions such
as using prepared statements or stored procedures. As stated above, "SQL injections [...]
can be thwarted relatively easily once one understands the problem and the ways to
program around it." On the other hand, no database can provide protection from gaping
security holes in external applications.
Mmmmhh
Posted Apr 1, 2006 8:53 UTC (Sat) by hppnq (guest, #14462) [Link]

It seems to me that we are making a lot of fuss about something that we basically feel the
same about. If you take the time to calm down and read the comments as well as the
article you might see this too.

Now, you were the one that brought up the topics of sloppy programming and "security
through obscurity", taking this discussion explicitly to the realm of the real world, where
the perfect solution does not exist. You observed that database features are no excuse for
bad programming, while I am of the opinion that they should not be an excuse.

In the real world resources are limited. At some point a decision will have to be made: is
it good enough? Since security means nothing in the laboratory, and everything in the real
world, this is a very important observation. This is also why I mention writing perfect
code: it cannot be done, and the only way to avoid having to make suboptimal decisions
is to remove the necessity of making those decisions. This is a classic trade-off between
security and functionality.

Instead of having to protect features one does not need, it is better to not have them
available in the first place. That of course leaves more resources available to get the
actual job done: defining the correct interfaces to the functionality you want to provide or
use and protecting those interfaces properly.

On the other hand, no database can provide protection from gaping security holes in
external applications.

This is the same problem. Do take some time to think about it.

Practice What You Preach


Posted Apr 3, 2006 15:50 UTC (Mon) by GreyWizard (subscriber, #1026) [Link]

You reply to a comment about security through obscurity with an irrelevant analogy to
shooting mosquitoes, and now you accuse me of not reading what I reply to? You rant
and rave about the impossibility of perfect code, and now you tell me to calm down?
Amusing. But your airy hand waving about "protecting features one doesn't need" still
misses the point: using the dumbest database available would be a trade-off between
security and functionality only if this were an effective substitute for plugging SQL
injection holes in the application. As long as there are remote exploits the application
cannot meet even the least demanding security requirements with any database.

This is really not so complicated. Practice what you preach, especially with regard to
taking the time to think about it.

Practice What You Preach


Posted Apr 3, 2006 22:19 UTC (Mon) by hppnq (guest, #14462) [Link]

Well, I just tried to add some more perspective to your rather simplistic "thou shalt not
program sloppily" statement. It appears to me that in your enthusiasm to slight me, you
seem to miss your own point completely.

*plonk*

Perspective Indeed
Posted Apr 4, 2006 2:58 UTC (Tue) by GreyWizard (subscriber, #1026) [Link]

You are confused. "Database features do not excuse sloppy applications" is simple. "Thou
shalt not program sloppily" is simplistic. The latter is your contribution, not mine.
Rambling about mosquitoes, whining about perfect code, splitting hairs over "are" and
"should" and pretending I don't understand my own point is your idea of adding
perspective, is it? Spare me such generosity.
SQL injection attacks
Posted Mar 31, 2006 11:06 UTC (Fri) by pdc (subscriber, #1353) [Link]

To try to avoid this we do all access from a web app to the SQL Server database via
stored procedures, with user input passed as parameters. At least then you can reastrict
the privileges of the web application to just the procedures it needs to use. Makes the
database development rather tedious, however.
SQL injection attacks
Posted Mar 31, 2006 19:20 UTC (Fri) by dwkunkel (subscriber, #5999) [Link]

I use Oracle stored procedures to simplify my web applications. All the business logic is
handled by stored procedures and there is no sql in the web pages. The pages just pass
parameters to the appropriate stored procedure.

An Oracle stored procedure can return multiple reference cursors that can be cast to Java
ResultSets and used directly in a web page. The reference cursors can also be converted
to Cached RowSets and used in Data Transfer Objects. Performance is quite good
because everything is done in a single trip to the database.

I don't know about SQL Server, but I find Oracle's PL/SQL to be an easy to use
programming language that makes it relatively simple to produce very readable code.

CHAR() + mysql for injection?


Posted Mar 30, 2006 8:21 UTC (Thu) by wingo (subscriber, #26929) [Link]

I hadn't heard of the CHAR() injection strategy briefly mentioned in the article. Google is
not being helpful about it. Anyone have more information on that one? It sounds
particularly nasty.
SQL injection attacks
Posted Mar 30, 2006 10:14 UTC (Thu) by NAR (subscriber, #1313) [Link]

Great article. I've worked on a project which displayed the results of an SQL query in a
HTML page and once I played with inserting HTML code into the database to break the
output. However, I didn't try to break the SQL insert commands. I think I've just found an
interesting pet project for the afternoon :-)
Bye,NAR
SQL injection attacks
Posted Mar 31, 2006 15:46 UTC (Fri) by hppnq (guest, #14462) [Link]

Cancel your dinner reservations, NAR. ;-)


quoting
Posted Mar 30, 2006 14:20 UTC (Thu) by rfunk (subscriber, #4054) [Link]

It's important to note that the details of quoting strings are DBMS-dependent, and PHP's
addslashes() is insufficient (or in some cases just plain wrong). Some of the comments on
the PHP addslashes() doc page go into the details. It's always better to use a DBMS-
specific quoting function (e.g. mysql_real_escape_string) than to blindly add backslashes.
Which is why PHP's "magic quotes" feature is so annoyingly useless.

This tendency to SQL injection is one of the reasons people see PHP as an inherently
insecure language, or at least one that encourages insecure programming.

input filtering
Posted Mar 30, 2006 14:51 UTC (Thu) by ccyoung (subscriber, #16340) [Link]

the function db_quote() for input filtering is in my experience inadequate.

what is needed is one filter function for each data type. this not only formats but does
type checking. for example, db_get_string may not allow quotes and punctuation,
whereas db_get_text might be more forgiving.

db_get_code( $code, $mustexist=false )


db_get_string( $str, $mustexist=false )
db_get_text( $text, $mustexist=false )
...
db_get_integer( $int, $mustexist=false )

a big gotcha in PHP is it's confusion between 0, null, and an empty string.

SQL injection attacks


Posted Mar 30, 2006 17:55 UTC (Thu) by iabervon (subscriber, #722) [Link]

The prepared statement functionality, at least in some databases, is actually even better
than this article suggests: it causes the statement with the question marks to be parsed to
generate the sequence of database-internal operations which will be performed. Then
when the arguments are filled, it doesn't need to escape the strings, because it doesn't
unescape them; it doesn't treat any characters specially at this point. Furthermore, since
all of the parsing is already done when user input comes in, it can't be induced to perform
unexpected operations, because the sequence of operations it will perform is already
determined at this point.

Furthermore, it's often faster, because it can cache the execution plan for the query,
because all of the "SELECT id FROM users WHERE user=? AND pass=?" parts are
identical, and the parsing can be a significant portion of the query time, since there's a
bunch of effort in figuring out what's going on, and that the useful optimization for this
query is the unique index on users.user.

SQL injection attacks


Posted Mar 30, 2006 19:10 UTC (Thu) by yodermk (subscriber, #3803) [Link]

Great article! However, given this:

SELECT id FROM users WHERE name='$name' AND pass='$pass';

the shown "modified" query:


SELECT id FROM users WHERE name='' OR 1=1 AND pass='' OR 1=1 LIMIT 1;

does not show the end quote (') after $name and $pass. Would it not translate to this:

SELECT id FROM users WHERE name='' OR 1=1' AND pass='' OR 1=1 LIMIT 1';

which would be an SQL error? Or am I missing something???

SQL injection attacks


Posted Mar 30, 2006 19:54 UTC (Thu) by jake (editor, #205) [Link]

> SELECT id FROM users WHERE name='' OR 1=1' AND pass='' OR 1=1 LIMIT 1';

> which would be an SQL error? Or am I missing something???

No, nice catch.

$user = "' OR 1=1 OR name='";


$pass = "' OR 1=1 LIMIT 1 --";

should do the trick ...

jake

SQL injection attacks


Posted Mar 31, 2006 16:56 UTC (Fri) by dps (guest, #5725) [Link]

There is a simpler fix, which also stops other attacks too... do proper input validation---if
something is suppsesd to be a number, make sure it really is. Numbers like "1 OR 1=1"
(without the quotes) can do evil things in contexts expecting numbers. Input validation
stops that too.

Using magic_quote_qpc, SQL syntax randomisation, etc are all useful backstops in case
you somehow fail to properly validate something. I have my doutbs about the security of
stored procedures when fed evil input, unless handling it safely is a primary design goal.

Sadly there is no agreement about parameters in prepared statements. ODBC and MySQL
wants ?. Postgresql want $1, $2, etc and oracle accept $<almost anythihg>. (This sort of
thing is one of the "joys" of writng multiple database server SQL.)

SQL injection attacks


Posted Apr 1, 2006 13:35 UTC (Sat) by holstein (subscriber, #6122) [Link]

As other have pointed out, '1 OR 1=1' can be a valid password. Or at least, a validating
routine for valid password should accept this; something like '1hotguys = 1' is a valid
password...

So, input validation is not a silver bullet; it's just a step in the journey.
Using a stored procedure help because the data will be used just as it is: data. You can't
turn the input in a variation of the execute query (at least, note easily). Of course, bad
data could trigger other kind of problems (like, say, a buffer overflow attack on the
RDBMS).

A for the syntax for placeholders in prepared statements, with the Perl DBI at least, the
syntax is the same for every RDBMS. And having worked with MySQL, Oracle and
SQLite with PHP, I don't recall having seen different syntax used; maybe it was because I
was not using the 'direct' API (like mysql_xx, oc8_xx, etc.) but instead using abstraction
layer, like PEAR DB. But if you are writing multiple database server application, I would
guess that this is a 'sine qua none' condition. At least for me!

SQL injection attacks


Posted Apr 7, 2006 9:33 UTC (Fri) by m.alessandrini (guest, #36991) [Link]

A very interesting review of internet application vulnerabilities can be found at


http://www.owasp.org/documentation/topten.html (OWASP Top Ten Most Critical Web
Application Security Vulnerabilities)

Vous aimerez peut-être aussi