5.4 KiB
Parameterize Database Queries
Often we write code that interacts with a database using parameters provided by the application's users. These parameters include credentials, resource identifiers and other user-supplied data.
Care must be taken when dynamically creating database queries to prevent them being subverted by user supplied malicious input, this is generally referred to as SQL injection (SQLi). SQL injection works because the user input changes the logic of the SQL query, resulting in behaviour that is not intended by the application developer.
The results of a successful SQL injection attack can include disclosure of sensitive information such as user passwords, modification or deletion of data, and gaining execution privileges, which would allow an attacker to run arbitrary commands on the database server.
SQL injection can typically be mitigated by using some combination of prepared statements , stored procedures and escaping of user supplied input. Most secure web applications will use all three and we have described their use below.
Code Examples
SQLAlchemy
Incorrect
This example uses the built-in parameter substitution mechanism '%' to insert a value into the query string, this will perform an unsafe literal insertion and not provide any escaping.
import sqlalchemy
= engine.connect()
connection = 'jsmith' # our intended usage
myvar = 'jsmith or 1=1' # this will return all users
myvar = 'jsmith; DROP TABLE users' # this drops (removes) the users table
myvar = "select username from users where username = %s" % myvar
query = connection.execute(query)
result for row in result:
print "username:", row['username']
connection.close()
Correct
This example uses SQLAlchemy's built in parameter substitution mechanism to safely replace the ':name' variable with a provided value.
import sqlalchemy
= engine.connect()
connection = 'jsmith' # our intended usage
myvar = 'jsmith or 1=1' # only matches this odd username
myvar = "select username from users where username = :name"
query = connection.execute(query, name = myvar)
result for row in result:
print "username:", row['username']
connection.close()
MySQL
Incorrect
Without using any escaping mechanism, potentially unsafe queries can be created.
import MySQLdb
= "select username from users where username = '%s'" % name
query = MySQLdb.connect('localhost', 'testuser', 'test623', 'testdb');
con
with con:
= con.cursor()
cur cur.execute(query)
Better
In this example the query is created using pythons standard, unsafe '%' operator. MySQL's 'escape_string' method is used to perform escaping on the user input string prior to inclusion in the string.
import MySQLdb
= "select username from users where username = '%s'" % MySQLdb.escape_string(name)
query = MySQLdb.connect('localhost', 'testuser', 'test623', 'testdb');
con
with con:
= con.cursor()
cur cur.execute(query)
Correct
The correct way to do this using a parameterized query might look like the following:
import MySQLdb
= "select username from users where username = '%s'"
query = MySQLdb.connect('localhost', 'testuser', 'test623', 'testdb');
con
with con:
= con.cursor()
cur cur.execute(query, (username_value,))
This works because the logic of the query is compiled before the user input is considered.
PostgreSQL (Psycop2)
Incorrect
This example uses python's unsafe default parameter substitution mechanism to build a query string. This will not perform any escaping, unlike the correct example below the string is processed and passed as a single parameter to 'execute'.
import psycopg2
= psycopg2.connect("dbname=test user=postgres")
conn = conn.cursor()
cur "select username from users where username = '%s'" % name) cur.execute(
Correct
This example uses Psycop2's parameter substitution mechanism to build a query string. Despite the use '%' to indicate the substitution token, it is not the same as Python's built in string operator %. Note the value(s) are passed as parameters to 'execute' separately.
import psycopg2
= psycopg2.connect("dbname=test user=postgres")
conn = conn.cursor()
cur "select username from users where username = '%s'", (name,)) cur.execute(
Consequences
- Potential for full disclosure of data
- Potential for remote code execution