Текст книги "19 смертных грехов, угрожающих безопасности программ"
Автор книги: Майкл Ховард
Жанр: Зарубежная компьютерная литература, Зарубежная литература
сообщить о неприемлемом содержимом
Текущая страница: 3 (всего у книги 26 страниц) [доступный отрывок для чтения: 9 страниц]
Из CVE–2002–0842:
Ошибка при работе с форматной строкой в одной модификации функции mod_dav, применяемой для протоколирования сообщений о плохом шлюзе (например, Oracle9i Application Server 9.0.2), позволяет удаленному противнику выполнить произвольный код, обратившись к URI, для которого сервер возвращает ответ «502 BadGateway». В результате функция dav_lookup_uri() в файле mod_dav.c возвращает спецификаторы форматной строки, которые потом используются при вызове ap_log_rerror().
Из CVE–2003–0095:
Переполнение буфера в программе ORACLE.EXE для Oracle Database Server 9i, 8i, 8.1.7 и 8.0.6 позволяет удаленному противнику выполнить произвольный код, задав при входе длинное имя пользователя. Ошибкой можно воспользоваться из клиентских приложений, самостоятельно выполняющих аутентификацию, что и продемонстрировано на примере LOADPSP.
Из CAN–2003–0096:
Многочисленные ошибки переполнения буфера в Oracle 9i Database Release 2, Release 1, 8i, 8.1.7 и 8.0.6 позволяют удаленному противнику выполнить произвольный код, (1) задав длинную строку преобразования в качестве аргумента функции TO_TIMESTAMP_TZ, (2) задав длинное название часового пояса в качестве аргумента функции TZ_OFFSET и (3) задав длинную строку в качестве аргумента DIRECTORY функции BFILENAME.
Эти ошибки документированы в бюллетене CERT СА–2003–05 по адресу www.cert.org/advisories/CA–2003–05.html. Их – в числе прочих – обнаружил Дэвид Литчфилд со своими сотрудниками из компании Next Generation Security Software Ltd. Попутно отметим, что не стоит объявлять свои приложения «не–взламываемыми», если за дело берется г–н Литчфилд.
CAN–2003–0352Из описания в CVE:
Переполнение буфера в одном интерфейсе DCOM, используемом в системе RPC, применяемой в Microsoft Windows NT 4.0,2000, ХР и Server 2003, позволяет удаленному противнику выполнить произвольный код, сформировав некорректное сообщение. Этой ошибкой воспользовались черви Blaster/MSblast/LovSAN and Nachi/Welchia.
Это переполнение интересно тем, что привело к широкому распространению двух весьма разрушительных червей, что повлекло за собой глобальные неприятности в сети Интернет. Переполнялся буфер в куче, доказательством возможности эксплуатации ошибки стало появление очень стабильного червя. Ко всему прочему еще был нарушен принцип предоставления наименьших привилегий, этот интерфейс не должен был быть доступен анонимным пользователям. Интересно еще отметить, что предпринятые в Windows 2003 контрмеры свели последствия атаки к отказу от обслуживания вместо эскалации привилегий.
Подробнее об этой проблеме можно прочитать на страницах www.cert.org/ advisories/CA–2003–23.html и www.microsoft.com/technet/security/bulletin/MS03–039.asp.
Искупление греха
Путь к искуплению греха переполнения буфера долог и тернист. Мы обсудим несколько способов избежать этой ошибки, а также ряд приемов, позволяющих сократить ущерб от нее. Посмотрите, как можно улучшить свои программы.
Замена опасных функций работы со строкамиКак минимум вы должны заменить небезопасные функции типа strcpy, strcat и sprintf их аналогами со счетчиком. Замену можно выбрать несколькими способами. Имейте в виду, что интерфейс старых вариантов функций со счетчиком оставляет желать лучшего, а кроме того, для задания параметров часто приходится заниматься арифметическими вычислениями. В грехе 3 вы увидите, что компьютеры не так хорошо справляются с математикой, как могло бы показаться. Из новых разработок стоит отметить функцию strsafe, Safe CRT (библиотеку времени исполнения для С), которая войдет в состав Microsoft Visual Studio (и очень скоро станет частью стандарта ANSI C/C++), и функции strlcat/strlcpy для *nix. Обращайте внимание на то, как каждая функция обрабатывает конец строки и усечение. Некоторые функции гарантируют, что строка будет завершаться нулем, но большинство старых функций со счетчиком таких гарантий не дают. Опыт группы разработки Microsoft Office по замене небезопасных функций работы со строками в Office 2003 показал, что коэффициент регресии (число новых ошибок в расчете на одно исправление) очень мал, так что пусть страх внести новые ошибки вас не останавливает.
Следите за выделениями памятиЕще одна причина переполнения буфера – это арифметические ошибки. Прочитайте в грехе 3 о переполнении целых чисел и найдите в своем коде все места, где для вычисления размера буфера производятся арифметические вычисления.
Проверьте циклы и доступ к массивамПереполнение возможно и в случае, когда неправильно проверяется условие выхода из цикла и не контролируется выход за границы массива перед записью в него. Это одна из самых трудных для обнаружения ошибок; вы увидите, что часто ошибка проявляется совсем не в том модуле, где была допущена.
Пользуйтесь строками в стиле С++, а не СЭто эффективнее простой замены стандартных С–функций, но может повлечь за собой кардинальную переработку кода, особенно если программа собиралась не компилятором С++. Вы должны хорошо представлять себе характеристики производительности STL–контейнеров. Вполне возможно написать очень эффективный код с использованием STL, но, как всегда, нежелание читать руководство (RTFM – Read The Fine Manual) может привести к коду, далекому от оптимального. Наиболее типичное усовершенствование такого рода – переход к использованию шаблонных классов std::string или std::wstring.
Пользуйтесь STL–контейнерами вместо статических массивовВсе вышеупомянутые проблемы относятся и к STL–контейнерам, например vector, но ситуация осложняется еще и тем, что не все реализации класса vector::iterator контролируют выход за границы контейнера. Указанная в заголовке мера может помочь, и автор полагает, что STL способствует быстрому написанию правильного кода, но имейте в виду, что это все же не панацея.
Пользуйтесь инструментами анализаНа рынке появляются прекрасные инструменты для анализа кода на языках C/C++ на предмет нарушения безопасности, в том числе Coverity, PREfast и Kloc–work. В разделе «Другие ресурсы» приведены ссылки. В состав Visual Studio .NET 2005 войдет программа PREfast и еще один инструмент анализа кода под названием Source code Annotation Language (SAL – язык аннотирования исходного текста), позволяющий обнаружить такие дефекты, как переполнение буфера. Проще всего проиллюстрировать SAL на примере. В показанном ниже фрагменте (примитивном) вы знаете, как соотносятся аргументы data и count: длина data составляет count байтов. Но компилятору об этом ничего не известно, он видит только типы char * and a size_t.
void *DoStuff(char *data, size_t count) {
static char buf[32];
return memcpy(buf, data, count);
}
На первый взгляд, код не содержит ничего плохого (если не считать, что мы возвращаем указатель на статический буфер, ну посмейтесь над нами). Однако если count больше 32, то возникает переполнение буфера. Если бы этот код был аннотирован с помощью SAL, то компилятор мог бы отловить эту ошибку:
void *DoStuff(__in_ecount(count) char *data, size_t count) {
static char buf[32];
return memcpy(buf, data, count);
}
Объясняется это тем, что теперь компилятор и/или PREfast знают о тесной связи аргументов data и count.
Дополнительные защитные меры
Дополнительные защитные меры – это как ремни безопасности в автомобиле. Часто они смягчают последствия столкновения, но в аварию попадать все равно не стоит. Важно понимать, что для всех основных способов минимизировать эффект от переполнения буфера условия, которые приводят к переполнению буфера, продолжают существовать, поэтому достаточно изощренная атака может обойти ваши контрмеры. Все же рассмотрим некоторые подходы.
Защита стекаЗащиту стека первым применил Криспин Коуэн в своей программе Stack–guard, затем она была независимо реализована Microsoft с помощью флага компилятора /GS. В самом простом варианте суть ее состоит в том, что в стек между локальными переменными и адресом возврата записывается некое значение. В более современных реализациях для повышения эффективности может также изменяться порядок переменных. Достоинство этого подхода в том, что он легко реализуется и почти не снижает производительности, а кроме того, облегчает отладку в случае порчи стека. Другой пример – это программа ProPolice, созданная компанией IBM как расширение компилятора Gnu Compiler Collection (GCC). Любой современный продукт должен включать в себя защиту стека.
Запрет исполнения в стеке и кучеЭта контрмера существенно усложняет задачу хакера, но может сказаться на совместимости. Некоторые приложения считают допустимым компилировать и исполнять код на лету. Например, это относится к языкам Java и С#. Важно также отметить, что если противник сумеет заставить ваше приложение пасть жертвой атаки с возвратом в libc, когда для достижения неблаговидных целей выполняется вызов стандартной функции, то он сможет снять защиту со страницы памяти.
К сожалению, современная аппаратура по большей части не поддерживает эту возможность, а имеющаяся подержка зависит от процессора, операционной системы и даже ее конкретной версии. Поэтому рассчитывать на наличие такой защиты в реальных условиях не стоит, но все равно следует протестировать свою программу и убедиться, что она будет работать, когда стек и куча защищены от исполнения. Для этого нужно запустить ее на том процессоре и операционной системе, которые поддерживают аппаратную защиту. Например, если целевой платформой является Windows ХР, то прогоните тесты на машине с процессором AMD Athlon 64 FX под управлением Windows ХР SP2. В Windows эта технология называется Data Execution Protection (DEP – защита от исполнения данных), а раньше носила имя No eXecute (NX).
ОС Windows Server 2003 SP1 также поддерживает эту возможность, равно как подсистема РаХ для Linux и OpenBSD.
Другие ресурсы
□ Writing Secure Code, Second Edition by Michael Howard and David C. LeBlanc (Microsoft Press, 2002), Chapter 5, «Public Enemy #1: Buffer Overruns»
□ «Defeating the Stack Based Buffer Overflow Prevention Mechanism of Microsoft Windows Server 2003» by David Litchfield: www.ngssoftware.com/papers/ defeating–w2k3–stack–protection.pdf
□ «Non–stack Based Exploitation of Buffer Overrun Vulnerabilities on Windows NT/2000/ХР» by David Litchfield: www.ngssoftware.com/papers/non–stack–bo–windows.pdf
□ «Blind Exploitation of Stack Overflow Vulnerabilities» by Peter Winter–Smith: www.ngssoftware.com/papers/NISR.BlindExploitation.pdf
□ «Creating Arbitrary Shellcode In Unicode Expanded Strings: The 'Venetian' Exploit» by Chris Anley: www.ngssoftware.com/papers/unicodebo.pdf
□ «Smashing The Stack For Fun And Profit» by Alephl (Elias Levy): www.insecure.org/stf/smashstack.txt
□ «The Tao of Windows Buffer Overflow» by Dildog: www.cultdeadcow.com/ cDc_files/cDc–351/
□ Microsoft Security Bulletin MS04–011/Security Update for Microsoft Windows (835732): www.microsoft.com/technet/security/Bulletin/MS04–011 .mspx
□ Microsoft Application Compatibility Analyzer: www.microsoft.com/windows/ appcompatibility/analyzer.mspx
□ Using the Strsafe.h Functions: http://msdn.microsoft.com/library/en–us/winui/ winui/windowsuserinterface/resources/strings/usingstrsafefunctions.asp
□ More Secure Buffer Function Calls: AUTOMATICALLY!: http://blogs.msdn.com/michael_howard /archive/2005/2/3.aspx
□ Repel Attacks on Your Code with the Visual Studio 2005 Safe С and С++ Libraries: http://msdn.microsoft.com/msdnmag/issues/05/05/SafeCand С / default.aspx
□ «strlcpy and strlcat – Consistent, Safe, String Copy and Concatenation by Todd C. Miller and Theo de Raadt: www.usenix.org/events/usenix99/millert.html
□ GCC extension for protecting applications from stack–smashing attacks: www.trl. ibm.com/projects/security/ssp/
□ PaX: http://pax.grsecurity.net/
□ OpenBSD Security: www.openbsd.org/security.html
□ Static Source Code Analysis Tools for C: http://spinroot.com/static/
Резюме
Рекомендуется
□ Тщательно проверяйте любой доступ к буферу за счет использования безопасных функций для работы со строками и областями памяти.
□ Пользуйтесь встраиваемыми в компилятор средствами защиты, например флагом /GS и программой ProPolice.
□ Применяйте механизмы защиты от переполнения буфера на уровне операционной системы, например DEP и РаХ.
□ Уясните, какие данные контролирует противник, и обрабатывайте их безопасным образом.
Не рекомендуется
□ Не думайте, что компилятор и ОС все сделают за вас, это всего лишь дополнительные средства защиты.
□ Не используйте небезопасные функции в новых программах.
Стоит подумать
□ Об установке последней версии компилятора C/C++, поскольку разработчики включают в генерируемый код новые механизмы защиты.
□ О постепенном удалении небезопасных функций из старых программ.
□ Об использовании строк и контейнерных классов из библиотеки С++ вместо применения низкоуровневых функций С для работы со строками.
Грех 2.
Ошибки, связанные с форматной строкой
В чем состоит грех
С форматной строкой связан новый класс атак, появившихся в последние годы. Одно из первых сообщений на эту тему прислал Ламагра Аграмал (Lamagra Arga–mal) 23 июня 2000 года (www.securityfocus.com/archive/1/66842). Месяцем позже Паскаль Бушарен (Pascal Bouchareine) дал более развернутое пояснение (www.securityfocus.eom/archive/l/70552). В более раннем сообщении Марка Слемко (Mark Slemko) (www.securityfocus.com/archive/1 /10383) были описаны основные признаки ошибки, но о возможности записывать в память речи не было.
Как и в случае многих других проблем, относящихся к безопасности, суть ошибки в форматной строке заключается в отсутствии контроля данных, поступающих от пользователя. В программе на C/C++ такая ошибка позволяет произвести запись по произвольному адресу в памяти, а опаснее всего то, что при этом необязательно затрагиваются соседние блоки памяти. В результате противник может обойти защиту стека и модифицировать очень небольшие участки памяти. Проблема может возникнуть и тогда, когда форматная строка читается из не заслуживающего доверия источника, контролируемого противником, но это свойственно скорее системам UNIX и Linux. В Windows таблицы строк обычно хранятся внутри исполняемого файла или в динамически загружаемых библиотеках ресурсов (ресурсных DLL). Если противник может изменить основной исполняемый файл или ресурсную DLL, то он способен провести прямолинейную атаку, и не эксплуатируя ошибки в форматной строке.
Но и в программах на других языках атаки на форматную строку могут стать источником серьезных неприятностей. Самая очевидная заключается в том, что пользователь не понимает, что происходит, однако при некоторых условиях противник может организовать атаку с кросс–сайтовым сценарием или внедрением SQL–команд, тем самым запортив или модифицировав данные.
Подверженные греху языки
Самыми опасными в этом отношении являются языки С и С++. Успешная атака приводит к исполнению произвольного кода и раскрытию информации. В программах на других языках произвольный код обычно выполнить не удается, но, как отмечено выше, возможны другие виды атак. С программой на Perl ничего не случится, если пользователь подсунет спецификаторы формата, но она может стать уязвимой, когда форматные строки считываются из ненадежного источника данных.
Как происходит грехопадение
Форматирование данных для вывода или хранения – это довольно сложное дело. Поэтому во многих языках программирования есть средства для решения этой задачи. Как правило, формат описывается так называемой форматной строкой. По существу, это мини–программа на очень специализированном языке, предназначенном исключительно для описания формата выходных данных. Однако многие разработчики допускают примитивную ошибку – позволяют задавать форматную строку пользователям, не заслуживающим доверия. В результате противник может подсунуть такую строку, при работе с которой возникнут серьезные проблемы.
В программах на языке C/C++ это особенно рискованно, поскольку обнаружить сомнительные места в форматной строке очень сложно, а кроме того, форматные строки в этих языках могут содержать некоторые опасные спецификаторы (и прежде всего %п), отсутствующие в других языках.
В C/C++ можно объявить функцию с переменным числом аргументов, указав в качестве последнего аргумента многоточие (…). Проблема в том, что при вызове такая функция не знает, сколько аргументов ей передано. К числу наиболее распространенных функций с переменным числом аргументов относятся функции семейства printf: printf, sprintf, snprintf, fprintf, vprintf и т. д. Та же проблема свойственна функциям для работы с широкими символами. Рассмотрим пример:
#include <stdio.h>
int main(int argc, char* argv[])
{
if(argc > 1)
printf(argv[1]);
return 0;
}
Исключительно простая программа. Однако посмотрим, что может произойти. Программист ожидает, что пользователь введет что–то безобидное, например Hello World. В ответ будет напечатано то же самое: Hello World. Но давайте передадим программе в качестве аргумента строку %х %х. Если запустить эту программу в стандартном окне команд (cmd.exe) под Windows ХР, то получим:
E:projects19_sinsformat_bug>format_bug.exe «%x %x»
12ffc0 4011e5
В другой операционной системе или при использовании другого интерпретатора команд для ввода точно такой строки в качестве аргумента может потребоваться слегка изменить синтаксис, и результат, вероятно, тоже будет отличаться. Для удобства можете поместить аргументы в shell–сценарий или пакетный файл.
Что произошло□ Функции printf передана форматная строка, вместе с которой следовало бы передать еще два аргумента, то есть поместить их в стек перед вызовом функции. Встретив спецификатор %х, printf прочтет четыре байта из стека. Нетрудно представить себе, что при наличии более сложной функции, которая хранит в стеке некоторую секретную информацию, противник смог бы эту информацию распечатать. В данном же случае на выходе мы видим адрес кадра стека (0xl2ffc0), за которым следует адрес, по которому вернет управление функция main(). То и другое – важная информация, которую противник сумел несанционированно получить.
Теперь возникает вопрос: «Как противник может воспользоваться ошибкой при работе с форматной строкой для записи в память□» Существует довольно редко используемый спецификатор %п, который позволяет записать число выведенных к настоящему моменту байтов в переменную, адрес которой передан в качестве соответствующего ему аргумента. Вот предполагаемый способ его применения:
unsigned int bytes;
printf("%s%nn", argv[1], &bytes);
printf("Длина входных составляла %d символовn, bytes");
В результате было бы напечатано:
E:projects19_sinsformat_bug>format_bug2.exe «Some random input»
Some random input
Длина входных составляла 17 символов
На платформе, где длина целого составляет четыре байта, спецификатор %п выводит четыре байта, а спецификатор %hn – два байта. Противнику осталось только вычислить, какой адрес должен быть помещен в нужную позицию стека, а потом, манипулируя спецификаторами ширины, добиться, чтобы число выведенных байтов равнялось числовому значению нужного адреса.
Примечание. Более подробная демонстрация шагов, которые нужно предпринять для реализации такого эксплойта, приведена в главе 5 книги Michael Howard и David С. LeBlanc «Writing Secure Code, Second Edition» (Microsoft Press, 2002) или в книге Holesby Jack Koziol, David Litchfield, Dave Artel, Chris Anley, Sinan «noir» Eren, Neel Mehta and Riley Hassell «The Shellcoder's Handbook» (Справочник no shell–кодам) (Wiley, 2004).
Пока достаточно принять за аксиому, что если вы позволите противнику контролировать форматную строку в программе на C/C++, то рано или поздно он придумает, как заставить эту программу выполнить нужный ему код. Особенно неприятно, что перед запуском такой атаки противник может изучить содержимое стека и изменить направление атаки на лету. На самом деле в первый раз, когда автор демонстрировал эту атаку публично, ему попался не тот интерпретатор команд, на котором эксплойт разрабатывался, поэтому атака не сработала. Но вследствие удивительной гибкости этой атаки удалось исправить ошибку и взломать уязвимое приложение на глазах аудитории.
В большинстве других языков эквивалент спецификатора формата %п не поддерживается, поэтому напрямую противник не сможет таким образом выполнить код по своему выбору. Тем не менее проблемы все равно остаются, поскольку существуют более тонкие варианты этой атаки, перед которыми уязвимы и другие языки. Если противник может задать форматную строку для вывода в файл протокола или в базу данных, то сумеет сформировать некорректный или сбивающий с толку протокол. Кроме того, приложение, читающее протоколы, может считать их заслуживающими доверия, а если это предположение нарушается, то ошибки в синтаксическом анализаторе могут все же привести к исполнению произвольного кода. С этим связана и другая проблема – запись в файл протокола управляющих символов. Так, символ забоя можно использовать для стирания данных, а символы конца строки могут скрыть или даже уничтожить следы атаки.
Без слов понятно, что если противник может задать форматную строку, передаваемую функции scanf и ей подобным, то беда неминуема.
Правообладателям!
Данное произведение размещено по согласованию с ООО "ЛитРес" (20% исходного текста). Если размещение книги нарушает чьи-либо права, то сообщите об этом.Читателям!
Оплатили, но не знаете что делать дальше?