WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 | 2 || 4 | 5 |   ...   | 10 |

«Michael Howard David LeBlank WRITING SECURE CODE Second Edition Press Майкл Ховард Дэвид Лебланк ЗАЩИЩЕННЫЙ код 2-е издание, исправленное Москва 2004 УДК 004.45 ББК 32.973.26-018.2 Х68 ...»

-- [ Страница 3 ] --

Типы Методы Возникающие Опасность опасностей предотвращения проблемы пакета сразу стоит ли тратить на него время Ограничение списка Подробно об этом — отладчика к процессу учетных обла- в главе дающих привилегией доступ Т, R Физическая защита. Надежного решения злоумышленника важных к оборудованию данных и предотвраще ние размещения ключей в компьютерном оборудован иии процесса Обязательная аутенти- Требует фикация перед выполне- в коде проверок в сти.

нием администра- Windows NT. Чтобы тивных задач. Только как правильно членам группы локаль- определять членство ных администраторов в группах, обратитесь следует предоставлять к главе привилегию завершения процесса аутенти- Процесс подписи Изменение Т, R I, конфигурационных фикация всех соедине- данных требует данных ний, работающих времени и сложен с данными. для реализации на файлах и под держка подписей Сообщения Не предоставляйте пользовате взломщику слишком ли также получают об ошибках содер жит слишком много много сведений. Дайте малоинформативные которая краткие сведения сообщения об ошибка об ошибке, а полное что чревато ростом помогает злоумыш леннику реализовать описание занесите числа обращений в журнал в отдел технической атаки поддержки Запрет на Иногда На совместно исполь зуемых рабочих стан- важных данных, неудобство правомоч циях злоумышленник данных, пользователям доступ или пересылаемых пользо вателю по протоколам использует данные, оставшиеся в кэше SSL/TLS и от предыдущего пользователя Доступ и изменение Шифрование файлов, Сложно обеспечить сек например средствами ретность ключей данных маршрутиза рования.

ции на Web-сервере EFS. Надежная защита ключей шифрования EFS в домене более от атак пасно, чем на ванном компьютере Часть I Безопасность приложений сегодня Резюме Я уверен, что моделирование опасностей крайне важно при проектировании систем. Без построения модели невозможно выяснить, устране ны ли самые критичные опасности, грозящие приложению. Использование в при ложении всех случайных технологий обеспечения безопасности не сделает его защищенным: они могут не подойти или не справиться с задачей предотвраще ния опасностей. Я также уверен в что, потратив усилия и построив актуаль ные и точные модели опасностей, вы создадите более защищенные системы. Наш опыт показывает, что примерно половина изъянов в защите на этапе моделирования опасностей, так как при этом определяются те опасности, кото рые не заметны при прямом анализе кода.

Решение просто: соберите команду, выполните декомпозицию приложения (например, посредством определите грозящие системе опаснос ти при помощи деревьев опасностей и методики STRIDE, расположите опаснос ти по ранжиру с помощью такого как DREAD, а затем выберите методы борьбы с опасностями на основе категорий STRIDE, Ну и, наконец, модели опасностей это важный компонент процесса разра ботки надежной защиты. В Microsoft моделирование опасностей стала обязатель ной процедурой, через которую проходит каждое приложение до проектирования.

МЕТОДЫ БЕЗОПАСНОГО КОДИРОВАНИЯ переполнение буфера буфера и опасность этого известны давно. Проблемы с перепол нением буфера возникали еще в Один из самых известных примеров — «червь», написанный Робертом Т. Моррисом (Robert T. Morris) в На неко торое время он полностью парализовал работу так как администра торы отключали свои сети, пытаясь локализовать разрушение. Весной ботая над первым изданием этой книги, я выполнил поиск по словам buffer, security и bulletin в базе знаний Microsoft Knowledge и получил 20 ссылок, в основном на бюллетени, рассказывающие о дырах, делающих возможным удаленно повышать привилегии. Каждый подписчик рас сылки BugTraq имеет сомнительное удовольствие практически каждый день читать новые отчеты о возможности переполнения буфера в массе приложений, работающих под управлением самых разных ОС.

Как бы высоко вы ни оценили серьезность ошибок переполнения буфера, все равно ошибетесь в меньшую сторону. В Центре безопасности Microsoft (Microsoft Security Response Center) подсчитали, что выпуск одного бюллетеня вкупе с паке том исправлений обходится в 100 000 долларов, и это только «цветочки». Тысячи системных администраторов тратят кучу времени на установку пакетов исправ лений. Администраторам по безопасности приходится выявлять еще не обновлен ные системы и оповещать об этом их владельцев. Хуже всего то, что системы не которых клиентов все-таки становятся жертвами хакеров. Стоимость ущерба при этом может оказаться астрономической, особенно если взломщику удается глу боко проникнуть в систему и получить доступ к ценной информации, например к базе данных кредитных карточек. Одна крохотная оплошность с вашей сторо ны может обернуться миллионами долларов убытков, не говоря уже о том, что вы ГЛАВА 5 Враг буфера имя. В общем, последствия ужасны. Естественно, каждый но ошибки ошибкам рознь.

Основная причина переполнения буфера — плохой стиль кодирования (осо бенно это касается С и которые предоставляют массу возможностей прогр вырыть себе защищенных и простых в использовании стро ковых функций и непонимание последствий тех или ошибок. Во время пании по повышению безопасности Windows (Windows Security Push) в 2002 г. в Microsoft разработали новый набор функций для работы со строками, Аналогичные функции были созданы и для других операционных систем. Я деюсь, что они станут стандартом, и мы сможем наконец безопасно работать со строками независимо от целевой платформы. Подробнее о них рассказано в деле «Использование Мне нравится то, что все разновидности языка BASIC (для некоторых из это Visual Basic, а я начал писать на BASIC, еще когда строки программы нумеро вались), а также Java, и прочие языки высокого уровня во время испол:

границы массива, а многие из них имеют вдобавок собственный удобный строковый тип данных. Но операционные системы до сих пор пишутся на С и изредка на C++. Поскольку собственные интерфейсы системных написаны на С и C++, программисты вряд ли откажутся от гибкости, мощи и с ;

о рости, присущих С и C++. Неплохо переместиться назад во времени и язык С собственным безопасным строковым типом, а заодно и библиотекой :

дежных строковых функций. Но, к великому сожалению, это невозможно. что нам осталось — аккуратно работать с ним, чтобы его мощь не обернулась против ас.

Когда я собирался писать эту то выполнил поиск в Интернете по buffer overrun. Результат потряс! Я получил массу инструкций для керов, где подробно объяснялось, как задать жару клиентам. А для тов информации было очень мало, и практически не нашлось никаких о фокусах, которые могут выкинуть хакеры. Я собираюсь заполнить пробел и опубликовать ссылки на широко известные материалы по этой теме. Я чески не одобряю создание инструментов, с помощью которых можно совершить преступления, как писал Сун в книге «Искусство войны»: «Знай врага, к самого себя, и успех обеспечен*. В частности, я слышал от многих тов: «Это всего лишь переполнение кучи. Им нельзя Ничего глупее не придумаешь. Я надеюсь, эта глава заставит вас по-новому относиться к полнениям буфера всех мастей.

Вы о разных типах переполнения буфера, ошибках индексации м сивов и формата строк, а также о несовпадении размеров буфера для ANSI и Unicode. Ошибки формата строк необязательно связаны с переполнением буфера, но с их помощью нападающий может проделывать такие же штуки.

тем речь пойдет о нескольких способах нанесения «тяжких а так о методах самозащиты.

Переполнение стека Переполнение буфера в виде переполнения стека когда буфер, ленный в стеке, перезаписывается данными, объем которых превосходит его р мер. Размещенные в стеке переменные физически располагаются рядом с Часть Методы безопасного кодирования сом возврата для кода, вызвавшего функцию. Обычно «виновниками» ошибки бывают данные, введенные пользователем и переданные затем в функцию типа strcpy. В результате настоящий адрес возврата перезаписывается подставным ад ресом. Как правило, переполнение буфера хакер использует, чтобы заставить программу сделать что-то нужное ему, например создать привязку командной оболочки (command shell) к определенному порту. Иногда взломщику приходит ся преодолевать затруднения, например: вводимые пользователем данные все-таки проходят какую-то или в буфер помещается лишь ограниченное число Если в системе применяются наборы двухбайтовых символов, хакеру придется чуть больше попотеть, но непреодолимой проблемой это не станет. Если вы любите головоломки, переполнения буфера можете рассмат ривать как интересное и полезное упражнение. (Если вам удалось обнаружить брешь, пусть это останется вами и производителем ПО до тех пор. пока не достаток не устранят.) Подобные усложнения оставим за рамками книги. А сей час я покажу программу на демонстрирующую самый простой метод эксплуа тации переполнения, Л Эта программа как буфера в стеке для произвольного кода.

Задача состоит в нахождении строки, которая запустит на исполнение void foo(const input) // Что? Нет дополнительных аргументов для функции // Этот дешевый трюк позволяет посмотреть стек // увидим его когда приступим к рассмотрению строк формата.

сте< выглядит так:

вводимые "пользователем" данные прямо в руки "врага input);

стек выглядит void bar(void) { Меня int argc, ГЛАВА 5 Враг переполнение буфера // Откровенное мошенничество, упрощающее мне жизнь.

foo = foo);

bar = if (argc != 2) • должны передать строку в качестве return -1;

return 0;

.

Это приложение по простоте сродни программе «Hello, Оно ется с небольшого жульничества — я вывожу адреса двух моих функций и bar. Для этого я использую параметр функции Если бы я «ломал» pea, ное приложение, то скорее всего попытался бы вернуться в статический буфер, объявленный в или найти подходящую функцию, из DLL-библиотеки. Цель всего этого — заставить программу выполнить функцию Функция foo содержит пару вызовов которые используют побочные свойства функции с переменным числом чтобы стека. Проблемы начинаются, когда слепо принимает вводимые вателем данные и копирует их в 10-байтовый буфер, Примечание Переполнение выделенного в стеке буфера часто называют пе реполнением статического буфера. Несмотря на то, что слово ческий» часто подразумевает статическую переменную, размещенную в глобальной области памяти, здесь оно используется для противопостав ления динамически выделенному буферу, то есть выделенному в куче фун кцией Очень часто «переполнение статического и «пе реполнение буфера, выделенного в используют как синонимы.

Это приложение лучше всего скомпилировать из командной строки, чтобы получить конечную (Release) версию исполняемого файла. Не стоит исходный код в среду Microsoft Visual C++ и запускать в режиме отладки — отла дочная версия содержит проверку проблем со стеком, и требуемого эффекта не добьетесь. Впрочем, вы можете загрузить проект Visual C++ и его в режиме Release. Вот что выведет программа, если передать ей строку в каче стве аргумента командной строки:

Hello Адрес foo = Адрес = стек выглядит так:

7FFDFOOO 0040108А <- Мы хотим переписать адрес возврата, подставив адрес функции foo.

Часть N Методы безопасного кодирования Hello Теперь стек выглядит так:

6С6С6548 <- Видно, куда "Hello", 7FFDFOOO 0040108А D0410EDE А теперь классический тест на переполнение буфера — введем длинную строку:

AAAAAAAAAAAAAAAAAAAAAAAA Адрес fOO = Адрес = стек выглядит так:

0040108А AAAAAAAAAAAAAAAAAAAAAAAA Теперь стек выглядит так:

Мы получим сообщение об ошибке (рис. информирующее, что команда, расположенная по адресу 0x41414141, попыталась обратиться к памяти по адре 0x41414141.

, on the CANCEL to the Рис. 5-1. Сообщение об ошибке, обусловленной переполнением буфера Заметьте: если на вашем нет среды разработки, эта информация записывается в журналы программы Dr. Watson. По таблице ASCII-кодов легко за метить, что 0x41 — это код символа «А». Такой результат подтверждает, что наше приложение уязвимо для атак. То, что вы не представляете как получить подобный результат, не что переполнением буфера нельзя воспользоваться в дурных целях. Просто вы не знаете как.

ГЛАВА 5 Враг переполнение буфера Как выяснить, поддается ли переполнение буфера «эксплуатации» Сейчас я массу способов воспользоваться переполнением За исключением немногих простых случаев, лету* редко удается доказать, что конкретное переполнение буфера поддается эксплуатации.

Одно при определенных обстоятельствах эти ошибки Так что любая подобная ошибка открывает, либо может открыть «черный ход» для Другими словами, отсутствие явных доказательств что переполнение не поддается эк следует его подверженным подобным манипуляциям.

Если вы заявите, что переполнение буфера в вашей нельзя ис пользовать в дурных целях, кто-нибудь просто из вредности обязательно обратное. Или, того хуже, найдет способ ошибки и передаст его А ведь вы уже что можно не торопиться с установкой пакета — теперь пользователям при дется несладко под градом атак Я нередко встречал разработчиков, хотелось ис правлять ошибку, поэтому они требовали что ее удастся за действовать взлома. Это подход!

правляйте и точка! Подобное желание выяснить серьезность про блемы основано на годами проверенной программистской истине: каж дое исправление влечет за собой новые ошибки, количество которых за висит от сложности кода и опыта Отчасти это но да вайте сравним последствия эксплуатируемого переполнения буфера и ка кой-нибудь заурядной Переполнение буфера чревато публичным позором, а если ваше ПО установлено на то и табными нарушениями работы сетей из-за повсеместного распространения Кроме того, переполнении буфера вам придется немедленно выпускать заплаты и А заурядную ошибку можно без проблем устранить в очередном пакете исправлений. Так что решайте сами. Лично я считаю, что буфера хуже, чем 100 обыч ных ошибок.

Разработчику иногда требуется несколько дней, чтобы оценить опасность переполнение буфера, а исправление и проверка как правило, не При исправлении ошибки, чреватые буфера, обычно не становятся регрессивными, Даже если, наизнан ку, вы не видите способов эксплуатации* ошибки, это отнюдь не значит, что нет. Меня часто как выявить код. Очень трудно в коде обходные способы, добраться до какой-то Это тема серьезного исследования. За исключением простых случаев, невозможно точно установить, все ли пути дости жения функции вы проверили, Внимание! Исправляйте не только заведомо эксплуатируемые ошибки. Устра няйте все недостатки!

Часть II Методы безопасного кодирования А сейчас посмотрим, как подобрать символы для приложению.

Попробуем так:

= Адрес bar = Иой стек выглядит так:

7FFDFOOO 0012FF 0040108А 00410ЕВЕ Теперь стек выглядит так:

4С4В4А 504F4E4D Теперь сообщение об ошибке гласит, что мы попытались выполнить команду по адресу Согласно 0x54 — это код символа А те перь сделаем так:

Адрес foo = Адрес = Мой стек выглядит так:

7FFDFOOO 0012FF 0040108А 00410ЕСЕ ABCDEFGHIJKLMNOPQRS Теперь стек выглядит так:

4С4В4А 504F4E4D 00410ЕСЕ Так-так, уже лучше! Изменяя входные данные, мы получаем возможность кор ректировать адрес которую программа будет исполнять следующей, Подумать только, мы контролируем работу программы при помощи ГЛАВА 5 Враг переполнение буфера данных! Ясно, что, подсунув ей символы с кодами 0x10, 0x40 вместо под строки мы заставим ее выполнить функцию bar. Но как передать эти коды в качестве аргументов командной строки? (Коду 0x10 вообще соответствует не печатаемый символ.) Как любой нормальный хакер, я напишу сценарий Ha..:k на Perl, который «накормит» приложение нужным «зельем» — в командной строке необходимые аргументы:

= = Запустив получаем желаемый результат:

Адрес = bar = Мой стек выглядит так:

Теперь стек так:

4С4В4А 504F4E4D Черт!

Просто, Такое даже начинающему программисту В реальной ат «се вместо первых символов мы вставили бы вредоносный ассемблерный код и уста новили бы адрес возврата на начало буфера. В раз, когда вы будете тать с вводом данных пользователем, вспомните, как просто вас могут «сделать».

Имейте в виду: при использовании другого компилятора или в ной (не U.S. — English) версии ОС смещения могут отличаться. Именно поэтому многие читатели первого издания этой книги жаловались, что примеры не все гда работали. Это одна из причин, по которой я жульничал и выводил на экран адреса двух моих функций. Чтобы заставить пример надо повторить выше и подставить в Perl-сценарий свой адрес функции bar. Кроме того, если вы скомпилируете программу в Visual C++ с установленным по умол чанию она вообще откажется работать. (В этом-то и идея /GC — предотвратить переполнение буфера!) Отключите в параметрах про екта или скомпилируйте программу из командной строки.

Часть II Методы безопасного кодирования А теперь посмотрим, как эксплуатируется ошибка занижения на единицу (off-by-one error). Звучит но при ближайшем рассмотре нии несложно.

Л */ void char* in) { char in, = На один больше!

buf);

void char* in) { Меня int • if(argc != 2) ;

return -1;

!

Яр, Адрес foo, return 0;

Наш горе-программист попал пальцем в небо — использовал функцию для копирования буфера и определения его размера. Ошибка в том, что в буфер записывается на один байт больше, чем требуется. Чтобы увидеть это, скомпилируйте программу режиме Release с отладочной информацией. В пара метрах в разделе C/C++ выберите значение Debug Information Format такое же, как и для отладочной версии, и отключите оптимизацию, посколь ку она конфликтует с отладочной информацией. Если вы пользуетесь Visual Stu dio отключите параметры командной строки и /RTC, иначе пример не будет работать. Затем перейдите в раздел Linker (компоновка) и там тоже вклю чите генерацию отладочной информации. Введем строку из большого числа сим волов в качестве аргументов командной строки, установим точку останова на вызов функции foo и приступим к более детальному анализу кода.

Во-первых, откройте окно Registers и запомните значение регистра ЕВР — оно очень важно для нас. Продолжите поэтапное выполнение программы и войдите ГЛАВА 5 Враг переполнение буфера в Откройте окно Memory и найдите там адрес переменной buf. Вы зов strncpy заполнит буфер символами «А», а значение, располагающееся сразу за переменной buf, — сохраненный указатель из регистра ЕВР. Выполните следую щую строку программы, где происходит запись завершающего символа и обратите внимание, как сохраненный ЕВР поменял свое значение с Ox0012FF80 па Ox0012FFOO (на моей машине установлена а у вас значения отличаться). Теперь обратите внимание, что мы информацию, расположенную по адресу — это 0x41414141! Затем выполните функцию не заходя в нее over), щелкните правой кнопкой мыши окно программы и перейдите в режим дизассемблирования. Откройте окно Registers и внимательно посмотрите, что произошло. Прямо перед командой ret pop ebp. что регистр ЕВР содержит теперь наше искажен roe значение. Теперь мы возвращаемся в функцию main, оказавшись перед самым выходом из нее, и последняя команда, которую она выполнит, — команда просто записывает содержимое регистра ЕВР в а это не что иное, как указатель на стек! И теперь как только мы пройдем финальную команду ret, сразу окажемся по адресу 0x41414141. Мы получили контроль над потоком ис полнения программы при помощи всего одного байта!

Чтобы использовать эту ошибку, применим тот же прием, что и в случае в стеке. «Поиграем* с программой, пока не исчезнут выполнения. Как и ранее, проще всего заставить программу работать на себя, за действовав сценарий на Perl. Например, такой:

= = А вот что получается в результате:

Адрес 00401000, Адрес bar в Черт! Меня Есть ряд условий, которые должны выполняться, чтобы код стал руемым». Во-первых, число байт в буфере должно быть кратным четырем, ин;

1че однобайтовое переполнение не изменит сохраненное значение регистра ЕВР. Во следует контролировать область памяти, на которую укажет так что если значение последнего байта в ЕВР — и размер буфера меньше мы не сможем напрямую изменить значение, которое в конечном счете попадет ESP. И тем не менее многие ошибки такого типа в реальных приложениях эксплуатации. Две наиболее известные: «Apache mod_ssl и «wuftpci Можете почитать о них на страницах http://onlmesecurityfocus.com/ ftp://ftpwu-ftpd.org/pub/wu-ftpd-attic/cert.org/CA-2001-33 соот ветственно.

Примечание 64-битный процессор Intel Itanium не помещает в стек возврата, а сохраняет его в регистре. Это не значит, что этот процессор невосприимчив к переполнению буфера — просто придется чуть боль ше попотеть.

Часть II Методы безопасного кодирования Переполнение кучи кучи — это почти то же, что и переполнение буфера, но его экс плуатация требует больше операций. Как и в случае с буфера стеке, хакер может любую информацию в места вашего при где по нечего делать. Одна из лучших из мне ста тей на эту тему — cm Heap Overflows» о переполнениях кучи).

