Некоторые реализации UNIX предоставляют функцию strlcpy(), которая, если ей передать аргумент n, копирует в заданный буфер максимум n — 1 байт и всегда добавляет в конце нулевой символ. Однако эта функция не входит в стандарт SUSv3 и не реализована в библиотеке glibc. Кроме того, если вызывающий процесс не выполняет тщательную проверку длины строки, эта функция меняет одну проблему (переполнение буфера) на другую (неявное отклонение данных).

38.10. Остерегайтесь DoS-атак

С ростом количества интернет-услуг увеличивается и риск удаленных DoS-атак (англ. denial-of-service — отказ в обслуживании). Эти атаки направлены на то, чтобы сделать ресурс недоступным для обычных клиентов, и заключаются в отправке серверу искаженных данных, приводящих к сбоям, или перегрузке его фиктивными запросами.

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

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

Сложнее защититься от атак, связанных с перегрузкой. Сервер не может контролировать поведение удаленных клиентов или частоту, с которой те отправляют свои запросы, поэтому такие атаки невозможно предотвратить (иногда сервер не может даже определить источник атаки, поскольку IP-адрес сетевого пакета может быть поддельным; кроме того, в распределенных атаках могут использоваться промежуточные сетевые узлы, захваченные для этой цели). Тем не менее вы можете снизить риск атак, связанных с перегрузкой, и минимизировать их последствия, предприняв следующие меры.

• Сервер должен регулировать загрузку и отклонять запросы, когда ее уровень превышает какое-то граничное значение. Следствием этого является отклонение некоторых нормальных запросов, однако данный подход помогает предотвратить перегрузку сервера и компьютера, на котором он работает. В ограничении чрезмерной загруженности также могут помочь ограничения на ресурсы и дисковые квоты (подробности о последних можно найти по адресу sourceforge.net/projects/linuxquota/).

• При взаимодействии с клиентом сервер должен иметь ограниченное время ожидания, чтобы не ждать бесконечно клиентов, которые перестают отвечать (возможно, намеренно).

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

• Структуры данных должны быть стойкими к атакам на алгоритмическую сложность (англ. algorithmic-complexity attacks). Например, при обычно загрузке двоичное дерево может быть сбалансировано и обеспечивать приемлемую производительность. Однако злоумышленник может сформировать последовательность входящих данных, которая способна привести к разбалансированию дерева (в худшем случае превращая его в эквивалент связного списка); от этого может пострадать скорость работы. Книга [Crosby & Wallach, 2003] подробно рассматривает суть таких атак и описывает методики написания структур данных, которые позволяют их избежать.

38.11. Проверяйте результаты выполнения и предусматривайте безопасное завершение в случае неудачи

Привилегированная программа должна проверять, завершились ли системные вызовы или библиотечные функции успешно и вернули ли они ожидаемый результат (конечно, это относится ко всем программам, но при наличии повышенных привилегий это имеет особенно большое значение). Различные системные вызовы могут завершиться неудачей, даже если вызывающая их программа выполняется от имени администратора. Например, к неудачному завершению вызова fork() может привести исчерпание общесистемного ограничения на количество процессов; в случае с вызовом open() это может быть попытка записи в файловой системе, предназначенной только для чтения; причиной неудачного вызова chdir() может стать отсутствие заданного каталога.

Иногда, даже если системный вызов завершился успешно, его результат все равно нужно проверить. Например, если это имеет значение, необходимо проверить, вернул ли успешный вызов open() один из стандартных файловых дескрипторов: 0, 1 или 2.

Перейти на страницу:

Похожие книги