Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #58, СЕНТЯБРЬ 2005 г.

Инструктаж перед боем

Крис Касперски

Спецвыпуск: Хакер, номер #058, стр. 058-018-1


ака мыщъх (no e-mail)

Ошибки клиентских приложений

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

Долгое время вопросам безопасности клиентских приложений не уделялось никакого внимания, и их писали кое-как. В самом деле, кто вдруг заинтересуется атакой на пользователя? Серверы - другое дело! Однако сейчас все изменилось. Сотни миллионов клиентских машин - это солидный ломоть интернет-пирога, намного более лакомый, чем кучка серверов, охраняемых агрессивными церберами (они же админы). Это море конфиденциальной информации, россыпи электронных кошельков и целая армия ботнетов, сопоставимая по своей мощи с лучшими суперкомпьютерами, которыми только располагает Пентагон. И самое главное: клиентские компьютеры ничем не защищены. Чем вооружен типичный пользователь? Правильно, мышью :).

Свобода без границ, или переполнение

Среди всех типов ошибок по-прежнему лидирует переполнение буфера, характерное в основном для С/С++ приложений, но также встречающееся на DELPHI или Паскаль. Причина - банальная невнимательность, неряшливость и небрежность при программировании. Современные приложения используют сотни тысяч буферов, и при бешеных темпах разработки очень трудно уследить за всем этим хозяйством. Нет, я не собираюсь призывать девелоперов к порядку. Человеку свойственного ошибаться. Это факт. Многие руководства по безопасности рекомендуют: "Прежде чем класть что-то в буфер, проверьте, влезет ли". Например, объединение двух строк может выглядеть так:

(Традиционный, но глубоко неправильный способ контроля границ буферов)

if (strlen(FirsName)+strlen(" ")+strlen(LastName)) < BUF_SIZE)

spintf(buf,"%s %s", FirstName, LastName);

else

goto Error;

Это неправильно! И вот почему: контроль длины - ОЧЕНЬ прожорливая операция, отнимающая уйму процессорного времени, но не дающая никаких гарантий, так как за корректностью кода следит не машина, а программист! К тому же не совсем понятно, как обрабатывать ситуацию с нехваткой памяти. Аварийно прекращать работу приложения - так это настоящий DoS получается! А чтобы корректно сохранить все несохраненные данные, придется вводить поддержку транзакций, чтобы значительно усложнит архитектуру программы.

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

(Еще один неправильный способ контроля границ)

snprintf(buf, BUF_SIZE, "%s %s", FirstName, LastName);

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

Содержание  Вперед на стр. 058-018-2