...
A SQL command to authenticate a user might take the form:
Code Block |
---|
SELECT * FROM Users WHERE userid='<USERID>' |
AND
AND
password='<PASSWORD>' |
If it returns any records, the user ID and password are valid.
However, if an attacker can substitute arbitrary strings for <USERID>
and <PASSWORD>
, they can perform a SQL injection by using the following string 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 <PASSWORD>
such as:
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.
...
Unfortunately, this code example permits a SQL injection attack because the string passed to prepare
accepts unsanitized input arguments. The attack scenario outlined previously would work as described.
my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","") |
or die "Couldn't connect to database: " . DBI->errstr; |
# ... 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 =
# 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; |
) or die $userid, $hashed_password)
or die "Couldn't execute statement: " . $sth->errstr; |
if (my @data = $sth->fetchrow_array()) { |
print "Access granted to user: $username ($userid)\n"; |
print "Invalid username / password. Access denied\n"; |
Compliant Solution (prepared statement)
Fortunately, Perl's DBI library provides an API for building SQL commands that sanitize untrusted data. The prepare()
method properly escapes input strings, preventing SQL injection when used properly. This is an example of component-based sanitization.
my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","") |
or die "Couldn't connect to database: " . DBI->errstr; |
# ... 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 =
# 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()) { |
print "Access granted to user: $username ($userid)\n"; |
print "Invalid username / password. Access denied\n"; |
...
Risk Assessment
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|
IDS33-PL | high | likely | high | P9 | L2 |
...