Текст книги "19 смертных грехов, угрожающих безопасности программ"
Автор книги: Майкл Ховард
Жанр: Зарубежная компьютерная литература, Зарубежная литература
сообщить о неприемлемом содержимом
Текущая страница: 9 (всего у книги 26 страниц) [доступный отрывок для чтения: 9 страниц]
В следующем примере оператор new не возбуждает исключения, поскольку вы явно запретили компилятору это делать! Если внутри new возникнет ошибка, а вы попробуете воспользоваться переменной р, беды не миновать.
try {
struct BigThing { double _d[16999]; };
BigThing *p = new (std::nothrow) BigThing[14999];
// воспользуемся p
} catch(std::bad_alloc& err) {
// обработать ошибку
}
В примере ниже программа ожидает исключения std::bad_alloc, но работает с библиотекой Microsoft Foundation Classes, в которой оператор new возбуждает исключение CMemory Exception:
Греховность C#, VB.NET и Javatry {
CString str = new CString(szSomeReallyLongString);
// воспользуемся str
} catch(std::bad_alloc& err) {
// обработать ошибку
}
На примере показанного ниже псевдокода демонстрируется, как не следует обрабатывать исключения. Здесь перехватываются все возможные исключения, а это, как и приведенный выше пример Windows SEH, может замаскировать ошибки.
try {
// (1) Загрузить XML-файл с диска
// (2) Извлечь из XML-данных URI
// (3) Открыть хранилище клиентских сертификатов и достать оттуда
// сертификат в формате X.509 и закрытый ключ клиента
// (4) Выполнить запрос на аутентификацию к серверу, определенному
// на шаге (2), используя сертификат и ключ из шага (3)
} catch (Exception e) {
// Обработать все возможные ошибки,
// включая и те, о которых я ничего не знаю
}
Упомянутые в этом примере функции могут возбуждать самые разнообразные исключения. Если речь идет о каркасе .NET, то к ним относятся: SecurityEx–ception, XmlException, IOException, ArgumentException, ObjectDisposedExcep–tion, NotSupportedException, FileNotFoimdException и SocketException. Ваша часть программы действительно знает, как все их корректно обработать?
Не поймите меня неправильно. Иногда перехват всех исключений – вещь совершенно нормальная, только убедитесь, что вы понимаете то, что делаете.
Родственные грехиЭтот грех стоит особняком, никакие другие с ним не связаны. Впрочем, первая его разновидность обсуждается более подробно в грехе 13.
Где искать ошибку
Так просто и не скажешь, нет характерных признаков. Самый эффективный способ – провести анализ кода.
Выявление ошибки на этапе анализа кода
Обращайте особое внимание на следующие конструкции:
Тестирование
Как отмечено выше, лучший способ обнаружить проявления греха заключается в анализе кода. Тестирование затруднительно, поскольку предполагается, что вы должны заставить функцию систематически возвращать ошибку. С точки зрения экономичности и затраченных усилий анализ кода – это самое дешевое средство.
Существуют некоторые инструменты, аналогичные lint, которые обнаруживают отсутствующие проверки кода возврата.
Примеры из реальной жизни
Следующий пример взят из базы данных CVE (http://cve.mitre.org).
CAN–2004–0077 do_mremap в ядре LinuxЭто, наверное, самая известная в недавней истории ошибка из разряда «забыл проверить возвращенное значение». Из–за нее были скомпрометированы многие Linux–машины, подключенные к сети Интернет. Обнаружившие ее люди подняли шумиху в прессе, а пример эксплойта можно найти по адресу http://isec.pl/ vulnerabilities/isec–0014–mremap–unmap.txt.
Примечание. В конце 2003 – начале 2004 года в менеджере памяти, являющемся частью ядра Linux, был обнаружен целый ряд ошибок, в том числе две, относящиеся к теме этой главы. Не путайте эту ошибку с другой, касающейся механизма отображения адресов: CAN–2003–0985.
Искупление греха
Искупить грех можно, лишь выполняя следующие предписания:
□ Обрабатывайте в своем коде все относящиеся к делу исключения.
□ Не «глотайте» исключения.
□ Проверяйте возвращаемые значения, когда это необходимо.
Искупление греха в C/C++В следующем фрагменте мы вместо использования макросов assert явно проверяем все аргументы функции и значение, возвращенное fopen.
Утверждения (assert) следует применять лишь для проверки условий, которые никогда не должны встречаться.
DWORD OpenFileContents(char *szFileName) {
if (szFileName == NULL || strlen(szFileName) <= 3)
return ERROR_BAD_ARGUMENTS;
FILE *f = fopen(szFileName, "r");
if (f == NULL)
return ERROR_FILE_NOT_FOUND;
// Можно работать с файлом
return 1;
}
Включенная в Microsoft Visual Studio .NET 2005 технология аннотирования исходного текста (Source code Annotation Language – SAL) помогает в числе прочих обнаружить и ошибки, связанные с проверкой возвращаемых значений. При компиляции показанного ниже кода будет выдано предупреждение:
«Warning С6031: return value ignored: «Function» could return unexpected value».
(Предупреждение C6031: возвращенное значение проигнорировано. Функция могла вернуть неожиданное значение.)
Искупление греха в C#, VB.NET и Java__checkReturn DWORD Function(char *szFileName) {
DWORD dwErr = NO_ERROR;
// Выполнить, что положено
return dwErr;
}
void main() {
Function("c:\junk\1.txt");
}
Следующий псевдокод обрабатывает только те ошибки, о которых знает, и ничего более:
try {
// (1) Загрузить XML-файл с диска
// (2) Извлечь из XML-данных URI
// (3) Открыть хранилище клиентских сертификатов и достать оттуда
// сертификат в формате X.509 и закрытый ключ клиента
// (4) Выполнить запрос на аутентификацию к серверу, определенному
// на шаге (2), используя сертификат и ключ из шага (3)
} catch (SecurityException e1) {
// обработать ошибки, относящиеся к безопасности
} catch (XmlException e2) {
// обработать ошибки, относящиеся к XML
} catch (IOException e3) {
// обработать ошибки ввода/вывода
} catch (FileNotFoundException e4) {
// обработать ошибки, связанные с отсутствием файла
} catch (SocketException e5) {
// обработать ошибки, относящиеся к сокетам
}
Другие ресурсы
□ Code Complete, Second Edition by Steve McConnell, Chapter 8, «Defensive Programming»
□ «Exception Handling in Java and C#» by Howard Gilbert: http://pclt.cis. yale.edu/ pclt/exceptions.htm
□ Linux Kernel mremap() Missing Return Value Checking Privilege Escalation www.osvdb/displayvuln.php?osvdb_id=3986
Резюме
Рекомендуется
□ Проверяйте значения, возвращаемые любой функцией, относящейся к безопасности.
□ Проверяйте значения, возвращаемые любой функцией, которая изменяет параметры, относящиеся к конкретному пользователю или машине в целом.
□ Всеми силами постарайтесь восстановить нормальную работу программы после ошибки, не допускайте отказа от обслуживания.
Не рекомендуется
□ Не перехватывайте все исключения без веской причины, поскольку таким образом можно замаскировать ошибки в программе.
□ Не допускайте утечки информации не заслуживающим доверия пользователям.
Грех 7.
Кросс–сайтовые сценарии
В чем состоит грех
Ошибки, связанные с кросс–сайтовыми сценариями (cross–site scripting – XSS), специфичны только для Web–приложений. В результате пользовательские данные, привязанные к домену уязвимого сайта (обычно хранящиеся в куке), становятся доступны третьей стороне. Отсюда и термин «кросс–сайтовый»: кук передается с компьютера клиента, который обращается к уязвимому сайту, на сайт, выбранный противником. Это самая распространенная XSS–атака. Но есть и другая разновидность, напоминающая атаку с изменением внешнего облика сайта; мы поговорим и о ней тоже.
Примечание. Ошибки, связанные с XSS–атаками, называют еще CSS–ошибками, но предпочтение отдается аббревиатуре XSS, так как CSS обычно расшифровывается как Cascade Style Sheets (каскадные таблицы стилей).
Подверженные греху языки
Уязвим любой язык или технология, применяемые для создания Web–сайтов, например PHP, Active Server Pages (ASP), C#, VB.NET, J2EE QSP, сервлеты), Perl и CGI (Common Gateway Interface – общий шлюзовой интерфейс).
Как происходит грехопадение
Согрешить очень легко: Web–приложение принимает от пользователя какие–то данные, например, в виде строки запроса, и, не проверяя их, выводит на страницу. Вот и все! Но входные данные могут оказаться сценарием, написанным, например, на языке JavaScript, и он будет интерпретирован браузером, на котором эта страница просматривается.
Как видите, это классическая проблема доверия. Приложение рассчитывает получить в строке запроса некоторый текст, скажем, имя пользователя, а противник подсовывает то, чего разработчик никак не ожидал.
XSS–атака организована следующим образом:
1) противник находит сайт, в котором есть одна или несколько XSS–ошибок, например в результате эхо–копирования сервером строки запроса;
2) противник подготавливает специальную строку запроса, включающую некоторую HTML–разметку и сценарий, например на языке JavaScript;
3) противник намечает жертву и убеждает ее щелкнуть по ссылке, содержащей злонамеренную строку запроса. Это может быть ссылка на какой–то другой Web–странице или в письме, отформатированном в виде HTML;
4) жертва щелкает по ссылке, и ее браузер отправляет уязвимому серверу GET–запрос, содержащий злонамеренную строку;
5) уязвимый сервер отправляет эту строку назад браузеру жертвы, и браузер исполняет содержащийся в ней сценарий.
Поскольку сценарий исполняется на компьютере жертвы, он может получить доступ к хранящимся на нем кукам, которые относятся к домену уязвимого сервера. Кроме того, сценарий может манипулировать объектной моделью документа (Document Object Model – DOM) и изменить в ней произвольный элемент, например переадресовать все ссылки на порносайты. Теперь, щелкнув по любой ссылке, жертва окажется в некоей точке киберпространства, куда вовсе не собиралась попадать.
Примечание. XSS–ошибка возможна и тогда, когда выходная информация невидима, вполне достаточно любого копирования входных данных. Например, Web–сервер мог бы передать входные данные в виде аргумента корректному JavaScript–сценарию на странице или использовать их как часть имени графического файла в теге <IMG>.
Опасайтесь таких Web–приложений, как блоги (онлайновые дневники) или страницы обратной связи, поскольку они зачастую принимают от пользователя произвольный HTML–код, а затем выводят его на страницу для всеобщего обозрения. Если приложение написано без учета безопасности, это может стать причиной XSS–атаки.
Рассмотрим примеры.
Греховное ISAPI–расширение или фильтр на C/C++Ниже приведен фрагмент ISAPI–расширения, которое читает строку запроса, добавляет в начало слово «Hello,» и возвращает результат браузеру. В этом коде есть и другая ошибка с куда более серьезными последствиями, чем XSS–атака. Сможете ли вы ее найти? Взгляните на обращение к функции sprintf(). В ней может произойти переполнение буфера (грех 1). Если результирующая строка окажется длиннее 2048 байтов, то буфер szTemp переполнится.
Греховность ASPDWORD WINAPI HttpExtensionProc (EXTENSION_CONTROL_BLOCK *lpEcb){
char szTemp [2048];
...
if (*lpEcb->lpszQueryString)
sprintf(szTemp,"Hello, %s", lpEcb->lpszQueryString);
dwSize = strlen(szTemp);
lpEcb->WriteClient(lpEcb->ConnId, szTemp, &dwSize, 0);
...
}
Эти примеры почти не требуют комментариев. Отметим лишь, что <%= (во втором фрагменте) – это то же самое, что Response.Write.
<% Response.Write(Request.QueryString(«Name»)) %>
Или
Греховность форм ASP. NET<img src='<%= Request.QueryString(«Name») %>'>
В этом примере ASP.NET трактует Web–страницу как форму, из элементов которой можно считывать данные (и записывать тоже), как если бы это была обычная форма Windows. В таком случае найти XSS–ошибку может оказаться не так просто, поскольку запрос и ответ неявно разбираются и формируются ASP.NET во время выполнения.
Греховность JSPprivate void btnSubmit_Click(object sender, System.EventArgs e) {
if (IsValid) {
Application.Lock();
Application[txtName.Text] = txtValue.Text;
Application.UnLock();
lblName.Text = "Hello, " + txtName.Text;
}
}
Эти примеры мало чем отличаются от примеров для ASP.
<% out.println(request.getParameter(«Name»)) %>
Или
Греховность PHP<%= request.getParameter («Name») %>
Приведенный ниже код читает из строки запроса значение в переменную name, а затем копирует его в ответ:
Греховность Perl–модуля CGI.pm<?php
$name=$_GET['name'];
if (isset($name)) {
echo "Hello $name";
}
?>
Этот код почти не отличается от примера РНР выше.
Греховность mod–perl#!/usr/bin/perl
use CGI;
use strict;
my $cgi = new CGI;
print CGI::header();
my $name = $cgi->param('name');
print "Hello, $name";
При использовании mod–perl для вывода HTML–разметки нужно написать чуть больше текста. Но если не считать кода, формирующего заголовки, то это практически то же самое, что приведенные выше примеры на РНР и Perl–CGI.
#!/usr/bin/perl
use Apache::Util;
use Apache::Request;
use strict;
my $apr = Apache::Request->new(Apache->request);
my $name = $apr->param('name');
$apr->content_type('text/html');
$apr->send_http_header;
$apr->print("Hello ");
$apr->print($name);
Где искать ошибку
Любое приложение, обладающее перечисленными ниже признаками, уязвимо для атаки с кросс–сайтовым сценарием:
□ Web–приложение принимает данные из строки запроса, заголовка или формы;
□ приложение не проверяет корректность данных;
□ приложение отправляет принятые данные назад браузеру.
Выявление ошибки на этапе анализа кода
При анализе кода на предмет наличия XSS–ошибок обращайте внимание на места, где используется тот или иной объект запроса, а прочитанные из него данные копируются в объект ответа. Автор этой главы обычно ищет такие конструкции:
Выяснив, где производятся ввод и вывод, проверьте, контролируется ли корректность входных данных. Если нет, возможна XSS–ошибка.
Примечание. Данные могут не копироваться непосредственно из объекта запроса в объект ответа, возможно промежуточное сохранение в базе данных; обращайте внимание и на это тоже.
Хотелось бы отметить еще один важный момент. Многие полагают, что обращение к методу Response.Write и его аналогам – это единственный источник XSS–ошибок. На самом деле обнаружилось, что конструкции Response.Redirect и Response.SetCookie могут приводить к таким же последствиям; это получило название атаки с расщеплением HTTP–ответа. Вывод таков: любое копирование входных данных в выходные без проверки корректности – это ошибка, угрожающая безопасности. В разделе «Другие ресурсы» приведены ссылки на дополнительные материалы, относящиеся к уязвимостям из–за расщепления HTTP–ответа.
Тестирование
Простейший способ протестировать наличие XSS–ошибок – отправить запрос своему Web–приложению, задав всем входным параметрам заведомо небезопасные значения. Затем взгляните на полученный от сервера ответ, не ограничивайтесь только визуальным представлением. Изучите весь поток байтов, чтобы понять, вошли ли в ответ посланные вами данные. Если это так, ваш код может быть уязвим для XSS–атаки. Вот простой Perl–сценарий, который можно положить в основу теста:
#!/usr/bin/perl
use HTTP::Request::Common qw(POST GET);
use LWP::UserAgent;
# Сформировать заголовок, описывающий агента
my $ua = LWP::UserAgent->new();
$ua->agent("XSSInject/v1.40");
# Строки, содержащие внедряемый сценарий
my @xss = ('><script>alert(window.location);</script>'',
'"; alert(document.cookie);',
'' onmouseover='alert(document.cookie);' '',
'"><script>alert(document.cookie);</script>',
'"></a><script>alert(document.cookie);</script>',
'xyzzy');
# Построить запрос
my $url = "http://127.0.01/form.asp";
my $inject;
foreach $inject (@xss) {
my $req = POST $url, [Name => $inject,
Address => $inject,
Zip => $inject];
my $res = $ua->request($req);
# Получить ответ
# Если мы увидим внедренный сценарий, возможна проблема
$_ = $res->as_string;
print "Возможна XSS-ошибка [$url]n" if index(lc $_,lc $inject != -1);
}
Примеры из реальной жизни
Следующие примеры XSS–уязвимостей взяты из базы данных CVE (cve.mitre.org).
Уязвимость IBM Lotus Domino для атаки с кросс–сайтовым сценарием и внедрением HTMLПо какой–то причине этому бюллетеню не присвоен номер в базе данных CVE. Противник может обойти HTML–кодирование в вычисляемом Lotus Notes значении, добавив квадратные скобки («[" и "]») в начало и конец поля для некоторых типов данных. Подробности на странице www.securityfocus.eom/bid/l 1458.
Ошибка при контроле входных данных в сценарии isqlplus, входящем в состав Oracle HTTP Server, позволяет удаленному пользователю провести атаку с кросс–сайтовым сценариемИ на этот раз у бюллетеня отсутствует номер. Oracle HTTP Server основан на сервере Apache 1.3.x. В сценарии isqlplus есть XSS–ошибка, связанная с недостаточным контролем значений параметров «action», «username» и «password». Атака может выглядеть примерно так:
http://[target]isqlplus?action=logon&username=xyzzy%22%3e%3cscript%3e
alert('XSS')%3c/script%3e&password=xyzzy%3cscript%3ealert('XSS')%3c
/script%3e
Подробности на странице www.securitytracker.com/alerts/2004/Jan/1008838.html.
CVE–2002–0840XSS–уязвимость на стандартной странице с сообщением об ошибке в Apache 2.0 до версии 2.0.43 и в Apache 1.3.x до версии 1.3.26. Подробности на странице http://cert.uni–stuttgart.de/archive/bugtraq/2002/10/msg00017.html.
Искупление греха
Путь к искуплению состоит из двух шагов:
1) не допускайте некорректных входных данных. Для проверки обычно применяются регулярные выражения;
2) при выводе данных применяйте HTML–кодирование.
Необходимо принимать обе эти меры предосторожности в своих программах. В приведенных ниже примерах показано, как это делается на практике.
Искупление греха в ISAPI–расширениях и фильтрах на C/C++Во фрагменте ниже приведен код для HTML–кодирования информации, отправляемой браузеру.
/////////////////////////////////////////////////////////////////
// HtmlEncode
// Кодирует поток HTML-данных
// Аргументы
// strRaw: указатель на необработанные HTML-данные
// result: ссылка на результат, хранящийся в std::string
// Возвращаемое значение:
// false: не удалось закодировать HTML-данные
// true: все HTML-данные закодированы
bool HtmlEncode(char *strRaw, std::string &result) {
size_t iLen = 0;
size_t i = 0;
if (strRaw && (iLen = strlen(strRaw))) {
for (i = 0; i < iLen; i++)
switch(strRaw[i]) {
case ' ' : break;
case '<' : result.append("<"); break;
case '>' : result.append(">"); break;
case '(' : result.append("("); break;
case ')' : result.append(")"); break;
case '#' : result.append("#"); break;
case '&' : result.append("&"); break;
case '"' : result.append("""); break;
default : result.append(1,strRaw[i]); break;
}
}
return i == iLen ? true : false;
}
Если вы хотите пользоваться регулярными выражениями в программах на C/C++, то обратите внимание на класс CAtlRegExp, предлагаемый Microsoft, или на библиотеку Boost.Regex, документированную на странице http://boost.org/ libs/regex/doc/syntax.html.
Искупление греха в ASPПрименяйте сочетание регулярных выражений (в данном случает объект RegExp в сценарии на VBScript) и HTML–кодирования для проверки входных данных:
Искупление греха в ASP. NET<%
name = Request.QueryString("Name")
Set r = new ReqExp
r.Pattern = "^w{5,25}$"
r.IgnoreCase = True
Set m = r.Execute(name)
If (len(m(0)) > 0) Then
Response.Write(Server.HTMLEncode(name))
End If
%>
Приведенный ниже код аналогичен предыдущему примеру, но для сопоставления с регулярным выражением и HTML–кодирования используется язык С# и библиотеки, входящие в каркас .NET Framework.
Искупление греха в JSPusing System.Web; // Необходимо добавить ссылку на сборку System.Web.dll
...
private void btnSubmit_Click(object sender, System.EventArgs e)
{
Regex r = new Regex(@"^w{5,25}");
if (r.Match(txtValue.Text).Success) {
Application.Lock();
Application.txtName.Text = txtValue.Text;
Application.UnLock();
lblName.Text = "Hello, " +
HttpUtility.HtmlEncode(txtName.Text);
} else {
lblName.Text = "Кто вы?";
}
}
В JSP имеет смысл использовать нестандартный тег. Вот код тега, осуществляющего HTML–кодирование:
import java.IO.Exception;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;
public class HtmlEncoderTag extends BodyTagSupport {
public HtmlEncoderTag() {
super();
}
public int doAfterBody() throws JspException {
if (bodyContent != null) {
System.out.println(bodyContent.getString());
String contents = bodyContent.getString();
String regExp = new String("^\w{5,25}$");
// Сопоставить с регулярным выражением
if (contents.matches(regExp)) {
try {
bodyContent.getEnclosingWriter().write(contents);
} catch (IOException e) {
System.out.println("Ошибка ввода/вывода");
}
return EVAL_BODY_INCLUDE;
} else {
try {
bodyContent.getEnclosingWriter().write(encode(contents));
} catch (IOException e) {
System.out.println("Ошибка ввода/вывода");
}
System.out.println("Содержимое: " + contents.toString());
return EVAL_BODY_INCLUDE;
}
} else {
return EVAL_BODY_INCLUDE;
}
}
// В JSP нет функции для HTML-кодирования
public static String encode(String str) {
if (str == null)
return null;
StringBuffer s = new StringBuffer();
for (short i = 0; i < str.length(); i++) {
char c = str.CharAt(i);
switch (c) {
case '<':
s.append("<");
break;
case '>':
s.append(">");
break;
case '(':
s.append("(");
break;
case ')':
s.append(")");
break;
case '#':
s.append("#");
break;
case '&':
s.append("&");
break;
case '"':
s.append(""");
break;
default:
s.append(c);
}
}
return s.toString();
}
}
Ну и наконец пример JSP)страницы, из которой вызывается определенный выше тег:
<%@ taglib uri="/tags/htmlencoder" prefix="htmlencoder" %>
<head>
<title>Покайся, грешник...</title>
</head>
<html>
<body bgcolor="white">
<htmlencoder:htmlencode><script
type="javascript">BadStuff()</script></htmlencoder:htmlencode>
<htmlencoder:htmlencode>testing</htmlencoder:htmlencode>
<script type="badStuffNotWrapped()"></script>
</body>
</html>
Внимание! Это не конец книги.
Если начало книги вам понравилось, то полную версию можно приобрести у нашего партнёра - распространителя легального контента. Поддержите автора!Правообладателям!
Данное произведение размещено по согласованию с ООО "ЛитРес" (20% исходного текста). Если размещение книги нарушает чьи-либо права, то сообщите об этом.Читателям!
Оплатили, но не знаете что делать дальше?