Дэвид Лебланк - 19 смертных грехов, угрожающих безопасности программ
}
} catch (SqlException e) {
// Ой!
}
Греховность PHP
Вот та же классическая ошибка, но в программе на языке РНР, часто применяемом для доступа к базам данных.
...<?php
$db = mysql_connect("localhost","root","$$sshhh...!");
mysql_select_db("Shipping",$db);
$id = $HTTP_GET_VARS["id"];
$qry = "SELECT ccnum FROM cust WHERE id = %$id%";
$result = mysql_query($qry,$db);
if ($result) {
echo mysql_result($result,0,"ccnum");
} else {
echo "No result! " . mysql_error();
}
?>
Греховность Perl/CGI
И снова тот же дефект, но на этот раз в программе на достопочтенном Perl:
...#!/usr/bin/perl
use DBI;
use CGI;
print CGI::header();
$cgi = new CGI;
$id = $cgi->param('id');
$dbh = DBI->connect('DBI:mysql:Shipping:localhost',
'root',
'$3cre+')
or print "Ошибка connect : $DBI::errstr";
$sql = "SELECT ccnum FROM cust WHERE id = " . $id;
$sth = $dbh->prepare($sql)
or print "Ошибка prepare : $DBI::errstr";
$sth->execute()
or print "Ошибка execute : $DBI::errstr";
# Вывести данные
while (@row = $sth->fetchrow_array ) {
print "@row<br>";
}
$dbh->disconnect; print "</body></html>";
exit;
Греховность Java
Еще один распространенный язык, Java. Подвержен внедрению SQL по той же схеме.
...import java.*;
import java.sql.*;
public static boolean doQuery(String Id) {
Connection con = null;
try
{
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
con = DriverManager.getConnection("jdbc:microsoft:sqlserver: " +
"//localhost:1433", "sa", "$3cre+");
Statement st = con.createStatement();
ResultSet rs = st.executeQuery("SELECT ccnum FROM cust WHERE id=" +
Id);
while (rx.next()) {
// Полюбоваться на результаты запроса
}
rs.close();
st.close();
}
catch (SQLException e)
{
// Ой!
return false;
}
catch (ClassNotFoundException e)
{
// Не найден класс
return false;
}
finally
{
try
{
con.close();
} catch(SQLException e) {}
}
return true;
}
Греховность SQL
Подобный код встречается не так часто, но автор пару раз наталкивался на него в промышленных системах. Показанная ниже хранимая процедура просто принимает строку в качестве параметра и исполняет ее!...CREATE PROCEDURE dbo.doQuery(@query nchar(128))
AS
exec(@query)
RETURN
А вот следующий код распространен куда шире и не менее опасен:
...CREATE PROCEDURE dbo.doQuery(@id nchar(128))
AS
DECLARE @query nchar(256)
SELECT @query = 'select ccnum from cust where id = ''' + @id + ''''
EXEC @query
RETURN
Здесь опасная конкатенация строк выполняется внутри процедуры. То есть вы по–прежнему совершаете постыдный грех, даже если процедура вызвана из корректного кода на языке высокого уровня.
Стоит поискать и другие операторы конкатенации, имеющиеся в SQL, а именно «+» и «||», а также функции CONCAT() и CONCATENATE().
Во всех этих примерах противник контролирует переменную Id. Важно всегда представлять себе, что именно контролирует атакующий, это поможет понять, есть реальная ошибка или нет. В данном случае противник может задать любое значение переменной Id, участвующей в запросе, и тем самым управлять видом строки запроса. Последствия могут оказаться катастрофическими.
Классическая атака состоит в том, чтобы видоизменить SQL–запрос, добавив лишние части и закомментарив «ненужные». Например, если противник контролирует переменную Id, то может задать в качестве ее значения строку 1 or 2>1 – – , тогда запрос примет такой вид:
...SELECT ccnum FROM cust WHERE id=1 or 2>1 – –
Условие 2>1 истинно для всех строк таблицы, поэтому запрос возвращает все строки из таблицы cust, другими словами, номера всех кредитных карточек. Можно было бы воспользоваться классической атакой «1 = 1», но сетевые администраторы часто включают поиск такой строки в системы обнаружения вторжений (IDS), поэтому мы применили условие «2>1», которое столь же эффективно, но «проходит под лучом радара».
Оператор комментария – – убирает из поля зрения сервера все последующие символы запроса, которые могла бы добавить программа. В одних базах данных для комментирования применяются символы —, в других – #. Проверьте, что воспринимает в качестве комментария ваша база.
Различных вариантов атак слишком много, чтобы перечислять их здесь, дополнительный материал вы найдете в разделе «Другие ресурсы».
Родственные грехи
Во всех приведенных выше примерах демонстрируются и другие грехи:
□ соединение от имени учетной записи с высоким уровнем доступа;
□ включение пароля в текст программы;
□ сообщение противнику излишне подробной информации в случае ошибки.
Рассмотрим их по порядку. Везде соединение устанавливается от имени административного или высокопривилегированного пользователя, хотя достаточно было бы пользователя, имеющего доступ только к одной базе данных. Это означает, что противник потенциально сможет манипулировать и другой информацией, а то и всем сервером. Короче говоря, соединение с базой данных от имени пользователя с высоким уровнем доступа – скорее всего, ошибка и нарушение принципа наименьших привилегий.
«Зашивание» паролей в код – почти всегда порочная идея. Подробнее см. грех 11 и 12 и предлагаемые там «лекарства».
Наконец, в случае ошибки противник получает слишком много информации. Воспользовавшись ей, он сможет получить представление о структуре запроса и, быть может, даже узнать имена объектов базы. Более подробную информацию и рекомендации см. в грехе 6.
Где искать ошибку
Любое приложение, обладающее перечисленными ниже характеристиками, подвержено риску внедрения SQL:
□ принимает данные от пользователя;
□ не проверяет корректность входных данных;
□ использует введенные пользователем данные для запроса к базе;
□ применяет конкатенацию или замену подстроки для построения SQL–запроса либо пользуется командой SQL exec (или ей подобной).
Выявление ошибки на этапе анализа кода
Во время анализа кода на предмет возможности внедрения SQL прежде всего ищите места, где выполняются запросы к базе данных. Ясно, что программам, не обращающимся к базе данных, эта напасть не угрожает. Мы обычно ищем следующие конструкции:
Выяснив, что в программе есть обращения к базе данных [1] , нужно определить, где выполняются запросы и насколько можно доверять данным, участвующим в запросе. Самое простое – найти все места, где выполняются предложения SQL, и посмотреть, производится ли конкатенация или подстановка небезопасных данных, взятых, например, из строки Web–запроса, из Web–формы или аргумента SOAP. Вообще, любых поступающих от пользователя данных!
Тестирование
Надо признать, что реальной альтернативы добросовестному анализу кода на предмет внедрения SQL не существует. Но иногда у вас может не быть доступа к коду, или вы просто не имеет опыта чтения чужих программ. Тогда дополните анализ кода тестированием.
Прежде всего определите все точки входа в приложение, где формируются SQL–запросы. Затем создайте тестовую программу–клиент, которая будет посылать в эти точки частично некорректные данные. Например, если тестируется Web–приложение, которое строит запрос на основе одного или нескольких полей формы, попробуйте вставить в них произвольные ключевые слова языка SQL. Следующий пример на Perl показывает, как это можно сделать.
...#!/usr/bin/perl
use strict;
use HTTP::Request::Common qw(POST GET);
use HTTP::Headers;
use LWP::UserAgent;
srand time;
# Приостановить исполнение, если найдена ошибка
my $pause = 1;
# Тестируемый URL
my $url = 'http://mywebserver.xyzzy123.com/cgi-bin/post.cgi
# Максимально допустимый размер HTTP-ответа
my $max_response = 1000;
# Допустимые города
my @cities = qw(Auckland Seattle London Portland Manchester Redmond
Brisbane Ndola);while (1) {
my $city = randomSQL($cities[rand @cities]);
my $zip = randomSQL(10000 + int(rand 89999));print «Пробую [$city] и [zip]n»;
my $ua = LWP::UserAgent->new();
my $req = POST $url,
[ City => $city,
ZipCode => $zip,
];
# Послать запрос, получить ответ и поискать в нем признаки ошибки
my $res = $ua->request($req);
$_ = $res->as_string;
die "Хост недостижимn" if /bad hostname/ig;
if ($res->status_line != 200
|| /error/ig
|| length($_) > $max_response) {
print "nПотенциальная возможность внедрения SQLn";
print;
getc if $pause;
}
}# Выбрать случайное ключевое слово SQL, в 50% случаев перевести
# его в верхний регистр
sub randomSQL() {
$_ = shift;return $_ if ($rand > .75);
my @sqlchars = qw(1=1 2>1 «fred=»fre" + "d" or and select union drop update insert into dbo < > = ( ) ' .. – #);
my $sql = $sqlchars[rand @sqlchars]; $sql = uc($sql) id rand > .5;
return $_ . ' ' . $sql if rand > .9;
return $sql . ' ' . $_ if rand > .9;
return $sql;
}Этот код обнаружит возможность внедрения SQL только в случае, когда приложение возвращает сообщение об ошибке. Как мы уже сказали, нет ничего лучше тщательного анализа кода. Другой способ тестирования заключается в том, чтобы воспользоваться приведенной выше Perl–программой, заранее выяснить, как выглядит нормальный ответ, а затем анализировать, на какие запросы получен ответ, отличающийся от правильного, или вообще нет никакого ответа.
Имеются также инструменты третьих фирм, например AppScan компании Sanctum (теперь Watchfire) (www.watchfire.com), Weblnspect компании SPI Dynamics (www.spidynamics.com) и ScanDo компании Kavado (www.kavado.com).
Для оценки инструмента рекомендуем создать небольшое тестовое приложение с известными ошибками, допускающими внедрение SQL, и посмотреть, какие ошибки этот инструмент сумеет найти.