Ее автор, Коновер (Matt в wOOwOO Security Development (WSD), а текст статьи доступен по адресу tut.txt. WSD — это хакерская организация, члены которой сотрудничают с произ водителями над решением проблем с безопасностью, отыскивая недостатки в популярном ПО. В статье описано множество атак, но я лишь коротко резюми рую основные опасности переполнения кучи:

• многие программисты полагают, что переполнения кучи не поддаются экс плуатации, вследствие чего работают с буферами в куче менее аккуратно, чем со стековыми буферами:

• существуют специальные позволяющие усложнить эксплуатацию стековых Например, продукт разработанный Гриспином Кованом Cowan) с коллегами, использует тестовое значение, его на зывают (по аналогии с живыми которых шахтеры использовали для обнаружения опасного гортгого газа в шахте), чтобы сделать эксплуатацию переполнения буфера куда менее тривиальной задачей. В Visual C++ также есть методы предотвращения переполнения стековых буферов. А вот подобных средств для предотвращения переполне ния кучи пока в природе не существует;

• некоторые операционные системы и архитектуры процессоров позволяют создавать стек без исполняемого кода. Но это опять-таки не защитит от пере полнения кучи, поскольку эта мера годится только для атак с использованием буфера в стеке.

Статья Мэтта содержит примеры атак на UNIX-системы, но не думайте, что в Windows уязвимых мест меньше. В известно множество ошибок кучи, пригодных для использования в неблаговидных це лях. Одну возможных атак такого рода, не попавшую в статью группы wOOwOO, описал на сайте BugTraq некто под псевдонимом Solar Designer:

Кому: BugTraq Тема: Брешь в Netscape, связанная с обработкой марке ра JPEG СОМ Дата: 25 июля 2000 года, 04:56: Автор: Solar Идентификатор [не имеющий отношения к делу текст ГЛАВА 5 Враг переполнение буфера В приведенном примере предполагается фун кции версии Дуга Ли (Doug Lea) (которая применяется в как в библиотеке так и в и (locale) для 8-битных наборов сим волов (как и большинство конфигураций, постав ляемых с glibc, таких как и Каждому свободному блоку памяти в списке соответствуют сле дующие поля: размер предыдущего блока (если он свободен), раз мер самого блока и указатели на следующий и предыдущий бло ки, 0 в поле индикации того.

занятли предыдущий блок размера блока всегда содержит из-за размеров структуры и выравнивания Манипулируя этими можно чтобы вызо вы функции free (3) перезаписывали области памя ти нашими данными, имеющий отношения к делу текст опущен] Имейте в виду, что это относится не к платформе Linux/ х8б. Эта система выбрана лишь в качестве примера. Насколько я знаю, по крайней мере одна из подвержена экс плуатации точно таким же образом (через вызов На странице доступна более свежая презентация Флэйка (Halvar Flake) он рассказывает и о других типах обсуждаемых нами атак.

Следующая программа демонстрирует эксплуатацию переполнения кучи:

/* /* Насквозь дырявый демонстрации проблемы */ class ;

public:

I = NULL;

Часть II Методы безопасного кодирования ;

!= NULL) I void !

// плохой код = buf;

void input) // Глупее быть не input);

!

const char* return private:

m_buf;

// Объявим указатель на класс // который будет принимать вводимые нами данные.

BadStringBuf* = NULL;

void void BadFunc(const const input2) I // Я слышал, что переполнение кучи употребить во // так буфер в куче.

buf = NULL;

char* = = new buf = // Плохой программист - не проверяет ошибки после выделения памяти, ГЛАВА 5 Враг переполнение буфера // Самое плохое, что может случиться - аварийное не так 1 = 2 = buf, if(buf != NULL) int // Имитация строки аргументов в массиве // Адрес функции // Задом наперед, так как в процессорах Intel используется // прямой порядок байт endian).

= {OxOf, 0x10, 0x40, int offset = 0x40;

// Oxfd - уловка, // предотвращающая проверку кучи.

// Значение Oxfd в конце буфера подтверждает его целостность.

// Ошибки не проверяем - это только // как сконструировать строку для инициирования переполнения, Oxfd, offset);

arg1[offset] = = arg1[offset+2] = (char)0x12;

arg1[offset+3] = 0;

arg1[offset+4] = 0;

is bar);

arg2);

NULL) delete return 0;

Эта программа есть в папке Давайте разберемся, что исходит в функции main. Вначале я облегчил себе жизнь, объявив две строковые переменные, которые передаются в мою дырявую подопытную функцию. В реаль ной жизни строки вводит пользователь. Дальше я опять жульничаю, выводя на адрес, по которому хочу перейти, а затем передаю функции ные строки.

Часть II Методы безопасного кодирования Представим себе, что написана программистом, боявшимся допустить ошибку переполнения стекового буфера, но которого дезинформировал его друг, сказав, что переполнения кучи не опасны. Наш программист недавно освоил C++, поэтому написал класс хранящий указатель на буфер ввода. Главное достоинство последнего — предотвращение утечек памяти за счет корректного освобождения буфера в деструкторе. Понятно, что если буфер не был ранее вы делен функцией то при вызове free проблемы. Там есть и другие ошибки, но я оставляю их читателю в качестве упражнения.

А теперь попытаемся встать на позицию хакера. Мы заметили, что приложе ние «падает», если у одной из строк-аргументов слишком большая длина, но ад рес ошибки (который выводится в сообщении) свидетельствует, что нарушение произошло при доступе к памяти в куче. Затем мы запустили программу в отлад чике и увидели местоположение первой строки ввода. Какая важная область па мяти граничит с этим буфером? Небольшое исследование показало, что второй аргумент записывается в динамически выделенный буфер, но где расположен указатель на него? Порывшись «навозной куче» памяти, вы наконец-то извлек ли из нее — адрес второго буфера, который, как оказывается, всего на 0x40 байт отстоит от первого буфера. Теперь мы можем заменить адрес на что угодно, и таким образом любую переданную в качестве второго аргумента строку удастся записать в любое место адресного пространства Как и раньше, нам надо заставить программу выполнить функцию bar, поэто му перепишем указатель так, чтобы он ссылался на адрес который в нашем случае является адресом в стеке, по которому хранится адрес возврата из функции При желании можете «пройти» все шаги в отладчике, но учти те, что проект был создан в C++ 6.0, оттого в другой версии среды разра ботки или в версии Release программы смещения и адреса ячеек памяти будут отличаться. Мы «подкрутим» вторую строку так, чтобы она записала в память по адресу адрес В таком подходе есть один момент: мы не повредили стек, так что его механизмы защиты ничего не Выполнив программу, вы получите следующий результат:

Адрес функции ввод 1 = ввод 2 = 64@ 4ерт! Меня Рекомендую запустить этот код в режиме отладки и пройти его по шагам, ведь проверка стека в отладочном режиме Visual C++ в куче не работает!

Если приведенный пример показался вам надуманным и вы считаете, что ни кому ничего подобного и в голову не придет или что проделать это в реальной жизни практически невозможно, не спешите с выводами. Как Solar ука в своем произвольный код удается запускать на исполнение, когда два буфера не «лежат» рядом, — есть еще возможность обмануть процедуры уп равления кучей.

Способов эксплуатации переполнения кучи в системах становится все больше. В общем случае эксплуатировать переполнение кучи труднее, чем пере полнение стекового буфера, но для хакера (неважно, или «хорошего») чем сложнее задача, тем интереснее решить ее. Вывод ясен: следует очень внима ГЛАВА 5 Враг переполнение буфера тельно следить за тем, чтобы вводимые пользователем данные не попадали в правильные» места памяти.

Примечание Мне известно по крайней мере три способа заставить процеду ры управления кучей записать четыре байта «куда а затем исполь зовать их для перезаписи указателей, стека, ну, общем, чего угодно.

Нередко нарушение безопасности можно инициировать, перезаписывая данные внутри приложения. Наглядный пример — проверка прав доступа.

Ошибки индексации массива Такие ошибки эксплуатируются гораздо реже, чем переполнение буфера, но чре ваты такими же неприятными последствиями. Строка — это символов, а что мешает использовать массивы других типов для записи в произвольные стки памяти? На первый взгляд может показаться, что ошибка индексации ва позволяет данные только по адресам большим, чем базовый массива, но это не Скоро вы узнаете почему.

Давайте посмотрим пример кода, как ошибку массива можно применить для записи в произвольное место памяти:

/.

*/ ffinclude void :

Меня void long unsigned long value ) // Мы настолько уверены в том, что никто не передаст нам // больше 64 кб, что даже не пытаемся // параметры как unsigned short // или проверять индекса за границы.

в память по адресу IntVector[index] = value;

:

size) IntVector = переменной IntVector: IntVector);

Часть II Методы безопасного кодирования == NULL) return false;

else return int argc,.

unsigned long index, value;

if(argc != 3) [index] return -1;

функции bar bar);

// наш вектор - 64 кб должно хватить кому угодно <д>.

могу инициализировать return -1;

index = value = return 0;

также содержится в папке Вы «подстав ляете» приложение, когда разрешаете пользователю сообщать, сколько элементов содержится в массиве, и предоставляете ему произвольный доступ к существую щему массиву, не контролируя выход за границы диапазона.

А теперь разберемся с математической стороной вопроса. в нашем примере начинается по адресу 0x00510048, а значение, которое мы хотим запи сать (угадайте с одного раза), — адрес возврата в стеке, который расположен по адресу Ox0012FF84. Следующее уравнение описывает, как вычисляется адрес эле мента массива, исходя из базового адреса массива, номера элемента и размера элементов массива:

Адрес элемента массива = базовый адрес массива + номер элемента * Подставляя значения из нашего примера, получим:

= 0x00510048 + * Обратите внимание, что вместо Ox0012FF84 мы использовали Oxl0012FFS4, Сейчас вы поймете, почему я отбросил старший разряд.

что номер элемента равен Ox3FF07FCF, или 1072725967, и адрес ГЛАВА 5 Враг переполнение буфера функции (0x00401000) равен 4198400 в десятичном представлении. Вот ре зультат работы программы;

1072725967 Адрес функции Адрес переменной Запись в память по адресу 0012FF Черт! взломали!

