19 смертных грехов, угрожающих безопасности программ
Шрифт:
□ блокируйте доставку сигналов на время нахождения в обработчике сигнала.
Для решения проблемы «момент проверки / момент использования» (TOCTOU) лучше всего создавать файлы в таком месте, куда обычные пользователи не имеют права ничего записывать. В случае каталогов такое не всегда возможно. При программировании на платформе Windows не забывайте, что с файлом (как и с любым другим объектом) можно связать дескриптор безопасности в момент создания. Задание прав доступа в момент создания объекта устраняет возможность гонки между моментами создания и определения прав доступа. Чтобы избегнуть гонки между моментом проверки существования и объекта и моментом создания нового объекта, у вас есть несколько вариантов, зависящих от типа объекта. В случае файлов самое правильное – задать флаг CREATE_NEW при вызове функции CreateFile.
HANDLE hMutex = CreateMutex(... аргументы ...);
if (hMutex == NULL)
return false;
if (GetLastError == ERROR_ALREADY_EXISTS)
{
CloseHandle(hMutex);
return false;
}
Дополнительные защитные меры
Старайтесь вообще избегнуть проблемы, создавая временные файлы в области, выделенной конкретному пользователю, а не в общедоступной. Всегда пишите реентерабельный код, даже если программа не является многопоточной. Если кто–то захочет перенести ее на другую платформу, то его задача значительно упростится.
Другие ресурсы
□ «Resource contention can be used against you» by David Wheeler: www–106. ibm.com/developerworks/linux/library/l–sprace.html?ca=dgr–lnxw07RACE
□ RAZOR research topics: http://razor.bindview.com/publish/papers/signals.txt
□ «Delivering Signals for Fun and Profit: Understanding, Exploiting and Preventing Signal Handling Related Vulnerabilities» by Michal Zalewski: www.bindview.com/Services/Razor/Papers/2001/signals.cfm
Резюме
Рекомендуется
□ Пишите код, в котором нет побочных эффектов.
□ Будьте очень внимательны при написании обработчиков сигналов.
Не рекомендуется
□ Не модифицируйте глобальные ресурсы, не захватив предварительно замок.
Стоит подумать
□ О том, чтобы создавать временные файлы в области, выделенной конкретному пользователю, а не в области, доступной всем для записи.
Грех 17. Неаутентифицированный обмен ключами
В чем состоит грех
Да, я хочу защитить сетевой трафик! Конфиденциальность? Целостность сообщений? Отлично! Я буду пользоваться «впишите свое любимое готовое решение». Э, стоп… Необходимо ведь, чтобы у обеих сторон был общий секретный ключ. А как это сделать?
«Знаю! Я возьму другое готовое решение или напишу сам. Что сказано по этому поводу в книге ^Applied Cryptography* («Прикладная криптография»)? Ага, нашел… Применю–ка я протокол обмена ключами Диффи–Хеллмана. А может быть, даже воспользуюсь SSL или TLS».
Примерно так люди и рассуждают, готовясь реализовать какое–нибудь криптографическое решение, но забывая оценить все сопутствующие риски. Проблема в том, что к безопасности процедуры обмена ключами тоже предъявляются определенные требования: обмениваемые ключи необходимо держать в секрете, и, что еще важнее, все сообщения, передаваемые по протоколу, должны быть надежно аутентифицированы. Иными словами, нужно иметь гарантию, что
каждая сторона точно знает, кому вручает свой ключ. Поразительно, как часто это условие не выполняется! Аутентификация пользователей, после того как обмен ключами состоялся, обычно не решает проблему.Подверженные греху языки
Все языки подвержены этому греху.
Как происходит грехопадение
Эксперты по безопасности (включая и авторов) всегда предостерегают программистов от разработки собственных криптографических алгоритмов и протоколов. Обычно они внемлют этому совету. Когда перед разработчиком встает задача обеспечить защиту сетевых соединений и он осознает, что необходимо какое–то соглашение о ключах, то, как правило, применяет SSL или берет протокол, описанный в книге Брюса Шнейера «Прикладная криптография». Но на этом пути расставлено много силков и капканов.
Немало ошибок можно совершить в процедуре инициализации сеанса. Одна из самых распространенных опасностей – это атака с «человеком посередине». Рассмотрим ее на конкретном примере типичного приложения клиент / сервер, в котором клиент или сервер применяет протокол обмена ключами Диффи–Хел–лмана, а затем аутентифицируют друг друга с помощью выработанного ключа. Детали алгоритма Диффи–Хеллмана несущественны. Достаточно знать, что в этой схеме требуется, чтобы две стороны послали друг другу сообщения, основанные на случайно выбранном секрете. Обладая любым секретом и одним из открытых сообщений, можно вычислить третий секрет (первые два были случайно выбраны сторонами). Предполагается, что определить третий секрет, не зная хотя бы одного их первых двух, – очень трудоемкая вычислительная задача. Третий секрет обычно называют ключом, а процесс его выработки – обменом ключами. На первый взгляд, все замечательно, так как, не зная ни одного из исходных секретов, противник не может вычислить ключ.
Но, если не включить в схему дополнительные механизмы защиты, мы столкнемся с серьезной проблемой. Дело в том, что вся схема уязвима для атаки с «человеком посередине». Предположим, что клиент начал сеанс, но вместо сервера ему ответил противник. В протоколе нет способа определить, общается ли клиент с корректным сервером. На практике противник легко может занять место сервера, а затем обменяться с сервером ключами, действуя в качестве посредника при передаче законного трафика.
Корень проблемы в том, что обмен ключами не аутентифицирован. Стойте! Мы же сказали, что собираемся воспользоваться протоколом аутентификации, как только установим защищенное соединение! Мы могли бы взять ключ, выработанный по схеме Диффи–Хеллмана, и с ним организовать протокол проверки пароля. Совсем хорошо будет, если этот протокол реализует взаимную аутентификацию, то есть клиент и сервер аутентифицируют друг друга.
Ах, если бы таким образом можно было решить проблему!
Представьте, что в «середине» протокола аутентификации находится противник, и он не делает ничего, кроме подслушивания. Предположим, что противник не знает пароля и не получает никакой информации о нем из протокола (быть может, мы применяем схему с одноразовым паролем, например S/KEY). Что доказывает протокол аутентификации? Лишь тот факт, что никто не пытался изменить сообщения протокола (то есть сообщения аутентичны). Даже если противник все подслушал, «аутентификация» состоялась.
А что осталось недоказанным? Что аутентичными были сообщения, передаваемые в ходе исполнения протокола обмена ключами! Таким образом, после завершения аутентификации мы по–прежнему пользуемся неаутентифицированным ключом, которым перед этим обменялись с противником. Этот ключ годится для шифрования и дешифрирования всего трафика, так что у нас нет оснований полагать, что сообщения защищены.
Чтобы организовать защищенный сеанс, обе стороны обычно должны удостовериться в «личности» собеседника (хотя в некоторых случаях одна из сторон может быть анонимной). Личность должна быть уже удостоверена к моменту начала исполнения протокола обмена ключами. При этом каждое последующее сообщение посылается с ключом (требование аутентичности сохраняется на все время сеанса, хотя часто мы называем это целостностью сообщений).