Предотвращение атак, направленных на повреждение стека

В С и С++ локальные переменные хранятся в области памяти, называемой стеком. Сведения, касающиеся управления последовательностью выполнения программы (flow of a program), также обслуживаются стеком. Если массив размещается в стеке, а затем стек переполняется (то есть в массив помещается значений больше, чем может вместить выделенное пространство), атакующий может перезаписать сведения управления последовательностью выполнения программы (поток программы). Такой тип атаки зачастую называется крушением стека (stack-smashing attack).
Такие атаки представляют собой серьезную проблему, поскольку безвредная служба (web-сервер или FTP-сервер) может выполнять непредсказуемые команды. Уже разработано несколько технологий, позволяющих защитить программы от подобного рода атак. Некоторые из них реализованы в компиляторе, например ProPolice (http://www.trl.jbm.com/projects/security/ssp/) или альтернативный вариант Stackguard (http://www.immunix.org/stackguard.html) версии GCC, Другие являются динамическим решением, действующим во время работы сервера, например LfbSafe (http://www.researchavaydabs.com/project/libsafe/). И хотя перекомпиляция исходного текста программы наносит сокрушительный удар по атаке переполнения буфера, решения времени выполнения (rantime solutions) позволяют защитить программы в тех случаях, когда исходный код недоступен либо перекомпиляция просто невозможна.
Все решения уровня компилятора работают примерно одинаково, хотя в реализациях имеются и некоторые различия. Их работа основана на помещении в стек между информацией управления потоком и локальными переменными так называемой «канарейки» (некоторого произвольного значения). Программный код, который обычно генерируется компилятором для возврата из функции, изменяется таким образом, чтобы он выполнял проверку значения «канарейки» в стеке. Если это значение не совпадает с ожидаемым, то программа немедленно завершается.
Идея использования «канарейки» основана на том, что атакующему для перезаписи информации управления потоком необходимо провести атаку на стек, «перезаписав» «канарейку». Атакующий не сможет предсказать совершенно случайного значения «канарейки» и, следовательно, не сможет использовать ее в данных для «крушения» стека.
Когда программа распространяется в форме исходного текста, разработчик программы не может гарантировать использование StackGuard или ProPolice, поскольку они не являются стандартными расширениями компилятора GCC. Их использование лежит на совести лица, выполняющего компиляцию.
Для Linux-систем технология LibSafe (от Avaya Labs) реализована независимо от компилятора, но она использует динамический загрузчик, предварительно загружающий динамические библиотеки для каждого исполняемого файла. Использование IJbSafe не требует исходного кода защищаемой программы и может разворачиваться по всей системе.
IJbSafe заменяет реализации некоторых стандартных функций, уязвимость которых от переполнения буфера известна (gets(), strcpy() и scanf()). Замена реализаций пытается вычислить максимально возможный размер статически выделяемого буфера, используемого в качестве буфера назначения при записи, с помощью встроенной функции GCC, возвращающей адрес указателя фрейма. Этот адрес обычно является первым блоком информации в стеке, следующим за локальными переменными. Если будет предпринята попытка записать больше, чем расчетный размер буфера, программа будет завершена.
К сожалению, с подходом, используемым в LibSafe, связано несколько проблем. Во-первых, невозможно точно вычислить размер буфера. Самое лучшее, что можно сделать, — это ограничить размер буфера от начала буфера до указателя фрейма. Во-вторых, защита LibSafe не будет работать с программами, которые были откомпилированы GCC с флагом -fomit-frame-pointer. Выполненная компилятором оптимизация не помещает в стек указатель фрейма. Несмотря на относительную бесполезность, такая оптимизация популярна у программистов. И наконец, LibSafe не будет работать в отношении двоичных SUID-файлов без статического связывания или аналогичной уловки.
Помимо обеспечения защиты от обычных атак взлома стека, новейшие версии IJbSafe могут противостоять некоторым атакам, основанным на искажении формата строки (format-string attacks). Защита от таких атак требует доступа к указателю фрейма, поскольку она пытается отфильтровать аргументы, не являющиеся ни указателями на «кучу» (heap), ни указателями на локальные переменные в стеке.
Помимо использования решений в пространстве пользователя (user-space solutions), вы можете предпочесть обновить ядро, сделав стек неисполняемым и добавив функцию обнаружения атак переполнения буфера.  Повышение стойкости Unix-системы может быть довольно сложным процессом. Он обычно включает настройку всех служб, которые система должна запускать наиболее безопасным способом, а также блокировку системы для предотвращения локальных проникновений. Однако обеспечение безопасности заггущенных служб не связано с защитой остальной системы и ее неизвестных слабых мест. К счастью, хотя ядро Linux и предоставляет несколько функций превентивной защиты системы, существуют обновления, помогающие системному администратору в полной мере реализовать их. Одним из таких обновлений является grsecurity (http://www.grsecurity.net). Grsecurity входит в состав обновления OpenWall (http://www.openwall.com) для серий 2.4.x ядра Linux. Это обновление добавляет такие функции, как неиспол-няемый стек, улучшение защиты файловой системы, ограничение доступа к /ргос, а также ограничение доступа к ресурсам. Эти функции помогают защитить систему от атак, основанных на переполнении буфера стека, атак на файловую систему, включая «соревнование» файлов в каталоге /temp. Теперь пользователь может просматривать только свои процессы, а доступ к ресурсам за счет выполнения большего числа проверок ограничен. С момента появления обновление grsecurity значительно разрослось, вобрав в себя множество функций, которых не было в обновлении OpenWall. Теперь Grsecurity включает дополнительные варианты защиты адресного пространства, предотвращающие атаки переполнения буфера, а также улучшенные ограничения сред chrootO, jail, увеличенную степень случайности идентификаторов процессов и IP, а также расширение функций аудита, позволяющих отслеживать каждый процесс, выполняемый в системе. Grsecurity добавляет усовершенствованную систему ACL (access control list). Эта система может использоваться для ограничения привилегированных операций, которые могут выполняться отдельными процессами.
Конфигурация ACL обрабатывается с помощью утилиты gradm. Если вы уже установили на машине grsecurity, то можете спокойно перейти далее.
Для компиляции ядра с grsecurity необходимо загрузить обновление (исправление), соответствующее версии ядра, и применить его с помощью утилиты исправления (patch). Например, если установлена ОС Linux 2.4.24, это делается с помощью команд:
# cd /usr/src/linux-2.4.24
# patch -pi < ~andrew/grsecurity-1.9.13-2.4.24.patch
При выполнении команды в каждой строке будет выводиться результат обновления каждого файла исходного кода ядра. По завершении выполнения необходимо убедиться в том, что обновление прошло без ошибок, о чем свидетельствует отсутствие файлов с расширением .rej. Они создаются программой patch при невозможности выполнения исправления. Проверка наличия этих файлов выполняется командой
# find ./ -name \*.rej
При наличии «отклоненных» (rejected) файлов их список будет выведен на экран. После успешного выполнения обновления вы просто вернетесь в командную строку оболочки.
После завершения обновления настройка ядра на использование функций grsecurity осуществляется с помощью команд mate config в текстовом приглашении командной строки, mate menuconQg в curses-based-интерфейсе или mate xconfig при использовании графического интерфейса (GUI), основанного на Тк. Если вы пошли путем использования графического интерфейса, то, воспользовавшись командой mate xconfig, вы увидите диалоговое окно. При запуске mate config или mate menuconfig параметры ядра будут иметь те же имена, что и пункты меню, описываемые далее в этом примере.
Для настройки функций grsecurity, которые будут доступны в ядре, щелкните на одноименной кнопке. После этого появится диалоговое окно. Для включения grsecurity щелкните на переключателе у. После этого в раскрывающемся списке Security Level (Уровень безопасности) можно выбрать предварительно определенный набор функций или значение Custom (Пользовательский) и с помощью меню определить необходимые функции.
Выбор значения Low (Низкий) достаточно безопасен для любой системы и не должен влиять на нормальную работу программного обеспечения. Использование этой настройки включает ограничение связывания в каталогах с режимом 1777. Это предотвращает так называемое состояние гонок в каталоге /temp, используемое для проникновения за счет следования только символическим ссылкам на файлы, которыми владеет процесс. Аналогично, пользователи, находящиеся в каталоге с разрешением 1777, не смогут записывать в FIFO, которым они не владеют. Помимо задания строгих ограничений, касающихся символьных ссыпок и FIFO, настройка Low повышает случайность номеров, назначаемых идентификаторам процессов и IP. Это помогает предотвратить атаки с использованием методик удаленного определения типа ОС, установленного на машине (трюк № 40), а также усложняет предсказание номера идентификатора процесса определенной программы. Низкий уровень безопасности также заставляет программы, использующие перенаправление chrootO, изменять их текущий рабочий каталог после вызова chrootO на /. Иначе, если программа оставит свой рабочий каталог вне chroot-среды, это может использоваться для взлома «песочницы». Использование уровня безопасности Low отменяет для всех пользователей, за исключением root, возможность запуска утилиты dmesg, позволяющей просмотреть последние сообщения ядра.
Уровень Medium (Средний) включает те же ограничения, что и уровень Low, но делает более безопасными «песочницы», созданные на основе chrootO. Возможности монтирования файловых систем, вызова chrootO, записи переменных sysctl или создания узлов устройств (device nodes) в среде «песочницы» ограниченны, что в значительной степени снижает риски, связанные с запуском службы в «песочнице» ОС Linux. Кроме того, TCP-порты источника станут произвольными, будут протоколироваться неудачные вызовы forkO, изменения системного времени и ошибки сегментации. Использование уровня Medium ограничит общий доступ к /ргос, сохранив его только для привилегированных пользователей. Процессы пользователей будут скрыты от других пользователей, и будет запрещена запись в /dev/kmem, /dev/mem и /dev/port. При запущенном ядре обновление root-средств в нем станет более сложным. Размещение адресного пространства процесса в памяти станет случайным, что усложнит проведение атак переполнения буфера (buffer overrun attacks). По этой причине сведения о размещении процесса в адресном пространстве будут удалены из /ргос. Из-за ограничений, введенных для /ргос, вам придется запустить свой identd-демон от имени учетной записи, относящейся к группе привилегированных пользователей. В документации grsecurity указывается, что ни одна из этих функций не повлияет на работу вашего программного обеспечения, за исключением очень старого и написанного не совсем корректно.
Для включения почти всех функций grsecurity необходимо включить уровень безопасности High (Высокий). Наряду с функциями, предоставляемыми более низкими уровнями, этот уровень обеспечивает дополнительные ограничения /ргос за счет предоставления доступа к информации об устройствах и процессоре только пользователям, являющимся членами группы привилегированных пользователей. Ограничена также среда «песочницы» за счет отключения у chmod возможности устанавливать разряд SUID или SGID. Кроме того, приложениям, запущенным в такой среде, запрещается добавлять загружаемые модули, выполнять операции «сырого» ввода-вывода, настраивать сетевые устройства, выполнять перезагрузку системы, модифицировать неизменные файлы (unmutable files) или изменять системное время. Выбор этого уровня безопасности приведет также к произвольному размещению в памяти стека ядра для предотвращения успешной реализации атак переполнения буфера ядра. Кроме того, идентификаторы ядра будут скрыты (это значительно усложнит установку в запущенное ядро кода так называемых «троянских коней»), а операции монтирования, перемонтирования и демонтирования будут протоколироваться.   Выбор степени безопасности High активизирует программный код функции РаХ, которая обеспечивает функционирование неисполняемых страниц памяти. Это позволяет защититься от различных атак переполнения буфера, поскольку любой код, помещенный в стек путем перезапуска (overrun), выполняться не будет. Тем не менее по-прежнему сохраняется возможность использовать уязвимость переполнения буфера программы, хотя из-за произвольного расположения процесса в адресном пространстве сделать это очень сложно. На архитектуре х86 РаХ ведет к некоторому снижению производительности, хотя и минимальному. Кроме того, некоторые программы (такие, как XFree86, wine и виртуальные машины Java) ожидают, что адресное пространство, возвращаемое вызовом malloc( ), будет исполняемым. К сожалению, РаХ изменяет такое поведение, что может привести к невозможности работы этих и других программ. К счастью, с помощью утилиты chpax (http://chpax.grsecurity.net) РаХ можно отключить для конкретной программы. Для этого используется подобная команда:
# chpax -ps /usr/bin/java
Существуют и другие программы, которые используют специальные функции GCC, например trampoline. Это позволяет программисту определять небольшую функцию внутри другой функции, так чтобы эта функция выполнялась только в пределах той функции, в которой она определена. К сожалению, GCC помещает программный код функции trampoline в стек, поэтому РаХ нарушает работу программ, которые полагаются на нее. Но РаХ с помощью утилиты chpax с ключом -Е может эмулировать функцию trampoline.
Если вас не устраивает набор функций, предлагаемый рассмотренными уровнями безопасности, можно выбрать вариант Custom (Пользовательский) и включить только необходимые вам функции.
После установки определенного уровня безопасности или выбора набора функций перекомпилируйте ядро и модули обычным образом:
# male dep clean && mate hzlmage
# mate modules && mate modules_ install
Далее перезагрузите ядро. Помимо включенных ограничений ядра, теперь можно воспользоваться утилитой gradm для настройки ACL системы (см. трюк № 14).
Необходимо отметить, что grsecurity — это сложная, но крайне полезная модификация ядра Linux. Дополнительные сведения об установке и о настройке обновления можно найти в документации, размещенной на странице htip://www. grsecurity.net/papers.php.

Метки: , ,

Статьи по теме