Итак, ошибки подобного рода очень легко если хакеру уда ется запустить программу под отладчиком. Похожая проблема связана с ошибка ми старшего разряда (truncation или усечения. На самом деле в 32-битовых операционных системах число 0x100000000 равно ОхООООООСЭ.

Программисты с инженерным образованием знают подобные ошибки, поэтому они обычно более грамотный код, чем те, кто изучал только ные науки (впрочем, как и при любом обобщении возможны исключения). Я няю это тем, что многие инженеры разбираются в численном анализе — чивость чисел, возникающая при работе с числами с плавающей точкой, зас тавляет быть более осмотрительным. Даже если вы уверены, что вам никогда не придется моделировать аэродинамическую поверхность крыла, курс ного анализа вам не повредит, поскольку позволит лучше разбираться в V ках усечения.

Некоторые знаменитые примеры эксплуатации кода связаны с ошибками усе чения. В UNIX-системах идентификатор (ID) записи root ватель) равен нулю. Демон (аналог службы в Windows) сетевой файловой систе мы принимает ID пользователя как целое со знаком (signed integer), г, не равно ли оно нулю, и затем усекает до беззнакового целого (unsigned short). Это позволяет предоставить в качестве идентификатора ID, значение 0x10000, которое не равно нулю, но после усечения до двух превращается в 0x0000, то есть пользователь получит «корневые» (root) полно поскольку его ID равен 0. Будьте очень осторожны с операциями, в кото рых возможно усечение или переполнение.

Более подробно об ошибках усечения рассказано в главе 20. Ошибки отбр старших разрядов вызывают множество проблем с а только становятся причиной неверной индексации массивов, позволяющей зап:

данные в любое место памяти. Кроме того, ошибки при преобразования из знакового в беззнаковое представление и обратно чреваты аналогичными ) блемами, их мы тоже обсудим в главе 20, Ошибки в строках форматирования Строго говоря, ошибки в строках форматирования — это не переполнение ра, но они способны вызывать аналогичные проблемы. Если вы не подписчик списков рассылки, посвященных безопасности, вам вряд ли такого рода ошибки. Есть два хороших сообщения на эту тему на первого Тим Ньюшэм (Tim а второго — Часть II Методы безопасного кодирования Argamal). Совсем недавно Дэвид Литчфилд (David представил более полное описание проблемы в том, что нет универсального и практичного способа, позволяющего определить, сколько аргументов в действительности передано в функцию, которая принима ет переменное число параметров. (Наиболее известные принимающие произвольное число параметров, включая функции времени выполнения С, от носятся к семейству В них символ форматирования записывает указанное количество байт по адресу, в качестве аргумента. Немного повозив шись, можно обнаружить, что часть адресного пространства нашего процесса переписана байтами, нужными хакеру. В 2000 — 2001 гг. в приложениях для UNIX и UNIX-подобных систем было найдено большое количество ошибок, со строками форматирования. С момента выхода первого издания этой книги подобные ошибки обнаружены и в Windows-приложениях. Их эксплуатация в Windows сопряжена с некоторыми трудностями, блоков памяти, которые «интересно» было бы переписать, находятся в диапазоне адре сов от OxOOffffff и ниже, например, стек обычно в районе При небольшом везении хакер преодолевает эти трудности. Но даже не особо везучим все равно удается с легкостью писать в диапазоне — Решение проблемы относительно просто: в всегда надо пере давать строки форматирования. Например, поддается эксплуатации, — нет. Вот приложение-пример.

void long);

void long err) ' ошибка! - = // В общем случае такой способ назвать удачным.

// Выходы из приложения, в глубинах функций X Window, // однажды стоили мне недели отладки.

// Все выходы из приложения должны проходить через в в одном месте.

exit(-1);

void long пошло не так, но с похоже, можно справиться - err = I void PrintMessage(char* file, unsigned ;

ГЛАВА 5 Враг переполнение буфера char if(err == 5) // в отказано = > else ( fErrFunc = "He найден файл file);

// Этот оператор нужен только для того, чтобы показать, что в буфере // на случай, если ваш сам что-то меняет функции fErrFunc // Вот здесь-то и происходит все "нехорошее"!

// Никогда так не делайте.

buf);

;

void foo(void) ;

Нас int argc, I FILE // Небольшое жульничество, чтобы упростить пример функции foo - foo);

// Открываются только файлы pFile = fopen(argv[1], if(pFile == NULL) ;

else { файл 1 28 Часть II Методы безопасного кодирования 0;

А теперь — как это работает. Приложение пытается открыть файл и, если не получается, вызывает функцию которая определяет, есть ли возмож ность после ошибки или нет (в нашем случае это ошибка «доступ и устанавливает указатель на соответствующий адрес Далее форматирует в буфере об ошибке и выводит его на экран. Попутно я вставил дополнительные вызовы чтобы упростить написание exploit-про грамм и помочь читателям, у которых адреса отличаются. Наша цель, как всегда. — вызвать При вводе нормального имени файла программа работает Адрес - Не найден файл Адрес функции Не найден файл Вызов Что-то пошло не так, но с этим, похоже, можно справиться - егг = А если подсунуть «нехорошую» строку:

Адрес foo - Не найден файл Адрес функции fErrFunc - 0012FF1C Не найден файл 0746f20546e Вызов ErrFunc;

Что-то пошло не так, но с этим, похоже, можно справиться - егг = Уже интереснее! Это не что иное, как данные из стека. Обязательно обратите на повтор последовательности «7825» — это поскольку процессор «понимает* только прямой порядок байт (little Подумать толь ко — переданные нами приложению строки стали данными. А теперь немного поэкспериментируем. Проще всего это делать помощи Perl-сценария — я опустил только строки, где определяется переменная По мере изучения при мера вам придется ставить знак у последнего и рас комментировать следующий с присваиванием значения переменной Sarg.

Для перехода на следующий этап последовательно комментируйте очередную строку с $агд и Это первый отрезок exploit-строки Последний Хр будет на 0x ГЛАВА 5 Враг переполнение буфера С того, что мы работаем в архитектуре с прямым порядком байт, получим 0x $агд = Теперь закомментируйте предыдущее присваивание $агд и используйте это в $агд...... ХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХхХх Теперь собственно начнем перезаписывать память - перепишем указатель ft $агд.....

Наконец раскомментируйте строку чтобы посмотреть, как работет exploit-код it $arg = В первом прогоне в конец вставляются «ABC» и последний заме няется на Сначала ничего не произойдет, но добавьте еще несколько симво лов и получите что-то типа:

Адрес foo - Не найден файл ХхХхХхХхХхХхХхХхХхХхХхХрАВС Адрес функции fErrFunc - 0012FF1C Не найден файл ABC Если затем обрезать то в конце получим 00434241АВС. Мы записали п.) адресу, обозначенному последним строку Добавим завершающий нуль теперь мы можем писать в любое место адресного пространства приложения.

полностью подберем воспользуемся Perl-сценарием, чтобы нить ABC на что позволит нам переписать значение, записанное После этого программа сообщит, что ErrFunc происходит в интересных местах. При создании демонстрационного приложения я несколько символов (.) и затем подбирал необходимое количеетво сим волов Если у вас в конце печатается что-то, отличное от или удалите несколько начальных символов, чтобы добиться выравнивания ных по границе в 4 байта, а затем добавьте или удалите спецификаторы чтобы начинал чтение там, где нам надо. Закомментируйте первую exploit-строку и раскомментируйте вторую:

Адрес фунции foo - Часть Методы безопасного кодирования Не найден файл функции Не найден файл Как только заставите это работать как минимум с 4- лями, получите возможность писать в программу любые данные. Во-первых, вспом что запишет необходимое количество символов в 16-битное значение, на которое прежде указывал Удалите один (так как по явился символ замените «ABC» на и повторите попытку. Если вы все сделали так, как я, то получите примерно следующее:

Адрес - Не найден файл ?

Адрес fErrFunc - 0012FF1C Не найден файл 7D005c6f00727[..,]78257825786e682578? ?

Вызов После этого приложение завершится с ошибкой, а это уже шаг вперед. Заметьте:

мы теперь можем перезаписывать указатель на функцию Я знаю, что расположена по адресу а я установил то на 4055 байт больше, чем я могу записать. Все, что надо, — подставить в качестве спецификатора ширины поля в первый вызов и все! После запуска получим:

Вызов ErrFunc Черт! Нас взломали!

Приложение завершилось нормально, поскольку я не сильно память. Я перезаписал ровно 2 байта точно тем значением, которое мне нужно.

Всегда помните, что если вы позволили хакеру писать в память вашего то он всегда сможет «уронить» приложение или выполнить произвольный код — это лишь вопрос времени. Подобных ошибок избежать довольно легко.

явите особую бдительность, если у вас разные форматирующие строки для ных языков, поддерживаемых вашим приложением. Если это так, чтобы их не мог перезаписывать кто попало.

Несовпадение размеров буфера при использовании Unicode и ANSI Переполнение буфера, возникающее из-за несовпадения размеров буфера при использовании различных кодировок, ANSI и Unicode, — обычное явление в ОС Windows. Они возникают, если вы путаете число элементов массива с его разме ром в байтах. Тому есть две причины. Windows NT (и более поздние версии) под держивают строки как в формате ANSI, так и в и большинство Unicode версий функций работают с буферами, размер которых выражается в < широких» (wide), двухбайтовых символах, а не в байтах.

ГЛАВА 5 Враг переполнение буфера Одна из наиболее часто используемых из-за подверженности таким функция преобразует «многобайтные* строки в Посмотрите на этот код:

BOOL // имя из ANSI в Unicode.

0, wszUserName, He заметили дыру? То-то. А собака зарыта в последнем аргументе MultiByteToWideChar. В документации что он «в лах определяет размер буфера, на который указывает параметр передаем значение которое равно 256, верно? А вот и нет.

wszUserName — Unicode-строка, которая содержит 256 «широких» символов, каж дый из которых состоит из двух байт. Так что на самом деле равно 512 байт. Таким образом, функция считает, что размер буфера — 512 «ши роких» символов. Поскольку wszUserName располагается в стеке, то мы получас пригодную для эксплуатации возможность переполнения буфера.

Вот как надо написать вызов этой функции:

О, szName, -1, wszUserName, / Чтобы снизить вероятность подобных ошибок, можно создать такой макро.:;

ttdefine ElementCount(x) Еще один момент, на который следует обратить внимание при переводе Unicode в ANSI: не все символы преобразуются из формата в формат. Второй аргумент функции MultiByteToWideChar определяет, как ведет себя функция, встретив такой символ. Это важно, если вы выполняете приведение в канонический вид calization) или регистрируете вводимые пользователями данные, особенно в сети, Внимание! При использовании спецификатора формата функции семей ства printf «молча» проигнорируют (то есть выбросят) не под дающиеся переводу, так что вполне что число символов во Unicode-строке окажется больше, чем в выходной.

Часть II Методы безопасного кодирования Пример ошибки, связанной с Unicode Брешь, связанная с переполнением буфера в протоколе печати (Internet Protocol), связана с Unicode. Подробнее — бюллетене MS01- soft.com/technet/security). IPP работает как в одном процессе с IIS 5 (Internet Information Services), то есть под системной учетной записью;

сле довательно, поддающееся эксплуатации переполнение буфера становится намного опаснее. Причем ошибка не в IIS. «Дырявый* код выглядит примерно так:

TCHAR BOOL { DWORD = char if { // Что-то копирует байты в количестве dwSize в перемен ную szComputerName. Однако длина dwSize равна поскольку — макрос, который в данном случае определяет формат Unicode. Фактически функции со что можно копировать до байт в переменную дли на которой на самом деле 256 байт. Приплыли!

Бытует заблуждение, что переполнение, когда буфер преобразуется из ANSI в Unicode, не поддается эксплуатации. Каждый второй символ равен null, что тут эксплуатировать? В статье Криса Энли (Chris (http://www.nextgenss.com/papers/ описано, как это делается. Суть в том, что, когда буфер несколько больше, чем нужно, хакер может воспользоваться тем, что в архитектуре Intel команды состоят из разного числа байт. Это позволяет заставить систему интер претировать последовательность Unicode-символов как строку однобайтовых команд. Обычно предполагается, что, если хакер может каким-либо образом по влиять на ход выполнения программы, эксплуатация недостатков возможна, Предотвращение переполнения буфера Первая линия обороны — надежный код! Хотя некоторые особенности написа ния безопасного кода и не лежат на поверхности, предотвращение переполнения буфера — краеугольный камень создания надежных приложений. Великолепный источник сведений на эту тему — книга Стива (Steve Maguire) «Writing Solid Code» (Создание надежного кода) (Microsoft Press, 1993). Даже опытный и аккуратный программист почерпнет в ней много полезного.

Всегда проверяйте все входящие данные — все, что вне вашей функции, сле дует рассматривать как небезопасное и враждебное. Точно так же, никакая инфор мация о реализации функции — ничего, кроме входных и выходных параметров, не должно быть доступно извне. Недавно я общался с программис том, который написал примерно функцию печати строки:

ГЛАВА 5 Враг переполнение буфера void { "Префикс msg);

I На мой вопрос, почему нет проверки входных данных, он что конт ролирует весь код, вызывающий эту функцию, кроме знает размер буфера не собирается его переполнять. Тогда я спросил, что будет, если тот, кому поддерживать этот код, окажется не столь аккуратным. В ответ он лишь развел руками. Такого рода конструкции просто напрашиваются на неприятности — функция всегда должна завершаться корректно, даже если она получит которые никак не ожидала.

Еще об одном интересном методе я узнал от программиста из Microsoft. Я звал его "агрессивным Если функция принимает буфер и его размер в качестве аргументов, вставьте такой код:

//buflen = размер в байтах ttendif Если теперь кто-то попытается вызвать вашу функцию и подставить «свою длину буфера, получит ошибку. Если вы используете достаточно новый тор, проблема очень быстро обнаружит себя. Я считаю, что это отличный способ «внедрить» механизм тестирования в само приложение, чтобы отыскивать ки самому, не полагаясь на полную тестирования. Вы можете добиться того же эффекта с расширенными вариантами функций, которые есть в модуле о них я расскажу далее.

Безопасная обработка строк Работа со строками — самый крупный источник ошибок переполнения так что в обязательном порядке следует обсудить вызовы наиболее функций. Я расскажу о версиях, оперирующих однобайтовыми строками, но для двухбайтных версий рассуждения полностью аналогичны. сильно няет то, что кроме функций и поддерживаемых оболочка Windows содержит аналогичные функции, такие как л (экспортируются из Shlwapi.dll). Хотя функции семейства ся очень незначительно и работают как с так и многобайтовыми ми (все определяется тем, как в приложении определен макрос они же что и ANSI-версии. После рассказа о кциях, я как новые функции семейства Функция strcpy Она ненадежна по определению, и следует использовать ее как можно реже, а лучше вообще от нее отказаться. Вот ее объявление:

char const char );

1 34 Часть II Методы безопасного кодирования Количество способов вызова этой приводящих к краху, практичес ки бесконечно. Если источник или приемник равны null, по исключению и вы оказываетесь в обработчике. Если буфер-источник не заверша ется null, результат непредсказуем и зависит от того, где в строке выпа дет содержащий null. А самая большая проблема — переполнение — возни кает, когда длина исходной строки больше размера буфера-приемника. Исполь зование этой функции безопасно лишь в очень простых случаях, таких как копи рование фиксированной строки в буфер в качестве префикса другой строки, Вот пример максимально безопасного вызова strcpy:

/* Эта функция показывает, как использовать strcpy максимально безопасно. */ input) i == NULL) < return false;

// Вызов приведет к краху, если параметр не завершается символом null.

// Заметьте: как strlen, так и sizeof типа size_t, // так что сравнение корректно во всех случаях.

// Также что проверка длиннее ли size_t // числа со может привести к ошибке - подробности в главе // в о проверке кода на предмет < // Все нормально.

input);

else { return false;

обработка буфера, return true;

Как видите, проверок совсем немного если входная строка не завершается символом null, скорее инициирует исключение. Программисты меня часто уверяют, что они проверили массу вызовов strcpy и большинство из них безопасны. Может, оно и так, но если всегда применять более безопасные функ ции, то и проблем будет меньше. Даже достаточно аккуратному программисту очень просто совершить ошибку в вызове He знаю, как вы, а я наделал ГЛАВА 5 Враг переполнение буфера точно таких ошибок и нет ничего чем ошибиться снова, и не риз.

Я знаю массу проектов, в которых функцию «вне закона», и чис ло найденных ошибок переполнения буфера резко сократилось.

такую строку в стандартные заголовочные файлы:

strcpy Unsafe_strcpy Теперь каждая попытка использования strcpy будет инициировать сообщение об ошибке компиляции. Новый заголовочный файл с отменит подобные функции, если только перед включением заголовка вы не определите:

Я называю это средством безопасности — при езде лошади я редко падаю, но тем не менее всегда надеваю шлем, на всякий случай. (На самом деле после дний раз лошадь «уронила» меня в сентябре 2001 г., и шлем спас мне жизнь.) Точ но так же использование только безопасных строковых функций существенно снижает вероятность того, что моя ошибка приведет к катастрофическим после, ствиям. Исключив вы попутно избавитесь от ошибок, с ней связанных.

Функция Эта функция гораздо безопаснее, но и она не безгрешна. Вот ее прототип:

char char const count );

Здесь те же проблемы с передачей в качестве параметров — источника или null или других ошибочных указателей;

при этом инициируются исключения. Другая возможность промахнуться — передать неверное параметра count. Однако в этом случае, если буфер-источник не содержит шающего null, исключения не произойдет. Трудно догадаться о следующей возмож ной проблеме: нет никаких гарантий, что буфер-приемник будет содержать вершающий (Кстати, функция это гарантирует.) Я также обычно считаю серьезной ошибкой, если объем введенных пользователем данных больше, чем дусмотренный мной буфер — это, как означает, что либо я что-то сде лал не либо кто-то пытается взломать мою программу. В функции strncpy не так-то легко определить, что входной буфер слишком большой. Вот несколько меров, Первый способ:

/* функция показывает, как использовать strncpy, а лучший я покажу попозже. */ input) { NULL) assert(false);

return false;

6- 136 Часть II Методы безопасного кодирования input, - 1);

- = // обработка return !

Функция завершится с ошибкой, только если input или неправиль ные указатели. Также следует проявлять бдительность при вызове Исполь зуя этот оператор, вы можете изменить размер буфера в одном месте программы и получить вполне ожидаемый результат сотней строк ниже. Более обяза тельно устанавливайте в null последний символ буфера. Проблема в том, что ни когда нельзя гарантировать, что длина входной строки входит в намеченные рамки, В документации на заботливо сообщается, что в функции не предусмот рено возвращаемого значения, которое информирует об ошибке. Некоторые вполне счастливы тем, что просто обрезают буфер;

они надеются, что ошибку дальнейший код. Ничего подобного. Никогда так не поступайте! Если нужно «вы скочить» по исключению, делать это следует как можно ближе к источнику ошибки.

Отладка сильно упрощается, если ошибка происходит рядом с кодом, ее шим. Это еще и более производительно: зачем исполнять лишние команды? На конец, обрезание строки может дать результат, от дыры в защите до изумления пользователя. [Как сказано в книге Tao of Programming» (Дао программирования) (Info Books, 1986) Джеффри Джеймса (Jeffrey James), «изум лять пользователя нехорошо в любом Вот код, в котором проблема решена:

/* Эта функция показывает лучший способ strncpy.

Она предполагает, что входные данные завершаются символом null. */ char* input) ;

char == NULL) return false;

