Защита от хакеров корпоративных сетей
Шрифт:
«this is the skeleton of the string, %i»
Ниже приведена строка, которая выводится во время выполнения программы (значение переменной равно 10).
[dma@victim server]$ ./format_example this is the skeleton of the string, 10
Функция printf просматривает форматирующую строку и выводит каждый символ как он есть, буквально, пока не встретит спецификацию преобразования. Поскольку функция printf заранее не знает, сколько параметров будет ей передано, то каждый параметр считывается из стека по мере обработки форматирующей строки в соответствии с типом каждой спецификации преобразования. В рассматриваемом примере в форматирующей строке единственная спецификация преобразования – спецификация вывода целого числа со знаком %i, обеспечивает вставку в формируемую строку переменной целого типа. Функция ожидает, что переменная, соответствующая спецификации преобразования, будет передана функции printf вторым параметром. В архитектуре Intel (по крайней мере) параметры функций помещаются в стек до того, как будет создан стековый фрейм. Поэтому когда функция printf считывает
Примечание
В этой главе термин «ниже» («под») применяется к данным, которые были помещены в стек раньше каких-то других. Для описания данных, помещенных позже, применяется термин «выше» («над»). В архитектуре Intel стек растет вниз. В архитектурах с растущим вниз стеком адрес вершины уменьшается по мере роста стека, поэтому данные, расположенные «ниже», хранятся в областях памяти с большими адресами, чем данные, расположенные «выше».
Тот факт, что область памяти с большим адресом логически располагается в стеке ниже, может вызвать путаницу. Когда про область стека говорят, что она выше другой, это означает, что эта область находится ближе к вершине стека, чем другая.
В примере второй аргумент функции printf – целое число, которое включается в формируемую строку в соответствии со спецификацией вывода целого числа со знаком %i. В результате в формируемой строке на месте спецификации формата будет помещено значение переменной integer в формате целого десятичного числа, которое равно 10.
В соответствии со спецификацией вывода целого числа со знаком функция printf для формирования выводимой строки использует содержимое области памяти, размер которой совпадает с размером переменной целого типа и которая расположена в нужном месте стека. Сначала в соответствии со спецификацией формата двоичное представление содержимого выбранной области стека преобразуется в символьное представление, а затем включается в формируемую строку. Как будет показано позже, это происходит независимо от того, передан на самом деле второй параметр функции printf или нет. Если ни одного параметра, соответствующего спецификациям формата форматирующей строки, не было передано функции printf, то стековые данные вызывающей функции будут трактоваться как параметры, поскольку они занимают в стеке место предполагаемых параметров вызванной функции printf.
Вернемся к примеру. Допустим, что впоследствии было решено выводить только статическую строку, но при этом забыли указать переменную, соответствующую спецификации формата. В конечном счете функция printf вызывается следующим образом:
printf(“this is the skeleton of the string, %i”); /* note: no argument. only a format string. */
Во время своего выполнения функция не знает, что ей забыли указать переменную, соответствующую спецификации вывода целого числа со знаком %i. Поэтому при формировании строки функция printf прочтет целое число из области стека, в которую должен быть помещен второй параметр и которая занимает 4 байта под ее стековым фреймом. Если виртуальная память размещения второго параметра доступна, то программа продолжит свою работу и любые байты, оказавшиеся в области размещения второго аргумента, при работе функции будут проинтерпретированы и выведены как целое число. В результате будет напечатано следующее:
[dma@victim server]$ ./format_example this is the skeleton of the string, -1073742952
Отметим, что хотя при вызове функции printf не был задан параметр функции, соответствующий спецификации вывода целого числа со знаком %i , тем не менее в формируемую строку было включено целое число. Функция прочитала из области стека, в которую был бы помещен незаданный параметр функции, какие-то данные и представила их в формате целого числа со знаком. В данном случае после представления найденных байтов в формате целого десятичного числа со знаком получилось число -1073742952.
Таким образом, если дополнить форматирующую строку своими спецификациями формата, то можно воспользоваться функцией printf для просмотра содержимого стека, потому что функция printf выведет содержимое областей стека в соответствии с заданными спецификациями формата.
Как будет показано дальше, возможность управления функцией printf через входные данные программы может привести к образованию серьезной бреши в защите. При наличии программы с ошибками форматирующей строки, которая для формирования выводимой строки использует входные данные программы, злоумышленник сможет прочитать содержимое критических участков памяти. Такие участки памяти могут быть перезаписаны с использованием форматирующей строки с малопонятной спецификацией формата %n. Спецификация преобразования %n, известная также как указатель на целое, позволяет получить текущее число символов форматируемой строки в момент обработки указателя на целое. Как злоумышленник сможет воспользоваться ошибками форматирующих строк, будет объяснено далее во время изучения программы атаки, использующей ошибки форматирующей строки.
Как и почему возникают ошибки форматирующей строки?
Основная причина возникновения ошибок форматирующей строки заключается в использовании программистом непроверенных входных данных в форматирующей строке. Ниже приведены наиболее известные ошибки программирования, обусловленные уязвимостью форматирующей строки.
Первая ошибка состоит в том, что при вызове функции printf ей передается единственная непроверенная строка. Например:printf(argv[1]);
При этом единственная передаваемая переменная (часто передается первый аргумент командной строки) расценивается функцией как форматирующая строка. И если в эту переменную будут включены спецификации преобразования, то функция среагирует на них.
[dma@victim]$ ./format_example %i -1073742936
Часто эта ошибка совершается начинающими программистами
error warn(errmsg);
Рассмотренный ниже пример программы использования ошибки форматирующей строки основан на подобной ошибке. Наиболее общей причиной возникновения уязвимости форматирующей строки в системе Unix является использование функции syslog. Функция syslog служит программным интерфейсом для демона системного журнала. (Демон – скрытая от пользователя служебная программа, вызываемая при выполнении какой-либо функции.) Программист использует функцию syslog для записи сообщений об ошибке в файлы системного журнала с указанием их приоритетов. Параметрами функции syslog, кроме первого, может быть как единственная строка символов, так и форматирующая строка с произвольным числом спецификаций преобразования и соответствующие им параметры. Первым параметром функции является приоритет сообщения. Многие программисты при использовании функции syslog забывают или не знают о том, что в форматирующую строку не должны помещаться входные данные программы. Большинство уязвимостей связано с кодированием подобно следующему фрагменту кода
error warn(errmsg);
Если в строке errmsg содержатся входные данные программы, например имя пользователя, неудачно попытавшегося войти в систему, то они могут быть использованы для типичной атаки с использованием ошибок форматирующей строки.
Как устранить уязвимость форматирующей строки?
Источником большинства уязвимостей в системе безопасности программного обеспечения является небезопасное программирование. Поэтому наилучшим способом избавления от ошибок форматирующей строки является их предотвращение на этапе создания программы. Для этого необходимо, чтобы программисты были осведомлены о серьезности подобных ошибок и о возможности их использования для атаки. К сожалению, непохоже, что в ближайшее время произойдет всеобщее осознание проблем безопасности.
Для администраторов и пользователей программного обеспечения наилучшей линией является поддержание разумного уровня безопасности своих систем. Для достижения этого должны быть удалены все ненужные и запрещенные программы, а все лишние сервисы должны быть выключены или заблокированы.
Майк Франтцен (Mike Frantzen) опубликовал методику вылизывания программ с целью максимального устранения недоделок, которая позволяет администраторам и программистам предотвращать любые атаки с использованием ошибок форматирующей строки. Его методика основана на сравнении числа параметров, передаваемых функции printf, с числом спецификаций формата в форматирующей строке. Эта методика под названием Format Guard реализована в Immunix, одном из безопасных дистрибутивов ОС Linux.
Заархивированную методику Майка Франтцена в списке рассылки Bugtraq можно найти по адресу www.securityfocus.com/archive/1/72118. Format Guard находится по адресу www.immunix.org/formatguard.html.Способы использования ошибок форматирующей строки для атаки
Используя для атаки уязвимость форматирующей строки, злоумышленник может достичь трех основных целей. Во-первых, аварийное завершение атакованного процесса вследствие недействительного доступа к памяти. В результате может произойти отказ в обслуживании. Во-вторых, при выводе отформатированной строки злоумышленник может прочитать память атакованного процесса. И наконец, в-третьих, злоумышленник может перезаписать область памяти, что, возможно, приведет к выполнению подготовленных им команд.
Ошибки и защита
Переполнение буфера с помощью ошибок форматирующей строки
Спецификации формата пользователя могут привести к переполнению буфера. В некоторых случаях для переполнения буфера злоумышленник может воспользоваться функцией sprintf, если в программе не наложено никаких ограничений на длину строк, передаваемых небезопасной функции. Ограничения на длину используемых строк не позволят злоумышленнику воспользоваться слишком длинной форматирующей строкой или строкой чрезмерно большого размера, соответствующей спецификации формата %s при обращении к функции sprintf.
Если в программе допускается вставка данных пользователя в форматирующую строку – первый параметр функции sprintf, то размер выводимой строки может быть увеличен при задании в спецификации преобразования ширины поля. Например, если атакующий включит в форматирующую строку спецификацию вывода целого десятичного числа со знаком %100i, указав при этом ширину поля равной 100 символам, то сформированная строка будет не менее чем на 100 байт больше, чем ожидалось. Задание ширины поля позволит сформировать строки, которые при их записи в буфер приведут к переполнению буфера. В этом случае ограничения, наложенные на длину отформатированной строки, не спасут от переполнения буфера и позволят злоумышленнику выполнить подготовленный им программный код.
Далее этот способ атаки рассматриваться не будет. Хотя подобные атаки, используя спецификации формата, позволяют перезаписать содержимое памяти, тем не менее они используются только для увеличения размера строк до длины, достаточной для переполнения стека. А эта глава посвящена атакам, основанным исключительно на применении спецификаций формата без использования уязвимостей, основанных на ошибках программирования, например таких как переполнение буфера. Более того, описанная ситуация может быть вызвана присущими форматирующей строке уязвимостями при использовании спецификаций записи в память.