Parameter Markers with SQL - PART 1: Security

Monday, January 17 2005 @ 11:24 PM EST

Contributed by: boz

What is a parameter marker?

In the realm of applications that access a relational database through some sort of API, parameter markers can be used to indicate where the values of application values are to be positioned in an SQL statement. This is essentially a standard feature of most major database development environments. To clarify the term "database development environment", I am referring to the specific database system (i.e. DB2, mySQL, Oracle, MS SQL Server, Postgres...etc), the API used to access it, and the appropriate database driver if using a database-independent API like Perl's DBI module, Java's JDBC, or ODBC. You should consult your database driver's documentation (or native API if you're using that) to ensure that parameter markers can be used. In this article, you'll see how this works for creating secure database applications.

For the examples in this article, I will be using Perl's DBI module. The code snippets here are not complete and are meant for demonstration purposes only.

A Simple Example: Fending off an SQL Injection Attack

The Problem:

use DBI; 
use strict;

# Connect to the database, returning a database handle

my $dbh = DBI->connect($data_source, $username, $password)
                    or die $DBI::errstr;

# Form our SQL statement.

my $sql = "select name, description, price from products where category = '$somevalue'";

# Prepare the SQL statement, returning a statement handle

my $sth = $dbh->prepare($sql) or die ;

# Execute

my $sth->execute() or die $DBI::errstr;

# Fetch the rows and do something with them

while (my ($name, $description, $price) = $sth->fetchrow_array ) {

    # do some stuff....
}
  # Clean up and disconnect from the database.

$sth->finish();
$dbh->disconnect();


The problem with this code is in the line my $sql = "select name, description, price from products where category = '$somevalue'";, especially if $somevalue is determined by user input. The problem here is that $somevalue can be anything, and no checks are done to ensure that proper quoting can be done with ths pair of single quotes around the literal value $somevalue. If the user were to enter a string with a single quote as part of $somevalue, the query will most likely fail with a syntax error in the SQL.

A much bigger problem is the code's susceptibility to an SQL injection attack. This allows a malicious user to "inject" extra SQL to modify the query in a way that wasn't originally intended.

Consider what would happen if a user passed food' or 'a'='a. The SQL would be built as:

select name, description, price from products where category = 'food' or 'a'='a'

This would actually return all rows no matter what category was selected, since the where clause would now be true for all products. The good news is that although many command-line database clients will accept a statement terminator like a semicolon, most Perl DBD drivers and the mysql_query() function for PHP will return an error if a semicolon is passed with the query. This prevents the injection of a dangerous statement like this:

select name, description, price from products where category = 'food';delete from users where 'a'='a'

Such a statement would work, for example, when entered via the mySQL's command-line client, but not through Perl (using DBI) or a PHP script (via mysql_query()) accessing MySQL. However, it is not wise to assume that all APIs or drivers are as bomb-proof.

If the database schema is known by an attacker, SQL injection attacks can theoretically be used to take control of a entire site by logging in without a valid password, adding users with administrative privileges or modifying site content. But it gets even worse. Depending on the driver and on the system accessed, you can sometimes even add shell commands following a statement terminator to the SQL statement to execute arbitrary code on the server.

(Side Note: Make sure that $username has only the database authority absolutely required.)

It is possible to write a Perl regular expression to clean out special characters and quote it properly, escaping where needed, but parameter markers do this in an easy and database-independent manner.

The Fix:

use DBI; 
use strict;

# Connect to the database, returning a database handle

my $dbh = DBI->connect($data_source, $username, $password)
                    or die $DBI::errstr;

# Form our SQL statement.

my $sql = "select name, description, price from products where category = ?";

# Prepare the SQL, returning a statement handle

my $sth = $dbh->prepare($sql) or die ;

# Execute

my $sth->execute($somevalue) or die $DBI::errstr;

# Fetch the rows and do something with them

while (my ($name, $description, $price) = $sth->fetchrow_array ) {

    # do some stuff....
}
  # Clean up and disconnect from the database.

$sth->finish();
$dbh->disconnect();


The changes here were pretty trivial. We now have a parameter marker, the ?, in place of the string literal in the SQL statement, and we now pass $somevalue to the execute function. This implicitly binds the value of $somevalue to the marker, and will properly quote and escape the contents. So, if someone tries to submit food' or 'a'='a, we'd get:

select name, description, price from products where category = 'food' or 'a'='a'

This may return no rows when trying to find a category called food' or 'a'='a, but at least it didn't alter the intended meaning of the SQL, which is to find products in just one category.

Consistent with Perl's philosophy in having more than one way to do things, the DBI module includes an explicit bind function, so you can bind values to parameter markers at other points before the execute() function is called. Passing the values to the execute() function above does the bind, quoting, and execution in one step. DBI also provides a DBI::quote() function which does just string quoting and character escaping, which is useful when you need a properly quoted string before it actually gets bound.

Other APIs generally have the similar functions, where binding parameters works the same way. See your API's documentation for specifics on how to use this functionality.

0 comments



http://www.upperbound.com/article.php/20050117232424605