;

- 1] = // Некоторые развитые средства проверки кода пометят это место // как ошибку - поместите лучше комментарий или // чтобы никто не удивлялся, увидев равное // а не минус один.

strncpyCbuf, input, if(buf[sizeof(buf) - 1] != ГЛАВА 5 Враг переполнение буфера return обработка return true;

} Функция гораздо надежнее. Я сначала установил после, ний символ в null в качестве точки, а затем позволил записы вать буфер полноствю, а не только - 1 символов. Затем я выясняю, ли переполнения, проверяя последний символ на равенство null — единственно му значению, которое можно использовать для проверки;

все остальное может явиться просто по совпадению.

Функция sprintf Функция делит лавры с по разрушительности возможных последстви Вызвать ее безопасно практически невозможно. Вот ее объявление:

char const char [, argument] };

За исключением простых до вызова sprintf трудно проверить, доста точно ли в буфере места для данных. Вот пример:

/* Пример некорректного использования sprintf */ line, unsigned long err, char* !

NULL) ;

assert(false);

return false;

// Сколько есть возможностей потерпеть сбой у "Ошибка в строке = - msg);

// Выполните дополнительные действия, например регистрацию ошибки в журнале и // оповещение return true;

> Насколько вероятно, что эта функция потерпит сбой? Если msg не содержи завершающего null, возможно, инициирует исключение. Я зовал 21 символ для выявления ошибки. Аргумент err способен принимать 10 символов для отображения, a до И символов. (Номера строк не могуч быть отрицательными, но исключать этого полностью нельзя.) Таким образом, и строке msg безопасно передавать только 89 символов. Трудно запомнить число символов, которое разрешается использовать с различными кодами форматирс вания. Код возврата функции особо не поможет. Он сообщает, сколь ко символов было записано, так что ваш код будет выглядеть примерно так:

Часть It Методы безопасного кодирования "Ошибка в строке = line, >= Но это не назовешь элегантным выходом. Вы перезаписали неизвестно сколь ко байт непонятно чем и вполне могли и адрес обработчика исклю чений! Нельзя использовать обработку исключений для предотвращения перепол нения поскольку хакер способен обмануть и обработчиков. Неисправи мое уже случилось, игра закончена, и хакер выиграл. Если вы все-таки не хотите отказаться от то следующий некрасивый трюк поможет вам сделать это бе зопасно. (Я не собираюсь приводить пример кода.) Откройте нулевое уст ройство для вывода, используя и вызовите ное укажет, сколько потребуется байт. Затем сравните это значение с раз мером вашего буфера или даже выделите столько памяти, сколько нужно. В осно ве всего семейства функция а указанные манипуля ции достаточно накладны, поскольку ее дважды вызывают только для того, чтобы отформатировать символы в буфере.

Функция Это одна из моих любимых функций. Вот ее прототип:

_snprintf( size_t count, const [, argument]... );

Обладая всей мощью она тем не менее безопасна в использовании. Вот пример:

/* использования _snprintf */ line, unsigned long err, char * msg) { char == NULL) :

return false;

I // He оставить место под null!

// ошибку размера буфера на единицу?

"Ошибка в строке = - line, err, msg) < 0) :

// Переполнение!

return false;

else i = // дополнительные действия, например регистрацию ошибки в журнале // и оповещение ГЛАВА 5 Враг переполнение буфера return true;

;

Может что надо думать над чем угодно, не над тем, из этих функций использовать: гарантирует, что выходной буфер з вершается символом по крайней мере не так, как в библиотеке времени выполнения Microsoft С, — так что вам придется проверять все самим. Еще то, что функция не входила в стандартную библиотеку С, пока не был принят стандарт ISO С99- Поскольку нестандартная функция (поэтому, кстати, се имя начинается со знака возможны четыре типа поведения, если вы допускаете написание переносимого кода. Она способна: возвратить тельное число, если буфер слишком мал, количество байт, которые должна была а также завершить или не завершить буфер символом null. Если вы со бираетесь писать переносимый код, лучше всего создать макрос или функцию которая проверяет, нет ли ошибок;

это позволит изолировать ошибки и «не пустить* их в основной код. Помимо заботы о переносимости кода не забудь те ограничить количество символов числом, которое на единицу меньше, чем мер буфера, чтобы оставить место для завершающего Всегда завер шайте буфер символом null.

Конкатенация строк с использованием традиционных функций может оказаться небезопасной. Как и небезопасна (за исключением простых a сложна в работе, так как спецификатор длины обозначает место, шееся в буфере, а не действительный размер буфера. Использование • лает конкатенацию строк легкой и безопасной. Я как-то поспорил с одним работчиком о различиях в производительности между и с после дующей strncat. Измерения показали, что они очень незначительные и заметны только в циклах с тысячами повторов.

Строки в Standart Template Library Standart Template Library (STL) — одно из лучших облегчающих мирование на C++. STL сэкономила мне массу времени и сделала мой труд более эффективным. Мое недовольство по поводу отсутствия в С нормального строко вого типа удовлетворено — такой тип есть в C++. Вот пример:

/* Пример строковых типов в STL */ ftinclude namespace std;

void input) !

str2;

// форму, если уверены, // что строка заканчивается символом null.

= input;

// Если не уверены, есть ли в конце строки null, сделайте так;

str2.append(input, 132);

// 132 == максимальное символов, которое разрешается скопировать.

Часть II Методы безопасного кодирования обработка, // Так можно вернуть строку, Проще некуда! Склеить две строки так же просто:

string s1, s2;

= s2 = // А теперь значение s1 станет s1 += s2;

Строковые классы STL также содержат множество действительно полезных для поиска символов и подстрок в строках и обрезания строк, Есть версия и для Класс из библиотеки MFC Foundation Classes) работает точно так же. Я должен отметить лишь то, что способен инициировать при недостатке памяти или возникновении ошибок. присвоение STL-строке указателя NULL выливается в исклю чение. Порой это функция принимает Интернет адрес в бинарном виде, а возвращает его строковое представление. При сбое фун кции вы получите NULL.

С другой стороны, в одном из крупных серверных приложений Microsoft в последнее время применялся класс для всех строк. Дорогостоящий и доско нальный анализ кода, выполненный одной известной и уважаемой консалтинго вой компанией, не показал ни одного переполнения буфера в где для рабо ты со строками повсеместно использовался класс string. Также можно прибегнуть к более строгому контролю типов объектов, создав для string класс-обертку Как только вы увидите ссылку на это класс, вам сразу станет ясно, с чем вы име ете дело и как с этим обращаться.

Функции и Рассказ о небезопасных функциях обработки строк был бы неполным без функ ции gets. Она определяется так;

char *gets( char );

Это не функция, а одно большое несчастье. Она читает поток пока не получит символ перевода строки или возврата каретки. Нет способа узнать, пере полнился ли буфер. Никогда не используйте ее — лучше задействуйте или объект языка C++.

Использование Во время кампании по безопасности Windows (Windows Security в начале 2002 г. мы поняли, что все существующие функции обработки строк не лишены проблем и нам надо создать библиотеку, которую мы могли бы использовать в своих приложениях. Мы выделили свойства, которые нам требовались (выдержка из документации по SDK):

ГЛАВА 5 Враг переполнение буфера размер буфера-приемника обязательно передаваться в функцию, что бы она не выходила за его пределы;

буферы гарантированно должны содержать завершающий null, даже при усе чении результата:

все функции должны возвращать типа с одним кодом ус завершения — каждая функция должна быть доступной в двух версиях: с поддержкой символов (cch) и байт (cb);

у большинства функций быть расширенная расширенной функциональностью.

Примечание Копия содержится в папке Посмотрим, почему каждое из этих требований важно. нам обяз надо знать размер буфера. Его легко узнать, вызвав оператор Общая таких функций, как в том, что люди нередко в подсчете символов — использование только полного размера буфера ет избавиться от множества ошибок. Всегда завершать символом null буферы это хороший стиль, и мне, честно говоря, непонятно, почему стандартные функ ции так не делают. Далее, функции возвращают разные результаты. Иногда ка обрезается или один из указателей null. В библиотечных функциях это не так-то легко выяснить. Помните, к каким ухищрениям нам шлось чтобы вызвать Как я уже говорил, входных данных обычно чревато ошибками;

теперь-то мы точно знаем, что мо жет произойти.

Другая проблема, особенно часто проявляющаяся при работе со строками в и Unicode-формате одновременно, возникает из-за того, что путают строки в байтах и в символах, а это «две большие разницы*. Чтобы этого не про исходило, все функции библиотеки создаются в двух вариантах: один ботает только с символами, а второй — с байтами. Очень что у вас есть возможность указать, какую из двух версий вы желаете вызвать;

для этого надо • делить флаг JUNCTIONS или Кроме того, в распоряжении расширенные функции, которые делают практически все, что понадобится. Вот некоторые доступные флаги:

• определяет символ, которым заполняется я в буфере место. Он удобен для проверки вызывающего кода, а именно топ действительно ли размер буфера такой, как утверждается;

• трактует переданный null как пустую строку.

зуйте для замены вызовов • заполняет выходной буфер в случае аварийного за вершения • устанавливает выходной буфер в пустую строку в случае сбоя • урезание строки как неисправимую Разрешается комбинировать с одним или двумя флагами, приведенными 142 Часть II Методы безопасного кодирования Расширенные функции отрицательно сказываются на производительности. Я стараюсь их использовать в режиме отладки, чтобы ошибки, а также ког да крайне необходимы дополнительные возможности. Они делают и по лезные вещи, например выводят число символов (или байт), оставшихся в буфе ре, или указатель на конец строки.

Но самая главная особенность Strsafe.h такова: если не определить старые и опасные функции вызывают ошибки компилятора! Хочу вас предостеречь: задействовав эту возможность для большого по объему кода на поздних этапах разработки, вы можете «потонуть» в ошибках, а процесс разра ботки приложения дестабилизируется. Если хотите избавиться от всех старых лучше всего это сделать на ранних этапах разработки. С другой сторо ны, я больше всего боюсь ошибок, связанных с безопасностью, так что сами ре шайте, что вам важнее. Детальную информацию и обновленную версию ищите на Следующие примеры демонстрируют сценарий до и после замены в програм ме на С опасных функций на функции из библиотеки Крайне небезопасный \ TCHAR szCWD, cchPath);

// Более безопасный код с strsafe cchPath) { TCHAR if szCWD) cchPath, cchPath, cchPath, { return return false;

Пара слов об осторожности при работе со строковыми функциями Даже при работе с более безопасными строковыми функциями, в том числе из библиотеки strsafe, требуются умственные усилия. Посмотрите на следующий код на основе библиотеки strsafe. Видите «дыру»?

ГЛАВА 5 Враг переполнение буфера char HRESULT = szData);

h2 = szData);

Взгляните на второй аргумент в обоих вызовах Второй вызов некор ректен: переменная заполняется, исходя из размера А должно быть так:

char HRESULT = HRESULT = To же самое применимо и для версий функций библиотеки С. Мы с Майклом часто шутим насчет того, что можно месяц заменять все вызовы strcpy и strcat и соответственно, а затем еще месяц исправлять ошибки, появив шиеся из-за такого массового кода. Что не так в этом примере?

tfdefine MAXSTRLEN(s) if (bstrURL != NULL) { szExtDst;

szTmp, bstrURL, );

