...
Suppose a database contains user names and passwords used to authenticate users of the system. The user names have a string size limit of 8. The passwords have a size limit of 20.
An A SQL command to authenticate a user might take the form:
| Code Block |
|---|
SELECT * FROM Users WHERE userid='<USERID>' AND password='<PASSWORD>' |
If it returns any records, the user ID and password are valid.
However, if an attacker can substitute arbitrary strings for for <USERID> and and <PASSWORD>, they can perform a SQL injection by using the following string for for <USERID>:
...
| Code Block |
|---|
validuser' OR '1'='1 |
When injected into the command, the command becomes:
| Code Block |
|---|
SELECT * FROM Users WHERE userid='validuser' OR '1'='1' AND password=<PASSWORD> |
...
If validuser
...
is a valid user name,
...
this SELECT
...
statement selects
...
the validuser
...
record in the table. The password is never checked because userid='validuser' is true; consequently the items after
...
the OR
...
are not tested. As long as the components after the OR generate a syntactically correct SQL expression, the attacker is granted the access
...
of validuser.
Likewise, an attacker could supply a string for for <PASSWORD> such such as:
| Code Block |
|---|
' OR '1'='1 |
This would yield the following command:
| Code Block |
|---|
SELECT * FROM Users WHERE userid='' AND password='' OR '1'='1' |
This time,
...
the '1'='1'
...
tautology disables both user ID and password validation, and the attacker is falsely logged in without a correct login ID or password.
Noncompliant Code Example (SQL Injection)
This noncompliant code example shows Perl DBI code to authenticate a user to a system. The program connects to a database, prompts the user for a user ID and password, and hashes the password.
Unfortunately, this code example permits a SQL injection attack because the string passed to prepare accepts accepts unsanitized input arguments. The attack scenario outlined previously would work as described.
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
use DBI; use warnings; use strict; my $dbfile = "users.db"; my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","") or die "Couldn't connect to database: " . DBI->errstr; sub hash() { # hash the password } print "Enter your id: "; my $userid = <STDIN>; chomp $userid; print "Enter your password: "; my $password = <STDIN>; chomp $password; my $hashed_password = hash( password); my $sth = $dbh->prepare("SELECT * FROM Users WHERE userid = '$userid' AND password = '$hashed_password'") or die "Couldn't prepare statement: " . $dbh->errstr; $sth->execute($userid, $hashed_password) or die "Couldn't execute statement: " . $sth->errstr; if (my @data = $sth->fetchrow_array()) { my $username = $data[1]; my $id = $data[2]; print "Access granted to user: $username ($userid)\n"; } if ($sth->rows == 0) { print "Invalid username / password. Access denied\n"; } $sth->finish; $dbh->disconnect; |
...
Compliant Solution (prepared statement)
Fortunately, Perl's DBI library provides an API for building SQL commands that sanitize untrusted data. The The prepare() method method properly escapes input strings, preventing SQL injection when used properly. This is an example of component-based sanitization.
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
use DBI; use warnings; use strict; my $dbfile = "users.db"; my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","") or die "Couldn't connect to database: " . DBI->errstr; sub hash() { # hash the password } print "Enter your id: "; my $userid = <STDIN>; chomp $userid; print "Enter your password: "; my $password = <STDIN>; chomp $password; my $hashed_password = hash( password); my $sth = $dbh->prepare("SELECT * FROM Users WHERE userid = ? AND password = ?") or die "Couldn't prepare statement: " . $dbh->errstr; $sth->execute($userid, $hashed_password) or die "Couldn't execute statement: " . $sth->errstr; if (my @data = $sth->fetchrow_array()) { my $username = $data[1]; my $id = $data[2]; print "Access granted to user: $username ($userid)\n"; } if ($sth->rows == 0) { print "Invalid username / password. Access denied\n"; } $sth->finish; $dbh->disconnect; |
...
Risk Assessment
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
IDS33-PL | high | likely | high | P9 | L2 |
...