19 смертных грехов, угрожающих безопасности программ
Шрифт:
□ используйте в обработчиках сигналов только реентерабельные библиотечные функции. Для этого многие известные программы придется значительно переработать. Есть, правда, половинчатое решение – написать для каждой небезопасной библиотечной функции обертывающий код, который будет проверять некоторый глобальный флаг во избежание повторного входа;
□ блокируйте доставку сигналов на время выполнения неатомарных операций и проектируйте обработчики сигналов так, чтобы они не зависели от внутреннего состояния программы (например, обработчик может безусловно поднять некоторый флаг и этим ограничиться);
□ блокируйте доставку сигналов на время нахождения в обработчике сигнала.
Для решения проблемы «момент проверки / момент
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 или берет протокол, описанный в книге Брюса Шнейера «Прикладная криптография». Но на этом пути расставлено много силков и капканов.
Немало ошибок можно совершить в процедуре инициализации сеанса. Одна из самых распространенных опасностей – это атака с «человеком посередине». Рассмотрим ее на конкретном примере типичного приложения клиент / сервер, в котором клиент или сервер применяет протокол обмена ключами Диффи–Хел–лмана, а затем аутентифицируют друг друга с помощью выработанного ключа. Детали алгоритма Диффи–Хеллмана несущественны. Достаточно знать, что в этой схеме требуется, чтобы две стороны послали друг другу сообщения, основанные на случайно выбранном секрете. Обладая любым секретом и одним из открытых сообщений, можно вычислить третий секрет (первые два были случайно выбраны сторонами). Предполагается, что определить третий секрет, не зная хотя бы одного их первых двух, – очень трудоемкая вычислительная задача. Третий секрет обычно называют ключом, а процесс его выработки – обменом ключами. На первый взгляд, все замечательно, так как, не зная ни одного из исходных секретов, противник не может вычислить ключ.
Но, если не включить в схему дополнительные механизмы защиты, мы столкнемся с серьезной проблемой. Дело в том, что вся схема уязвима для атаки с «человеком посередине». Предположим, что клиент начал сеанс, но вместо сервера ему ответил противник. В протоколе нет способа определить, общается ли клиент с корректным сервером. На практике противник легко может занять место сервера, а затем обменяться с сервером ключами, действуя в качестве посредника при передаче законного трафика.
Корень проблемы в том, что обмен ключами не аутентифицирован. Стойте! Мы же сказали, что собираемся воспользоваться протоколом аутентификации, как только установим защищенное соединение! Мы могли бы взять ключ, выработанный по схеме Диффи–Хеллмана, и с ним организовать протокол проверки пароля. Совсем хорошо будет, если этот протокол реализует взаимную аутентификацию, то есть клиент и сервер аутентифицируют друг друга.