= 0;

szExtSrc = bstrURL, ', szExtDst = szTmp, { = 0;

{ szTmp, );

szTmp, szExtSrc, );

Вроде бы все хорошо, но в любой момент может случиться переполнение бу фера. Проблема кроется в последнем аргументе функций конкатенации. В шинстве случаев он должен содержать величину свободного места в буфере но это не так. Здесь всегда передается полный размер буфера, а ведь по мере до бавления данных свободное пространство в буфере уменьшается.

/GS компилятора Visual C++ Новый замечательный параметр /GS компилятора Visual C++ позволяет по мещать «канарейку» между любой определенной в стеке переменной и указателя ми на ЕВР, на адрес возврата и на обработчик исключений функции. Параметр /GS предотвращает эксплуатацию простого переполнения буфера.

Часть II Методы безопасного кодирования Примечание Параметр /GS делает то самое, что StackGuard, создан ная Гриспином Кованом Cowan) и доступная на сайте www.immunix.org. Она разработана для защиты приложений, скомпили рованных средствами gcc. Однако параметр /GS и StackGuard никак не связаны, они разрабатывались Класс — это действительно круто. ли это, что достаточно приобрести Visual C++ радостно скомпилировать свою программу с параметром /GS и забыть о переполнении буфера? Нет. Есть масса типов атак, которые ни ни StackGuard не в состоянии предотвратить. Сейчас я познакомлю вас с не которыми способами переполнения буфера для изменения хода выполнения программы. (Текст взят из замечательного документа, созданного командой по безопасности Microsoft Office.) • Разрушение стека (stack smashing) метод буфера для изменения адреса возврата функции. Пресекается «на корню» па • Перенаправление указателя (pointer subterfuge) — перезапись локального указателя с целью поместить данные в нужное место. Параметр /GS не в со стоянии предотвратить атаку, если это место — не адрес возврата.

• Атака на регистр (register attack) — перезапись значения, хранимого в регистре (например в ЕВР), для получения управления. Иногда удается предот вратить.

• Захват (VTable hijacking) — изменение локальной ссылки на объект так, чтобы вызов VTable приводил к нужной функции. Как здесь не помогает. Одна из интересных особенностей /GS — способность из менять порядок, в котором переменные размещаются в стеке, чтобы поместить опасные массивы поближе к таким образом предотвращая неко торые атаки. Имейте в виду, что захватить VTable удается и за счет переполне ний других типов, • Захламление обработчиков исключений (exception handler clobbe ring) — изменение кода обработки исключения, заставляющее систему выпол нить подставленный взломщиком код. здесь также не однако в будущих версиях предполагается обрабатывать такую ситуацию.

• Выход индекса за границы диапазона out of range) — использо вание индекса массива, который не проверяется на соответствие разрешенному диапазону. Параметр /GS здесь также не помощник, за исключением случая изменения адреса возврата.

• Переполнения кучи (heap overflow) — принуждение диспетчера кучи к выполнению злой воли хакера. От /GS тоже не спасет.

не избавляет от подобных проблем, что же в нем Провер ка целостности стека избавляет только от прямого нарушения структуры стека и особенно адреса который помещается регистры EIP и ЕВР. Он превос ходно справляется с проблемами, для борьбы с которыми и предназначен, но не совсем годится для предотвращения брешей, которых «не понимает*. Более я могу привести примеры заковыристых многошаговых атак, которые ГЛАВА 5 Враг переполнение буфера (и любой другой механизм защиты стека). Я не пытаюсь предотвращать мы в подобных сложных атаках, — я хочу предотвратить проблемы в коде приложений.

