Джон Виега - 19 смертных грехов, угрожающих безопасности программ стр 3.

Шрифт
Фон

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

Майкл Ховард, Дэвид Лебланк, Джон Виега, июль 2005 г.

Грех 1.Переполнение буфера

В чем состоит грех

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

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

Результатом переполнения буфера может стать что угодноот краха программы до получения противником полного контроля над приложением, а если приложение запущено от имени пользователя с высоким уровнем доступа (root, Administrator или System), то и над всей операционной системой и другими пользователями. Если рассматриваемое приложениеэто сетевая служба, то ошибка может привести к распространению червя. Первый получивший широкую известность Интернетчервь эксплуатировал ошибку в сервере finger, он так и назывался«fingerчервь Роберта Т. Морриса» (или просто «червь Морриса»). Казалось бы, что после того как в 1988 году Интернет был поставлен на колени, мы уже должны научиться избегать переполнения буфера, но и сейчас нередко появляются сообщения о такого рода ошибках в самых разных программах.

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

Подверженные греху языки

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

В языках более высокого уровня, появившихся позже, программист уже не имеет прямого доступа к памяти, хотя за это и приходится расплачиваться производительностью. В такие языки, как Java, С# и Visual Basic, уже встроены строковый тип, массивы с контролем выхода за границы и запрет на прямой доступ к памяти (в стандартном режиме). Ктото может сказать, что в таких языках переполнение буфера невозможно, но правильнее было бы считать, что оно лишь гораздо менее вероятно. Ведь в большинстве своем эти языки реализованы на С или С++, а ошибка в реализации может стать причиной переполнения буфера. Еще один потенциальный источник проблемы заключается в том, что на какойто стадии все эти высокоуровневые языки должны обращаться к операционной системе, а уж онато почти наверняка написана на С или С++. Язык С# позволяет обойти стандартные механизмы .NET, объявив небезопасный участок с помощью ключевого слова unsafe. Да, это упрощает взаимодействие с операционной системой и библиотеками, написанными на C/C++, но одновременно открывает возможность допустить обычные для C/C++ ошибки. Даже если вы программируете преимущественно на языках высокого уровня, не отказывайтесь от тщательного контроля данных, передаваемых внешним библиотекам, если не хотите пасть жертвой содержащихся в них ошибок.

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

Как происходит грехопадение

Классическое проявление переполнения буфераэто затирание стека. В откомпилированной программе стек используется для хранения управляющей информации (например, аргументов). Здесь находится также адрес возврата из функции и, поскольку число регистров в процессорах семейства х86 невелико, сюда же перед входом в функцию помещаются регистры для временного хранения. Увы, в стеке же выделяется память для локальных переменных. Иногда их неправильно называют статически распределенными в противоположность динамической памяти, выделенной из кучи. Когда ктото говорит о переполнении статического буфера, он чаще всего имеет в виду переполнение буфера в стеке. Суть проблемы в том, что если приложение пытается писать за границей массива, распределенного в стеке, то противник получает возможность изменить управляющую информацию. А это уже половина успеха, ведь цель противникамодифицировать управляющие данные по своему усмотрению.

Возникает вопрос: почему мы продолжаем пользоваться столь очевидно опасной системой? Избежать проблемы, по крайней мере частично, можно было бы, перейдя на 64разрядный процессор Intel Itanium, где адрес возврата хранится в регистре. Но тогда пришлось бы смириться с утратой обратной совместимости, хотя на момент работы над этой книгой представляется, что процессор х64 в конце концов станет популярным.

Можно также спросить, почему мы не переходим на языки, осуществляющие строгий контроль массивов и запрещающие прямую работу с памятью. Дело в том, что для многих приложений производительность высокоуровневых языков недостаточно высока. Возможен компромисс: писать интерфейсные части программ, с которыми взаимодействуют пользователи, на языке высокого уровня, а основную часть кодана низкоуровневом языке. Другое решениев полной мере задействовать возможности С++ и пользоваться написанными для него библиотеками для работы со строками и контейнерными классами. Например, в Webсервере Internet Information Server (IIS) 6.0 обработка всех входных данных переписана с использованием строковых классов; один отважный разработчик даже заявил, что даст отрезать себе мизинец, если в его коде отыщется хотя бы одно переполнение буфера. Пока что мизинец остался при нем, и за два года после выхода этого сервера не было опубликовано ни одного сообщения о проблемах с его безопасностью. Поскольку современные компиляторы умеют работать с шаблонными классами, на С++ теперь можно создавать очень эффективный код.

Но довольно теории, рассмотрим пример.

tinclude <stdio.h>

void DontDoIhis (char* input)

{

char buf[16];

strcpy(buf, input);

printf("%s\n» , buf);

}

int main(int argc, char* argv[])

{

// мы не проверяем аргументы

// а чего еще ожидать от программы, в которой используется

// функция strcpy?

DontDoThis(argv[l]);

return 0;

}

Откомпилируем эту программу и посмотрим, что произойдет. Для демонстрации автор собрал приложение, включив отладочные символы и отключив контроль стека. Хороший компилятор предпочел бы встроить такую короткую функцию, как DontDoThis, особенно если она вызывается только один раз, поэтому оптимизация также была отключена. Вот как выглядит стек непосредственно перед вызовом strcpy:

Ваша оценка очень важна

0
Шрифт
Фон

Помогите Вашим друзьям узнать о библиотеке

Скачать книгу

Если нет возможности читать онлайн, скачайте книгу файлом для электронной книжки и читайте офлайн.

fb2.zip txt txt.zip rtf.zip a4.pdf a6.pdf mobi.prc epub ios.epub fb3