Некоторые из с которыми позволяет справляться проверка стека, от носятся к ошибкам общего типа. Взять хотя бы приложение из этой главы, демон стрирующее ошибку занижения размера буфера на единицу. Любой из нас может ошибиться и написать такой код. Думаю, мне не удастся сказать лучше, чем го ворится в подобранных Гриспином Кованом (ссылки вы найдете на — это примеры ошибок из реальной жизни, торые удалось исправить путем простой перекомпиляции.

Как утверждает Грег Хогланд (Greg в своих сообщениях на сай нельзя расслабляться, просто установив Посмотрим, что мы в состоянии сделать, чтобы избавиться от проблем, • Запрет на вызовы небезопасных функций — неплохой способ, но про граммисты все равно найдут возможность напортачить, впрочем, об этом я уже говорил.

• Проверка кода — еще один хороший метод выявления ошибок, но как и автор кода, тоже человек, а значит, может ошибаться.

проверки кода напрямую зависит от опыта проверяющего, а также от того, насколько он свеж и бодр. Везение тоже не стоит сбрасывать со счетов.

санная Майклом программка содержала ошибку занижения размера и я нашел ее. Но до этого программу смотрели многие матерые (в том числе и сам Майкл), но никто ее не заметил, • Тщательное тестирование еще один мощный инструмент, но кто из претендует на обладание идеальным планом тестирования?

• Средства анализа кода — эта область пока находится в зачаточном нии. Их преимущество в том, что они всегда начеку и быстро миллионы строк кода. Убогое средство сканирования кода ничем не лучше команды:

grep strcpy А любой умелец, владеющий способен самостоятельно написать сцена рий получше. Впрочем, даже лучшие средства анализа не охватывают все типы брешей. Сейчас ведутся активные исследования, и я надеюсь, что поколения намного лучше справятся со своей задачей. Однако проблема оче.

сложна, так что не ждите быстрого решения.

Я считаю все эти меры предосторожности чем-то вроде ремней безопасности в автомобиле. Чтобы не случилось я стараюсь содержать свой в порядке: слежу, чтобы колеса были езжу регулярно ряю исправность подушек безопасности и системы ABS. Ремни безопасности панацея от всех бед. Они не спасут, если я свалюсь с обрыва высотой Но в случае аварии скорее всего, помогут мне выжить. То же касается и /GS. Избегайте опасных вызовов, проверяйте код, тестируйте и используйте хо рошие средства анализа кода. Проделайте все это, а затем добавьте чтобы защитить себя, если все остальные меры не спасут.

Часть II Методы безопасного кодирования Другое преимущество параметра /GS (а он выручал меня не раз) в том, что некоторые типы брешей он выявляет моментально. Проверка стека в паре с про думанным планом тестирования позволит вместо погони за случайными ошиб ками их первопричину (особенно это справедливо для сетевых приложений).

Внимание! — небольшая мера предосторожности, не более того.

Он никогда не заменит хорошо написанный, качественный код.

Резюме Переполнение буфера стало причиной многих разрушительных взломов систем безопасности. Мы показали, как переполнение различных типов и ошибки в стро ках форматирования влияют на ход выполнения приложения. Надеюсь, что, по няв, как хакеры пользуются этими ошибками, вы станете более серьезно относиться к обработке вводимых пользователем данных. Мы так же рассказали о некоторых популярных функциях обработки строк и о том, как неразумное их использова ние подрывает безопасность кода. Здесь также представлены некоторые решения:

правильное использование строковых классов или функций, определенных в Strsafe.h, поможет сделать ваш код более надежным и заслуживающим доверия. И.

не надо забывать об ограничениях имеющихся средств анализа кода.

Параметры компилятора, его выполнять проверку служат как страховка, но они не никогда не заменят хорошо написанный, безопасный код.

Выбор механизма управления доступом О Microsoft Windows предусмотрено множество способов управления доступом пользователей к объектам. Стандартное и наименее знакомое большинству пользо вателей средство — списки доступом (Access ACL). Спис ки ACL — структурообразующая часть Windows NT/2000/XP и Windows er 2003. В процессе анализа защиты приложений мне частенько приходится выяс нять, как ACL и другие механизмы управления доступом применяются в тех или иных приложениях для защиты важных ресурсов, таких как разделы реестра и файлы. В большинстве разработчики реализуют управление доступом рук вон плохо, отчего ресурсы остаются перед атаками, В этой главе я расскажу, как выбрать механизм управления доступом, чтобы защитить ресурсы, а также о том, почему так важны списки ACL, из чего они стоят, как выбрать, создать и настроить ACL, почему опасны пустые ные таблицы управления доступом (Discretionary Access Control List, DACL) и не удачно составленные записи управления доступом (Access Control АСЕ) и какие еще существуют механизмы управления доступом.

Почему списки ACL так важны Если считать качественную реализацию и управления списки можно считать буквально последним форпостом защиты, способным остановить прорвавшего большинство заслонов взломщика. Как только ленник получает доступ к ресурсу, основная задача решена.

Внимание! Качественные списки ACL — исключительно важный механизм защиты. Обязательно задействуйте его.

148 Часть II Методы безопасного кодирования Представьте себе, что ваше приложение хранит важную информацию в осо бом разделе реестра, a ACL этого раздела содержит Control (Пол ный доступ) для группы Everyone (Все). Это означает, что вправе делать с данными приложения все, что заблагорассудится, в том числе читать, писать или изменять их, а также запрещать доступ к ним других приложений и пользовате лей. Вот пример кода, который считывает информацию из раздела реестра, за небезопасным Sdefine (64) ftdefine BYTE NAX_BUFF);

// Открываем реестр.

HKEY hKey = MULL;

if 0, == // Определяем объем данных, которые нужно считать.

DWORD = 0;

if NULL, NULL, NULL, == { // всю информацию, if NULL, bBuff, == { // Класс! Мы считали информацию реестра.

!

if На первый взгляд программа вроде бы но на самом деле она ва» до безобразия. Здесь ошибочно предполагается, что объем данных в реестре не превышает 64 байта. В первом вызове функции считывается размер данных реестра, а во втором — в локальный буфер считывается число байт, определенное при первом вызове. Если объем данных превышает байта, буфер переполняется.

ГЛАВА 6 Выбор механизма управления доступом Насколько же это опасно? Прежде всего надо исправить код (чуть позже я покажу как). ACL раздела реестра исключительно рискован. Если он предусматривает разрешение Control для группы Everyone, опасность велика, так как любой пользователь сможет увеличить объем информации в разделе до объема, шающего 64 байта, и переполнить буфер. Кроме того, взломщику ничего не сто ит заменить разрешение для группы Everyone на Deny: Full Controll (Запретить:

Полный доступ), что перекроет доступ приложения к данным.

Если в ACL предусмотреть разрешение Full Control для Administrators и Read (Чтение) для Everyone, опасность уменьшится, так как изменять данные и разре шения смогут только администраторы (уровень доступа Всем ным информация станет только для чтения. Иначе го воря, возможность «обрушить» приложения останется только у администрато да и то лишь по неосторожности. Но если атакующий уже получил администратора, могу вам только посочувствовать — готовьтесь к наихудшему!

Следует ли из сказанного, что, имея списки ACL, можно програм мировать «спустя рукава»? Ни в коей мере! Если сомневаетесь, перечитайте е раз раздел «Защищайте все главы А теперь посмотрим, как следует править код.

Раздел не «по теме»: исправление кода доступа к реестру Этот подраздел никак не касается списков но поскольку книга безопасному программированию и раз уж речь зашла о доступе к реестру, дум;

нелишне как решаются подобные «задачки». Вначале надо примерно так:

// Определяем данных, которые нужно считать.

DWORD cbBuff = 0;

if NULL, NULL, BYTE = new // Теперь число байт, указанное в cbBuff.

if (pbBuff HY_VALUE, NULL, NULL, pbBuff, == { // Замечательно! Мы информацию из реестра.

// Используем данные, delete [] pbBuff;

Часть II Методы безопасного кодирования Этот код тоже не лишен недостатков, но другого плана. Здесь память выделя ется динамически, на основании реального размера данных, и только после это го программа переходит к чтению информации из реестра. Но что, если из-за ACL атакующему удастся записать в реестр Мб, вынудив приложе ние выделить 10 Мб памяти? А если такая операция выполняется в цикле десятки или сотни раз? Ваша программа «сожрет» сотни мегабайт только потому, что атакую щий заставил ее читать по 10 Мб в каждом проходе. приложение исчер пает а компьютер беспрерывно перебрасывая данные между памятью и страничным файлом.

Лично я бы решил проблему так:

BYTE MAX_BUFF);

= NULL;

if 0, ERROR_SUCCESS) { DWORD = (bBuff);

Считываем данные, но более байт, чем указано в if NULL, NULL, bBuff, == { // Замечательно! Мы считали из реестра.

} !

if В этом случае, даже если взломщик и загрузит значительный объем данных в реестр, программа считает данные, но не более, чем определено в Если информации окажется больше, возвратит ошибку сообщая, что данные не помещаются в буфере.

Не побоюсь повториться: риск уменьшится, если назначать рассматриваемо му разделу реестра надежные списки ACL. Но это никоим образом не избавляет вас от обязанности выкорчевывать ошибки из кода на случай ненадежного ACL или непреднамеренного его ослабления из-за неосторожных действий админис тратора. Но хватит посторонних разговоров — вернемся к спискам ACL.

Из чего состоит ACL Я вкратце расскажу о списках ACL, на случай, если вы не знаете или подзабыли, что это такое. Те же. кто владеет предметом, могут пропустить этот ACL — это метод управления доступом к ресурсам, принятый во многих ОС, в том числе ГЛАВА 6 Выбор механизма управления доступом в Windows NT/2000/XP. В Windows 95/98/Ме и в Windows СЕ списки ACL не под держиваются.

Windows NT и последующие ОС семейства поддерживают двух типов:

бирательную (Discretionary Access Control List, DACL) и системную (System Control List, SACL) таблицы управления доступом. Первая управляет доступом к защищенным ресурсам, а вторая — аудитом ресурсов.

Вот примеры ресурсов, доступ к которым управляется таблицами DACL a аудит — таблицами SACL:

• файлы и каталоги;

• общие файлы (например • разделы реестра;

• общая память;

• • • именованные каналы (named pipes);

• принтеры;

• семафоры;

• объекты каталога Active Directory.

Каждая DACL обычно содержит несколько записей управления доступом (Access Control Entry, ACEJ, хотя она может быть и пустой. DACL, равная NULL, что к ресурсу не применяются никакие механизмы управления доступом.

DACL — это очень плохо, и ее не следует применять, потому что леннику ничего не стоит установить свою политику доступа к объекту. О DACL мы поговорим попозже.

АСЕ состоит из двух основных компонентов: учетной записи, >й идентификатором безопасности (security ID, SID) и перечнем разрешенных действий над SID представляет учетную запись группы и.

компьютера. Наиболее известна — очень хочется сказать известна* — АСЕ-запись с SID группы Everyone (Все) с разрешением Full Control (Полный до ступ). Everyone — это название группы, иногда ее называют World (все тели), се идентификатор — Control разрешает делать с ресурсом е, что заблагорассудится. Поверьте мне, Control действительно означает «все, что Следует заметить, что АСЕ может быть и запрещающей, то есть разрешающей доступ конкретной учетной записи. Например, Everyone с запре щающим разрешением Full Control означает, что любой учетной записи — в числе и вашей! — доступ к ресурсу закрыт. Если удастся установить ресурсе такую АСЕ, создаются прекрасные условия для успешной атаки в обслуживании так как ресурс становится абсолютно Не совсем верно. Списки управления доступом (ACL) к объектам Windows NT/2000/ ХР состоят из избирательной (DACL) и системной (SACL) таблиц управления досту пом. Первая используется для регулирования доступа к объекту, а вторая — для управления аудитом. — 152 Часть II Методы безопасного кодирования Как ли файловая система списки ACL Для этого надо асего лишь изменить переменную она указыва на ТОМ:

{ if NULL, { списки.

& ? ;

вправе сетевые адреса, например Подробнее правила вызова Описаны в комплекте ресурсов SDK и в библиотеке Эту же задачу можно решить средствами (Microsoft Visual Basic Scripting Edition) или Microsoft В следующем на VBScript для выяснения, есть ли на томе файловая система которая списки АС1, используется объект Однако этот код не рабо если файловая система отличается от — даже что она Впрочем, в Windows только вая система ACL — NTFS.

drv "c:\" fso = Set Dim ;

* False If "NTFS", 0 * & vol & " с файловой & пи ACL? " & aels) Как с объектом вы узнаете из к Script ГЛАВА 6 Выбор механизма управления доступом Примечание Владелец объекта может в любой вернуть себе доступ к ресурсу, даже при наличии запрещающей У всех защищаемых объек тов Windows есть свой владелец. Например, создав файл, вы чески становитесь его владельцем. Единственное исключение — объек ты, создаваемые администратором;

они попадают во владение группы Administrators (Администраторы).

Как выбрать оптимальный ACL Последние несколько месяцев, анализируя приложения на предмет безопаснос ти, я следую жесткому правилу: каждой АСЕ в ACL, должно быть По сути означает, что если нельзя определить, почему АСЕ при сутствует в ACL, ее удаляют. Проект системы создается с применение методики высокоуровневого анализа и на собранных бизнес-требований, Такой же подход применяют к созданию списков Я видел массу приложений с ливо и в спешке созданными списками результат — уязвимости защиты или неудовольствие пользователей.

соответствует ли ACL целям приложения, просто:

1. определите ресурсы, которые нужны приложению;

2. выясните, какие требования по управлению доступом к вают особенности бизнеса;

3. выберите подходящую технологию управления доступом;

реализуйте требования по управлению ресурсами в виде технологии управ.

ния доступом.

Прежде всего надо выяснить, какие ресурсы требуются, например файлы, раз делы реестра, данных, Web-страницы, именованные каналы и т. д., и как дый из них надо защищать. Это позволит понять, какие списки ACL необходимы для защиты ресурсов. Если определить потребность в ресурсах трудно, подумайте, откуда берутся данные, — это поможет вам.

Затем нужно выяснить порядок доступа к ресурсам. Недавно я участвовал в собрании группы разработчиков приложения, в котором разрешение «Everyone:

Full устанавливалось на некоторых критически важных файлах.

тели объясняли этот факт просто: пользователям нужен доступ этим файлам». Благодаря моей настойчивости мне удалось получить от одного из про граммистов следующий ответ: пользователям надо читать файлы данных, Администраторам нужно разрешить выполнять с файлами любые задачи.

пользователям из бухгалтерии доступ к файлам следует закрыть», Обратите внимание на выделенные курсивом слова. Те, кто со сцена риями использования системы (use case) на языке объектного (Unified Modeling Language), что я выделяю ключевые элементы сценария, пытаясь определить бизнес-требования. На основе последних создаются ческие решения, в данном случае это требования по управлению доступом, на б;

зе которых и определяют списки управления 154 Часть И Методы безопасного кодирования Примечание Замечательное введение вы найдете в книге Мартина Фа (Martin Fowler) и Кендалла Скотта (Kendall Scott) «UML Distilled: A Brief Guide to the Standard Object Modeling Language» (Сущность UML:

краткое по стандартному языку объектного моделирования) (2nd Edition, Publishing Co, 1999).

Как вы помните, списки ACL состоят из записей АСЕ, а последние представля ют собой правила, составленные в следующей форме: «Субъекту разрешается вы действие над объектом» или «Такой-то может выполнять такие-то опера ции с данным ресурсом». В нашем примере три АСЕ. пользователи в компь ютере могут читать с — это правило, которое прекрасно транс формируется в первую АСЕ: Interactive: Read (Интерактивные: Чтение). Это клас сическое предложение «субъект — глагол — объект». Маска доступа — 32-разряд ное значение, в котором определены предоставляемые или запрещаемые записью АСЕ.

Примечание Группа Interactive представляет пользователей, которые локаль но вошли в систему компьютера и работают с ресурсом, расположен ным на этом компьютере (в отличие от пользователей, которые полу чают доступ к ресурсу через сеть). С технической точки зрения, эта группа состоит из S1D пользователей, вошедших в систему по вызову с параметром установленным в Интерактивные пользователи — то же, что и «Все локальные пользователи В эту группу также входят пользователи, обращающиеся к компью теру по протоколу FTP или HTTP и по умолчанию прошедшие аутентификацию по базовому методу на сервере IIS 5, Аналогичную процедуру следует выполнить со всеми субъектами (пользовате лями, группами и компьютерами), пока не готовый ACL. В нашем примере получается ACL, показанный в табл. 6-1.

Таблица Список управления доступом (ACL), полученный на основании бизнес-требований Разрешения Accounting (бухгалтерия) Full Control (Запретить: Полный доступ) Read (Чтение) Administrators (Администраторы) Control (Полный доступ) SYSTEM Full (Полный доступ) Внимание! При создании ACL программными методами запрещающие АСЕ надо обязательно размещать в начале списка. В ACL, созданных средствами пользовательского интерфейса такой порядок записей АСЕ обеспечивается автоматически, Если пренебречь этим правилом и раз местить запрещающие АСЕ после разрешающих, субъекты получат до ступ, который предполагалось для них закрыть.

ГЛАВА 6 Выбор механизма управления доступом Однажды я внес в систему управления разработкой ПО сообщение об защиты — наличие разрешения Everyone: Full Control в ACL создаваемого мой именованного канала. Программист закрыл ошибку как проекту, заметив, что всем нужен доступ для чтения, записи и именованного канала. Было забавным ошибку в состо янии исправлению», заметив что в своем ответе он четко опре делил требуемое содержимое и почему бы ему не реализовать сказанное в программе!

Примечание Качественные списки ACL жизненно если программа ра ботает в среде с серверами терминалов Server). Многим пользо вателям предоставляется доступ к большому количеству программных таких как именованные каналы и общая память, а неудачные списки повышают риск компрометации системы в случае блокиро вания доступа к ресурсам злоумышленником.

Примечание Подробнее о неудачных и серверах терминалов почитайте в опубликованном в январе 2001 г. компанией Microsoft бюллетене по безопасности «Weak Permissions on Winsock Mutcx Can Service (Неудачные разрешения способны вызвать сбой службы) (www.microsoft.com/technet/security).

Эффективные запрещающие АСЕ-записи Иногда в процессе определения политик безопасности оказывается, что о пользователям доступ к ресурсу не нужен. В таком случае не стоит ся или бояться применить запрещающие АСЕ.

правила управления доступом очень просто — они в Следующий этап — выбор технологий для реализации ления доступом и настройки механизмов в соответствии с политиками ния доступом.

Создание ACL что многие разработчики просто не знают, какие функции надо использовать для создания ACI, в поэтому я подробно расскажу, это делается. Я познакомлю вас с созданием ACL в Windows NT 4 и Windows а также с некоторыми новыми возможностями Visual Studio и библиотеки ATL (Active Template Library).

Создание ACL в Windows NT Я прекрасно помню, как первый раз решил создать список ACL в программе на C++, это оказалось не так-то просто. Именно тогда я понял, почему так не любят программировать качественные ACL — это сложная задача, выпол нение которой связано с созданием большого по объему и чреватого ошибкам кода. В помощь вам я приведу примеры кода для Windows NT 4 и 156 Часть II Методы безопасного поздних версий. (Код для предыдущих версий Windows NT намного сложнее и связан с вызовами и Здесь показано, как создавать и дескрип тор безопасности (security descriptor), а затем прикреплять его к новому каталогу, Имейте в виду: в каталоге обычно уже есть унаследованная от родительских каталогов. В этой программе новая ACL переопределяет все существовавшие до этого. Честно говоря, я всегда выбрасываю списки ACL, по умолчанию унаследо ванные от родительского контейнера, — может передаться потомку и свести на нет все мои усилия.

Л NT4ACL.CPP = NULL, = NULL, pNetworkSID NULL;

PACL pACL = NULL:

pSD = NULL;

// В ACL будут АСЕ-эаписи:

// Network Access) // Everyone (Read) // Administrator (Full Control) { const int = 3;

EXPLICIT_ACCESS * ;

// Создаем общеизвестный SID группы сетевых пользователей Network.

SIDAuthNT = if 1, 0, 0, 0, 0, 0, 0, ) throw = GENERIC_ALL;

= = = TRUSTEE = (LPTSTR) pNetworkSID;

// Создаем общеизвестный SID для группы Everyone.

= if 1, ГЛАВА 6 Выбор механизма управления доступом О, О, О, О, О, О, О, fcpEveryoneSID) ) throw GetLastErrorO;

= = SET_ACCESS;

= = = (LPTSTR) // Создаем общеизвестный SID // для встроенной группы if 2, 0, 0, 0, 0, 0, 0, ) throw GetLastErrorO;

= GENERIC_ALL;

= = = = // новый ACL с готовыми if != ea, NULL, throw // Инициализируем дескриптор pSD = LocalAlloc(LPTR, if (pSD == NULL) throw GetLastError();

if throw // Добавляем ACL в дескриптор if TRUE, // fDaclPresent flag { throw GetLastErrorO;

Часть Методы безопасного кодирования } else { sa;

= = FALSE;

pSD;

if throw } // Конец блока try.

// Условие if if (pACL) // Вызываем FreeSID для каждого SID, созданного функцией if if if Эта программа есть в папке с примерами Как видите, все очень непросто, а детали я сейчас объясню. Прежде всего надо понять, что к объекту никогда не прикрепляется «голый» — предварительно надо в де скриптор безопасности (security descriptor). Последний инкапсулируется в струк туре где есть поле, информирующее, унаследован ли дескрип тор процессом. В дескрипторе безопасности указаны:

• владелец (представленный SID), определенный вызовом функции • основная группа (представлена SID), определенная вызовом • DACL, определенная вызовом • определенная вызовом Отсутствующие компоненты дескриптора безопасности заменяются значени ями по умолчанию. Например, владелец по умолчанию — учетная запись, в кон тексте которой исполняется процесс, создавший объект, или встроенная группа Administrators (Администраторы), если вызывающая программа работает от име ни члена этой группы. В нашем примере определяется только которая со стоит из одной или нескольких структур Каждая представляет одну АСЕ и содержит идентификатор учетной записи (SID) и разре шения, предоставленные ей на объекте. В также хранятся ГЛАВА 6 Выбор механизма управления доступом сведения, например, должна ли АСЕ наследоваться потомками. Процесс создания ACL иллюстрирует рис. 6-1.

Есть еще две но они применяются для настройки и Первая доступна во всех версиях Windows а вторая — только в Windows NT 4 и более поздних, Если приложение будет работать в Windows 2000 или более поздней ОС, зада ча значительно облегчается наличием языка определения дескрипторов безоп, (Security Descriptor Definition Language), о котором мы сейчас поговорим, к ресурсу ACL Рис. 6-1. Процесс создания ACL Создание ACL в Windows Поняв, что многие разработчики не понимают, как действуют функции обработ ки ACL и дескрипторов безопасности в Windows NT 4, в команде создателей Win dows 2000 решили создать средство представления и дескрипто ров, получившее название Security Descriptor Definition Language (SDDL — описания дескрипторов безопасности). В SDDL идентификаторы безопасности (SID) и записи управления доступом (АСЕ) представляются понятным текстом.

Примечание Полное описание SDDL вы найдете в файле из набора ре сурсов Microsoft SDK.

В следующем примере создается каталог и ему назначаются следую щие • Guests (Deny: Full Control) [Гости (Запретить: Полный • SYSTEM Control);

• Administrators (Full Control);

• Interactive (Read, Write, Execute) [Интерактивные (Чтение, Запись, Часть II Методы безопасного кодирования Л 0x void { sa;

= = FALSE;

= // Запретить доступ группе Guests // Разрешить полный доступ учетной записи SYSTEM // Разрешить полный доступ группе Admins Разрешить доступ на чтение, // запись и исполнение группе Interactive if szSD, { if )) { DWORD err = Эта программа (ее текст в папке с примерами короче и понятнее, чем пример для Windows NT 4. Тем не менее строка в переменной szSD нуждается в отдельном комментарии. Эта переменная содержит ACL (табл. 6-2).

Таблица 6-2. Структура SDDL-строки Примечание _ D:P D означает, что это Другой вариант — 5: так отмечаются АСЕ аудита (в таблице За этим компонентом следуют АСЕ записи. Параметр Р устанавливает флаг что дает максимум контроля над АСЕ за счет запрещения ния от родительских контейнеров. Если предотвращать наследо вание не надо, этот параметр опускается Строка АСЕ. Определение каждой АСЕ отделяется круглыми скоб ками.

D — запрещающая АСЕ.

ГЛАВА 6 Выбор механизма управления доступом Таблица 6-2.

Примечание — наследование потомками. Иначе говоря, эта АСЕ автоматически будет присоединяться к объектам (например файлам) и (например ниже в иерархии.

GA — Generic All Access, то есть полный доступ.

— встроенная группа Guests Эта АСЕ всем гостям доступ к данному объекту и рас • положенным ниже в иерархии.

Два отсутствующих значения представляют соответственно и Они не используются в этом примере, так как применяются только к АСЕ объектов (в противовес контейнерам). АСЕ тонко доступом к объектам-потомкам A — разрешающая АСЕ.

SY— учетная запись SYSTEM система) — встроенная группа Administrators (Builtin GR — чтение, — запись, GX — исполнение.

— группа Interactive вошедшие в систему ком пьютера) На рис. 6-2 показана SDDL-строки.

DACL Запрет или разрешение Участник безопасности Л Л Л V V АСЕ Флаги наследования Маска АСЕ Рис. 6-2. Структура SDDL-строки Вам придется использовать и другие обычные и встроенные учетные поэтому в табл. 6-3 мы перечислили наиболее SID в Windows 2000 в более поздних ОС.

Таблица Типы SID в SDDL SDDL-элемент Учетная запись АО Account Operators (Операторы учета) Authenticated Users (Прошедшие проверку) BA Builtin Administrators — встроенная Administrators торы) BG Builtin Guests — встроенная группа Guests (Гости) ВО Backup Operators (Операторы архива) Builtin Users — встроенная группа Users (Пользователи) Certificate Server Administrators — группа Administrators на сервере сер • СО Creator Owner DA Administrators (Администраторы домена) см. след.

Часть II Методы безопасного кодирования Таблица 6-3.

Учетная запись DG Domain Guests (Гости домена) Domain Users домена) Interactive (Интерактивные) LA Local (Локальный администратор) LG Local Guest (Локальные гости) Network (Сеть) PO Print (Операторы печати) Power Users (Опытные пользователи) RC Restricted Code — ограниченный маркер, созданный вызовом функции в Windows 2000 и более поздних ОС SO Server Operators (Операторы сервера) Service Logon User — любая учетная в контексте которой рабо тает служба SY Local System (Локальная система) World (то же, что и Network Service (в Windows XP и более поздних версиях) Local Service (в Windows XP и более поздних версиях) AN Anonymous Logon (Анонимный вход) (в XP и более поздних версиях) RD Remote Desktop Users (Пользователи удаленного рабочего стола) и Terminal Server Users (Пользователи сервера терминалов) XP и более поздние версии) NO Network Configuration Operators (Операторы настройки сети) (в Windows XP и более поздних версиях) Logging Users (в Windows Server и более поздних версиях) Monitoring Users (в Windows Server и более поздних версиях) Преимущество языка SDDL в том, что SDDL-текст можно сохранять в конфи гурационных или XML-файлах. В частности, SDDL применяется в ре дактора конфигурации безопасности (Security Configuration Editor) для представ ления списков системного и NTFS.

Примечание В кампании по безопасности Windows (Windows Security Push) было решено ограничить доступ к счетчикам производительнос ти, для чего в Windows XP создали группы Logging Users и Monitoring Users.

Создание ACL средствами Active Template Library ATL (Active Template Library) — это набор шаблонных классов поставляемых в составе Visual Studio 6 и Visual Studio В последнюю добавили много свя занных с защитой которые облегчают выполнение стан дартных задач по управлению защитой Windows, в том числе и дескриптора ми безопасности. В следующем примере (он разработан средствами Visual Studio создается каталог с ACL такого состава:

ГЛАВА Выбор механизма управления доступом • (Read);

• Administrators (Full Control);

• Guests (Deny: Access).

ttinclude using namespace std;

void try { // Пользовательские учетные CSid CSid = CSid = // Создаем ACL и его // Заметьте: запрещающие АСЕ перед разрешающими.

dad;

// дескриптор безопасности и атрибуты, CSecurityDesc sd;

// Создаем каталог с заданными атрибутами if cout "Каталог } e) cerr "Ошибка, приложение завершилось с ошибкой hex endl;

Примечание Обратите внимание на строки и При работе с общеизвестным SID вместо «обычных» таких как Administrators и Guests, подобные строки обязательны, так как в других версиях Windows (отличных, как в нашем случае, от англоязычной) груп пы называются по-другому. Полный список всех общеизвестных SID есть в пространстве имен C++, в файле Часть II Методы безопасного кодирования По моему мнению, этот код значительно чем показанные ранее программы для Windows NT 4 и Windows 2000. Он проще, чем в Windows NT 4, так как не перегружен деталями, и чем код для Windows 2000, так как SDDL слишком уж специфичен. пример также есть в папке А теперь, после рассказа о качественных списках ACL и методах их создания, пора познакомить вас со стандартными ошибками программистов при создании Как правильно упорядочить АСЕ-записи Я уже говорил о правильном порядке следования АСЕ в Стандартные сред ства интерфейса автоматически корректно упорядочивают АСЕ-записи.

Однако при создании программы эти средства недоступны, поэтому о правиль ном порядке придется позаботиться самостоятельно. Это особенно важно, когда программа считывает ACL ресурса, например раздела реестра, добавляет АСЕ, а затем модифицирует информацию в реестре. Вот правильная последовательность АСЕ в • явно запрещающие доступ (Explicit Deny);

• записи, явно разрешающие доступ (Explicit Allow);

• запрещения, унаследованные от непосредственного родителя;

• разрешения, унаследованные от непосредственного родителя:

• запрещения, от • разрешения, унаследованные от «дедушки»;

• унаследованные от «прадедушки»;

• унаследованные от «прадедушки» и т. д.

Чтобы правильно добавить в новую АСЕ, соблюдайте такую последователь ность, 1. Вызовите функцию или чтобы извлечь информацию ACL из дескриптора объекта.

каждой новой АСЕ создайте и заполните отдельную структуру 3. Вызовите передав существующий ACL и массив структур соответствующих новым АСЕ-записям.

4. Вызовите функцию или чтобы присоединить новый ACL в дескриптор безопасности.

Вот программа на C++, иллюстрирующая описанный процесс. Обратите вни мание, что здесь применяется новая функция, (она есть в Windows 2000 Windows XP и Windows которая делает практически то же, что и I* 0x ГЛАВА 6 Выбор управления argc, char = PACL = NULL;

PACL = NULL;

sd = NULL;

= NULL;

DWORD = 0;

{ dwErr = NULL, NULL, MULL, if (dwErr != throw dwErr;

ea;

DWORD cbSid = if (SidAuthUsers == NULL) throw if NULL, sidAuthUsers, throw = GENERIC_READ;

= SET_ACCESS;

= = TRUSTEE_IS_SID;

= TRUSTEE_IS_GROUP;

dwErr = if (dwErr != throw dwErr;

dwErr = NULL, Часть II Методы безопасного кодирования NULL, e) { // ошибка if if if Функции и добавляют АСЕ конец ACL. А о правильности записей в ACL придется позаботиться про граммисту.

осторожны так как эта функция не под держивает управление наследованием ACL. В этом случает надо задействовать Безопасность при использовании SID сервера терминалов и удаленного рабочего стола Б Windows стандартные SID пользователей сервера терминалов (Terminal Server) и рабочего стола (Remote Desktop), которые присутствуют в маркере пользователя, если тот вошел в систему через сервер терминалов (Win dows 2000 Server) или удаленный рабочий стол (Windows XP и более поздние вер сии). Поскольку SID размещается в маркере, вы вправе применить его для управ ления доступом к ресурсам, создав специальный например:

• Administrators (Full Control);

• Remote Desktop Users (Read) [Пользователи удаленного рабочего стола (Чте • Interactive Users (Read, Write), Знайте: маркер не всегда содержит SID группы Remote Desktop если пользователь ранее вошел в систему в интерактивном режиме. Объясню эту мысль на примере:

• находясь на работе, пользователь Madison входит в систему своего офисного компьютера и выполняет обычные задачи. Маркер Madison содержит SID группы Interactive, так как вход в систему выполнялся с локальной консоли;

• вечером Madison блокирует компьютер и оправляется домой:

ГЛАВА 6 Выбор механизма доступом • дома он решает подключиться к офисному компьютеру средствами го рабочего стола Windows XP по VPN-каналу;

• в процессе подключения система рабочего компьютера создает для новый маркер с SID группы Remote Desktop Users. Но затем служба вает, что этот пользователь уже вошел в систему и его сеанс не закрыт, поэто му, чтобы сохранить состояние рабочего стола в неизменном терминалов отбрасывает новый маркер и подключается к открытому интер активному сеансу.

Теперь, с точки зрения ОС, Madison — интерактивный пользователь, и в качестве он получает доступ не только на чтение, но и на В этом нет ни чего плохого: ему все равно предоставляется доступ для чтения и записи при ра боте с консоли, Кроме того, при удаленном доступе Madison никак не сможет инициировать интерактивный сеанс, Конечно, циники наверняка что Madison скорее всего администратор на собственном компьютере, и все эти камлания с бубнами вокруг других в маркере совершенно излишни!

Вы спросите, к чему я это все рассказал? Просто не забывайте о такой ности, создавая Нулевая DACL и другие опасные типы АСЕ Нулевая DACL (NULL — один из способов предоставить полный доступ к объекту всем пользователям без разбора, в том числе и взломщикам. Я часто го ворю, что DACL - полное отсутствие И это абсолютно верно.

вам совершенно все равно, что пользователи будут делать с вашим объектом — читать, писать, удалять, изменять или же закрывать доступ к объекту другим, да — в путь, пользуйтесь нулевой DACL, но только на свой страх и риск. я не могу себе представить продукт, в котором удастся извлечь выгоду из подобно го поведения, а это означает, что нулевая DACL совершенно бесполезна!

Увидев код, подобный далее, бейте в набат и реги стрируйте ошибку в системе разработки ПО. Объекты надо защищать.

if // В дескрипторе есть NULL, // она пуста!

{ // безопасности с нулевой DACL.

} Еще одна разновидность этой же ошибки — явное заполнение «нулями* туры Этот код также создаст нулевую DACL:

= { 0x0, 0x0, 0x0, // DACL пуста, т.е. нулевая.

7- Часть II Методы кодирования Примечание Отладочная версия приложения будет информировать о наличии нулевых DACL, если создать ее средствами библиотеки из состава Visual Studio Работая над Windows XP, мы с членами команд Secure Windows Initiative Team и Windows Security Penetration Team потратили немало времени на поиск пустых DACL, уведомление авторов программ и борьбу за устранение подобных брешей.

Впоследствии мы проанализировали, почему же программисты создают объекты с нулевыми DACL, и обнаружили две причины:

• программисты «тонут» в огромном объеме кода, необходимого для создания списков ACL. Хочется надеяться, что вы освоили по крайней мере один из при веденных ранее трех примеров и сможете запрограммировать нужные ACL;

• программисты считают нулевые DACL вполне удовлетворительными, потому что код прекрасно работает с такими DACL объектами. Теперь то вы понимаете, что так делать нельзя — если программа служит прекрасную службу пользователям, то очень вероятно, что таким же чудесным образом она открывает свет хакеру!

Честно говоря, в основе обеих причин — лень или недостаток опыта и про фессионализма. Верно, над качественной ACL придется но овчинка стоит выделки. Если продукт падет жертвой хакера из-за плохо реализованного ACL, вам все равно придется выпускать «заплату». Так почему бы не уничтожить брешь в зародыше.

Примечание Нулевая DACL и нулевой дескриптор безопасности — это большие разницы». Если при создании объекта установить дескриптор в NULL, ОС создаст дескриптор безопасности по умолчанию с DACL, обыч но это от родительского объекта.

Как-то я написал простую Perl-утилиту для поиска нулевых DACL в исходном коде на C++ и С и ею исходные тексты, полученные от одного из парт неров Microsoft. Я нашел около дюжины нулевых DACL, зарегистрировал эти ошиб ки в системе а после исправления снова пробежался по коду утили той — нулевые DACL пропали. Почти тремя месяцами позже, анализируя исход ный текст программы на предмет безопасности, я обнаружил странные исправ ления где раньше были нулевые DACL. До исправления код выглядел так:

TRUE, // DACL FALSE);

а после (утилита не увидела здесь подвоха) — так:

TRUE, // DACL FALSE);

ГЛАВА 6 Выбор механизма управления доступом Этот трюк но и остроумен одновременно. Если функция не в со стоянии выделить требуемый блок памяти, она возвращает NULL. Программист пытается выделить или 4 294 967 295 байт, данных, что на н машин приведет к ошибке, и DACL установится в Пришлось серьезно поговорить с программистом, «исправившим» ошибку, и, конечно же, повторно «открыть» все ошибки в системе управления разработкой. Я не успокоился, все не залатали самым тщательным образом.

Нулевая DACL и аудит У пулевых DACL есть одна коварная особенность: если пользователь (даже «легальный») изменит ее на Everyone (Deny: Access), то очень высока вероятность того, что в журнал событий Windows эта операция не попадет и это ное действие незамеченным. Причина в том, что почти наверняка аудит объекта с такой DACL отключен — ведь SACL также Внимание! Нулевая DACL представляет прямую опасность. Обнаружив ее, не медленно проинформируйте об ошибке и добейтесь, чтобы ее устранили.

Опасные типы АСЕ Следует опасаться АСЕ-записей трех типов: Everyone Everyone и ACL, разрешающих добавлять в каталог исполняемые программь.

Everyone (WRITE_DAC) — право изменять DACL в дескрипторе безопасности объекта. Полу чив возможность изменять пользователь может назначить ничем не ограниченный доступ к объекту и его для остальных, Everyone — право изменять владельца в дескрипторе безопасности та. По определению, владелец объекта может делать с ним все, что утодно. Недо бросовестный пользователь, присвоивший владение объектом, получает ничный доступ и возможность закрыть объект для доступа других пользователе Everyone АСЕ с Everyone особенно опасна, потому что позволяет ненадеж ным пользователям добавлять в файловую систему свои исполняемые мы. в том, что атакующий может разместить опасный файл в нужном каталоге и дождаться, когда администратор запустит программу на Короче говоря, никогда не разрешайте пользователям, которым не особо доверн ете, файлы в общие каталоги приложения.

Everyone (DELETE) АСЕ с таким разрешением позволяет удалить объект вашего приложения любому, а этого нельзя, особенно которым вы не особенно до веряете, Часть II Методы безопасного кодирования Everyone (FILE_DELETE_CHILD) Это разрешение отображается в пользовательском интерфейсе Windows как Delete and (Удаление подпапок и файлов) и позволяет уда лять дочерний объект, например файл, даже если у него нет к нему доступа. Об ладая разрешением на родительском объекте, вы вправе уда лить любой дочерний объект независимо от разрешений последнего.

Everyone Разрешение или Full Control (Полный доступ), столь же опасно, как и NULL DACL. Лучше его не применять.

Что делать, если нельзя изменить нулевую DACL Мне сложно представить причину, по которой надо создавать объект с нулевой DACL, кроме случая, когда совершенно неважно, скомпрометирован ли объект.

Подобная ситуация мне встретилась лишь однажды: в приложении время от мени открывалось диалоговое окно с анекдотом. Для использовался мьютекс с пустой который предотвращал одновременное отображение окна несколькими копиями программы. Даже если взломщик установит на этом объекте запрещающую АСЕ, невелика потеря пользователь лишиться очередного анек дота, только и всего!

Как обязательный минимум следует создавать ACL, который не позволяет всем пользователям:

• перезаписывать DACL объекта • изменять владельца объекта [Everyone • удалять объект [Even-one Маска доступа зависит от вида объекта, например, для раздела реестра нужна такая маска:

DWORD = KEY_ALL_ACCESS & & & -DELETE;

а для файла или каталога другая:

DWORD dwFlags = FILE_ALL_ACCESS & & & Другие механизмы управления доступом Списки ACL — полезный, но далеко не единственный метод защиты ресурсов.

Наиболее популярны роли Framework или СОМ+, IP-ограничения, а также триггеры и разрешения SQL Server. От списков их отличает то, что они привяза ны к определенным а АС1. — неотъемлемый компонент ОС, ГЛАВА 6 Выбор управления доступом Роли часто применяются в финансовых или бизнес-приложениях для управ ления приложением на политик, для ограничения размера закции в зависимости от роли пользователя, ее выполняющего. Верхний размера транзакции у рядовых сотрудников невысок, у — чуть боль ше, а самый высокий (или вообще неограниченный) — у вице-президентов. Так же часто применяют основанную на ролях систему безопасности, например, ког да для выполнения операции требуется получить «визу» у многих субъектов. По добная ситуация возможна в системе закупок: любой сотрудник вправе ввести на закупку, но только менеджеру разрешается вывать запрос в реальный заказ, отправляемый поставщику.

Определение ролей зависит от логики приложения, впрочем, как и условия, три которых разрешены те или иные операции.

В Windows-программах реализованы два механизма создания ролей: в Framework и СОМ+. Сейчас я познакомлю вас с ними поближе, Роли в Framework Основанный на ролях механизм безопасности в Framework авторизацию, предоставляя потоку программы информацию об участнике пасности (principal). информация (и определяемый ею уча стник безопасности) может быть учетной записью Windows или Приложения Framework решения об авторизации на основе идентификационных данных участника безопасности и/или его роли. Роль — это поименованная участников которые обладают наковыми привилегиями с точки зрения системы безопасности (например или Участник безопасности может обладать одной или несколькими ролями. Таким образом, в приложениях роли позволяют ро выяснять права пользователя на выполнение тех или иных операций, Примечание Полное описание ролей выходит за рамки кни ги. За более подробной информацией рекомендую обратиться к книгам следующих авторов (см. библиографический список): или • chia, и др, Для простоты и совместимости с механизмами управления доступом к коду основанная на ролях защита в Framework реализована в виде объектов которых CLR-среда (Common Language Runtime) выполняет ав торизацию по такой же схеме, что и проверка безопасности доступа к коду. Класс представляет личность или роль пользователя, которая до на проходить как декларативные, так и обязательные проверки системой безопас ности. Бы также вправе проверять идентификационную информацию напрямую и, если выполнять проверку роли и личности прямо из программы, Вот отрывок программы, демонстрирующий применение ролей Framework в Web-сервисе или на Web-странице:

WindowsPrincipal as if ( { 172 Часть II Методы безопасного кодирования // Пользователь проходит авторизацию функций менеджера.

Аналогичную операцию разрешается выполнять с текущим потоком:

principal = as WindowsPrincipal);

if { // Пользователь Заметьте: позволяет выяснить, ли вызываю щая программа членом a — определить, разрешена ли программе универсальная роль, причем сведения о составе роли обычно хранятся в специальной базе данных или конфигурационном файле.

Конструктор позволяет определить, какие роли выделены участ безопасности. Вот пример на С#, демонстрирующий это:

id = new // Список ролей из XML-файла или базы roles = GenericPrincipal principal = new Роли в СОМ+ В COM+ роли напоминают группы однако создает роли и определяет их состав не сетевой администратор. Автор приложения создает роли в процессе а их пользователями администратор приложения при раз вертывании. Таким образом, удается добиться большей гибкости, потому что се тевые группы и роли в приложении хотя и связаны, но, тем не менее, независи мы, что предоставляет администратору огромное пространство для творчества, Роли СОМ+ создаются на прикладном уровне средствами оснастки Component Services (Службы компонентов) или методом Как его вызывать, показано в примере на Visual Basic:

' Получаем контекст вызова безопасности.

Dim As Boolean Dim objCallCtx As SecurityCallContext Set objCallCtx = Выполняем проверку роли.

fAllowed = If (fAllowed) Then ' Действуем в соответствии с полученным результатом.

End If В отличие от списков ACL, которые защищают ресурсы, роли защищают то есть тот участок программы, из которого выполняется к защищаемому ресурсу. С логикой роли в коде можно совмещать другие бизнес-правила, опре деляющие порядок доступа.

ГЛАВА б Выбор механизма управления доступом = = If (flsDoctor And flsOnDuty) Then задач, для которых нужен не просто ' а дежурный доктор на посту.

End If Комбинирование бизнес-логики и основанных на ролях разрешений — мощ ная и исключительно полезная возможность.

IP-ограничения поддерживаются большинством Web-серверов, в том числе IIS. Разработчи ки или применяют их для ограничения доступа к отдельным частям Web-сайта, отдельным IP-адресам (например, DNS-именам (www.microsoft.com) и доменным именам При создании Web-приложений мы рекомендуем не забывать о возмож ности использования например для определения круга страторов путем ограничения перечня машин, с которых разрешается выполнять администрирование, Если в процессе анализа и прав доступа вы обнаружите формулировку типа «доступно только с локальной машины» или «запретить до ступ всем пользователям и компьютерам из домена мой вам совет: воспользуйтесь IP-ограничениями.

IP-ограничения иногда оказываются как нельзя кстати, если нужно включить какую-то функцию приложения по умолчанию и вместе с тем не позволить взлом щику воспользоваться ею. Задача решается просто: достаточно установить 1Р-ог] >а на специально созданный виртуальный каталог, разрешив исполнение кода только на локальной машине (127.0.0.1).

Внимание! Если надо разрешить по умолчанию опасную Web-функцию, опре деляйте исполнять код только по IP-ад ресу 127.0.0.1.

В следующем примере на VBScript показано, как установить IP-ограничения на виртуальном каталоге Samples Web-сервера по умолчанию так, чтобы тот ся доступным только с localhost (то есть с зарезервированного адреса параметры протокола IP.

Dim Dim Set oVDir = oIP = ' список разрешенных IP-адресов с одним элементом - 127.0.0.1.

Dim = "127.0.0.1" = ' Запретить доступ по = False Часть II Методы безопасного кодирования ' Сохраняем изменения на IIS ' и обнуляем переменную, = Set oIP = Nothing Set oVDir = Nothing Триггеры и разрешения сервера SQL Server Триггеры SQL Server позволяют разработчику размещать правила произвольной сложности в таблицах базы данных. Ядро базы данных автоматически вызывает триггеры при удалении или изменении данных в таблицах. Имейте в виду: триггеры не работают при чтении данных. Это не очень так как хотелось бы предусмотреть в приложении определенную логику управления до ступом на основе триггеров, то есть что-то типа разрешений. Но увы, триггеры не реагируют на чтение.

Разрешения SQL Server — это аналоги списков ACL в Windows, а механизм их действия можно выразить простой фразой: «субъекту разрешается (или запреща ется) выполнять определенные операции над Вот примеры: «Пользо вателю Blake разрешается читать данные из таблицы Accounts рам разрешается читать, писать и удалять данные из таблицы (журнал Все объекты в SQL Server можно защитить с помощью разрешений.

Пример приложения для поликлиники Сейчас я покажу вам, как применять отличные от ACL методы управления досту пом. Это упрощенный вариант реального ПО для поликлиники. При сборе тре бований выяснилось, что действует следующий порядок обновления докторами медицинской карты пациента.

По доктор отыскивает медицинскую карту пациента, изучает ее, а затем добавляет в нее новые дан ные;

в этот момент в аудита создается об из менении медицинской карты. Рядовым и старшим медсестрам, а также докторам разрешается читать записи о выписанных пациенту лекарствах, а состав лекарств вправе только старшие и доктора. Доступ к информации о выпи санных лекарствах также подлежит аудиту. Право на чтение журналов аудита предоставляется только аудиторам, причем запрещено совмещать свои функции и работу то есть им нельзя читать или изменять данные аудита.

В рамках принятых допущений означает то же, что и Таким образом, мы готовы определить следующую политику к инфор мации о пациентах:

• группа право на чтение и изменение.

Следующая политика на основе правил обращения с медицинс кими данными пациентов:

ГЛАВА Выбор управления доступом • группа «Доктора»: право на чтение и изменение;

• группа «Старшие медсестры»: на чтение и изменение;

• группа право на чтение.

И последняя политика вытекает из работы с журналом аудита:

• группа запрещено чтение и изменение:

• группа полный доступ;

• группа «Все»: право на запись.

В этом примере для деления по группам старших и рядовых медсе стер можно воспользоваться группами Windows или SQL Server, а также (Заметьте: при изменении условий разрешения могут также изменения.) Здесь важно понять: защиту ресурсов не обязательно ь с помощью ACL. Хороший пример — данные, хранимые на SQL Server;

в этом случае вся информация о пациентах размещается в базе данных, как и журнал аудита.

Преимущество способа на основе сценариев использования системы чается в независимости политики управления доступом от реализации. Например, политику можно реализовать одними триггерами таблиц сервера SQL Server. Бот пример срабатывающего при попытке изменить или удалить журнала аудита. Если пользователь не входит в группу откатывается, create trigger on update, delete as begin if rollback tran Обратите внимание, что триггеры не вызываются при записи данных в нал аудита, так как в соответствии с бизнес-правилами запись разрешается всем.

Однако такое решение не лишено недостатков: читать информацию журнала аудита могут все, так как триггеры не реагируют на чтение. В этом случае логично при менить к таблице разрешение, например: пользователям (public) раз решается только запись в «Обычные пользователи» — это то же, что и группа Everyone в Журналы аудита очень важны, поэтому два уровня за щиты здесь совсем нелишни. Помните: «защита на всех уровнях»! В данном с разрешения таблицы и триггеры работают в надежно закрывая жур нал от злоумышленников (даже если администратор случайно удалит разрешение из контрольной таблицы) и обеспечивая защиту «в глубину».

Важное замечание по поводу механизмов управления доступом Не встроенные в операционную систему механизмы управления доступом опас ны тем, что иногда приводят к образованию уязвимых мест. Сейчас поясню чему. На рис. 6-3 показана схема системы защиты ресурсов с применением ограничений Web-сервера.

Часть II Методы безопасного кодирования Взломщик Служба доступа к файлам и Т Ресурсы Рис. 6-3. Защита ресурсов с помощью Проблема кроется в том. что в системе работает служба доступа к файлам. Если взломщику удастся получить доступ к ней, он сможет обойти IP-ограничения, так как ничего «не о них.

Внимание! Проектируя механизмы доступом, следите за что бы не оставить в системе позволяющих обойти вашу защиту.

Вот наглядный пример. Когда я работал в группе создателей в одной из групп нашей!) организовали частный Web-сайт для закрытого (только для ее чле нов) просмотра фильма «Звездные войны: Эпизод I — Призрачная угроза». Мы посчитали себя обделенными и решили себя на самосто ятельно! Ранее, анализируя Web-сервер, мы обнаружили, что логика определения принадлежности пользователя к той или иной группе реализована в коде Web страницы. Немного усилий — и нам удалось что Web-страницы хранятся в общем файловом Мы попробовали подключиться к общему ресур су — стоит ли говорить, что списки ACL файлов Web-сайта оказались на удивле ние Так что мы без проблем зарегистрировали свою группу и с удо вольствием посмотрели фильм!

Внимание! Способов управления доступом в приложении множество: списки ACL, разрешения SQL Server, IP-ограничения и роли. Очень вниматель но выбирайте технологию для своего продукта, а в некоторых случаях совмещайте технологии, размещая их на разных уровнях, например списки и IP-ограничения, — на случай компрометации или непра вильной настройки одного из уровней.

Резюме В отсутствие реализации шифрования и управления ключами списки становятся последним форпостом защиты, способным остановить прорвав шего остальные заслоны взломщика- Иногда один качественный ACL на защища емом объекте компрометацию целой сети. Помните о принципах защиты на всех описанных в главе 3, и используйте списки ACL для со здания надежной и эффективной многоуровневой системы безопасности, Принцип минимальных привилегий О области безопасности действует принцип: задачи всегда следует исполнять с минимально возможным набором привилегий. Отпилить кусок пластиковой тру бы можно ножовкой или мощной электропилой — обе годятся, но вторая избыточна для такой задачи. Если что-то пойдет не электропилой вы безна дежно испортите трубу, ножовка же подойдет идеально. То же верно в отноше нии программных процессов — их рекомендуется выполнять с привилегиями как раз достаточными для конкретной и не более того.

Принцип привилегий предполагает также сокращение времени работы с повышенными полномочиями — это уменьшает интервал, когда злоумыш ленник может воспользоваться недостатками системы. В Windows дополнитель ные права разрешается назначить прямо перед выполнением задачи, а после снова отозвать их. В нашем примере постоянная работа с повышенными привилегия ми похожа на постоянно включенную электропилу на кухне. Понятно, что это чрезвычайно опасно!

Любая серьезная программная ошибка (например подверженность перепол нению буфера), обнаруженная и использованная злоумышленником, меньше вреда, если программа работает с низкими привилегиями. Проблемы никают, когда пользователи случайно или неумышленно запускают на исполне вредоносный код (например «троянцев» во вложениях сообщений электрон ной почты или код, проникающий в систему, используя переполнение который исполняется с привилегиями самого пользователя. Иначе говоря, про цесс, созданный при запуске наследует все права пользователя, кото рый его вызвал. Кроме того, если пользователь является членом локальной груп пы (Администраторы), вредоносный код в принципе имеет возмож 178 Часть II Методы безопасного кодирования ность получить все системные привилегии и полный доступ к объектам. В этом случае возможность ущерба возрастает многократно, Вы себе не представляете, как часто я сталкиваюсь с программами, которые исполняются в администраторском контексте безопасности или, того хуже, в виде системной службы. Если немного подумать и правильно спроектировать программу.

ей не потребуются для работы столь высокие полномочия. В этой главе мы скажем, почему разработчики предпочитают, чтобы их программы работали с высокими привилегиями, а также обсудим более важный вопрос — как какие права требуются для и безопасного выполнения программы.

Внимание! Некоторым приложениям привилегии администратора все-таки нужны — это средства администрирования и программные средства, которые влияют на работу операционной системы.

В двух словах о вирусах, и троянский — программа с или скрытыми возможностями, причем обычно разрушительными.

Вирусом называется программа, которая копирует себя и свой вредонос ный код на машины пользователей. А червь — это компьютерная програм ма (обычно что позволяет ей выживать), которая по ражает компьютеры сети и мешает работе. Собирательное название та рода программ — вредоносное ПО Прежде чем заняться технической стороной принципа наименьших приви посмотрим, чем в реальности ситуации, когда приложения рабо тают с администраторскими правами или, хуже, с привилегиями системно го процесса.

Ущерб от вредоносного ПО Можно притвориться воткнуть голову в песок и сделать вид, что ниче го такого не происходит, но на самом деле в Интернете полно «плохих парней», жаждущих «крови» пользователей. Большинство атак закончились бы ничем, если бы программы не запускались с правами привилегированных учетных записей.

Два наиболее популярных вида атак в Интернете сегодня — распространение вирусов или троянцев и уродование (defacement) Web-сайтов. Мы по дробно расскажем о каждой из этих атак и объясним, как наносимый ими урон можно уменьшить, запуская приложения с правами обычных пользователей.

Вирусы и троянцы И и троянцы содержат вредоносный код, который пользователи запуска ют на исполнение неумышленно. Когда мы познакомим вас с некоторыми таки ми программами, вы увидите, что усилия враждебного кода тщетны, если у запус кающего его пользователя нет административных полномочий.

ГЛАВА 7 Принцип минимальных привилегий Средство удаленного управления Back Orifice на компьютере, этот троянец позволяет удаленному нику контролировать компьютер: перегружать, запускать просмат содержимое файлов — и все это без ведома пользователя. При Back Orifice пытается выполнить запись в системный каталог Windows и в ряд параметров реестра, в том числе в раздел Обе задачи доступны только администра го рам. Если пользователь не входит в группу залезть в систему Back Orifice попросту не сможет.

Средство удаленного управления SubSeven Как и Back Orifice, SubSeven скрыто предоставляет злоумышленнику доступ к >м через Интернет. В процессе работы SubSeven копирует себя в каталог Windows, корректирует файлы Win.ini и а также изменяет па раметры служб в ветках реестра и HKEY Эти операции доступны только администратору. Опять же, если — не администратор, SubSeven не удастся «прижиться» в системе, Вирус Вирус FunLove, по классификации Symantec — W32.Funlove.4099, использует ме тод, впервые примененный в вирусе W32.Bolzano. Он изменяет на зараженном компьютере код ядра, ответственный за контроль доступа и, таким образом, пре доставляет пользователям права на все файлы. FunLove записывает файл в систем ный каталог и корректирует ядро Windows NT — Ntoskrnl.exe. Отсутствие у пользо вателя полномочия администратора не позволяет FunLove изменить нужные и вирусу не удается заразить компьютер, Это, наверное, самый знаменитый из вирусов и троянцев, его еще называют или The Love распространяется за счет недостатков Microsoft Outlook. Вирус действует так копирует себя в системный каталог, а затем ся изменить записи в разделе HKEY_LOCAL_MACHINE реестра. И эта вредоносная программа бессильна, если у пользователя нет привилегий администратора.

Изменение страниц Web-сайтов Порча страниц особенно широко — любимое развлече ние вандалов-любителей (script kiddies). Они часто атакуют Web-серверы с Internet Information Services используя переполнение буфера в реализации протоко ла печати (Internet Printing Protocol, IPP) в Microsoft Windows 2000.

Pages:     | 1 | 2 || 4 | 5 |   ...   | 10 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.