Взлом с использованием данных
Обсудив "притчу во языцах" — взлом с помощью подбора паролей, — можно перейти к другому методу, также ставшему стандартом "де факто" при получении удаленного доступа. Этот метод заключается в использовании для взлома определенных данных (data driven attack), отправляемых активной службе, что позволяет получить неожиданные или нежелательные результаты. Конечно, формулировка "неожиданные или нежелательные" достаточно субъективна. Все зависит от того, кто вы — хакер или же программист, разработавший соответствующую службу. С точки зрения взломщика, результат может быть более чем желательным, поскольку в этом случае он сможет получить доступ к интересующему его компьютеру. С точки зрения же программиста, программа, получившая данные, к приему которых она не была готова, выдает нежелательные результаты. Методы взлома с использованием данных можно разделить на две категории: атака путем переполнения буфера и взлом при отсутствии проверки ввода. В последующих подразделах каждая из этих категорий будет рассмотрена более подробно.
Взлом путем переполнения буфера
В ноябре 1996 года подходы к компьютерной безопасности изменились раз и навсегда. Ведущий списка рассылки Bugtraq Алеф Ван (Aleph One) опубликовал в номере 49 журнала Phrack Magazine, посвященного вопросам безопасности, статью под названием "Разрушение стека для развлечения и извлечения выгоды" (Smashing The
Stack For Fun And Profit). Эта статья произвела колоссальный эффект на состояние дел в сфере обеспечения безопасности, поскольку в ней очень ясно показано, как практика некачественного программирования может привести к нарушению безопасности путем переполнения буфера. Первые упоминания об использовании этой методологии датируются 1988 годом в связи с нашумевшим делом о сетевом черве Роберта Морриса (Robert Morris), однако полезной информации о ее конкретных подробностях не было вплоть до 1996 года.
Состояние переполнения буфера (buffer overflow) возникает тогда, когда пользователь или процесс пытается поместить в буфер (или массив фиксированного размера) данных больше, чем для этого выделено памяти программистом. Подобная ситуация зачастую связана с использованием таких функций языка С, как strcpy (), strcat (} и sprintf (), а также ряда других. Переполнение буфера обычно приводит к генерации ошибки нарушения сегментации. Однако это состояние может вызываться преднамеренно с целью получения доступа к системе. Хотя мы рассматриваем методы взлома с помощью удаленного доступа, переполнение буфера может происходить и в локальных программах, о чем мы поговорим несколько позже. Для того чтобы лучше понять, как этот метод взлома срабатывает на практике, давайте рассмотрим один очень простой пример.
Допустим, для вводимых данных в программе выделяется буфер фиксированного размера 128 байт. Предположим, что этот буфер создается для размещения данных, поступающих от команды VRFY программы sendmail. Как вы помните из главы 3, эта команда использовалась для того, чтобы установить потенциальных пользователей по их почтовым адресам. Предположим также, что sendmail запущена в контексте прав SU1D пользователя root и пользуется его привилегиями (во многих системах этот так и есть, хотя и не всегда). Что произойдет, если взломщик подключится к демону sendmail и отправит в качестве параметра команды VRFY строку состоящую из 1000 символов а, а не короткое имя пользователя?
echo "vrfy 'perl -е 'print "a" x 1000''" |nс www.targetsystem.com 25
Поскольку буфер, предназначенный для хранения параметра VRFY, имеет размер всего 128 байт, возникнет ситуация его переполнения. Это может привести к генерации состояния DoS и аварийному завершению демона sendmail. Однако более опасными являются ситуации, когда программа, буфер которой переполнился, продолжает работать и выполняет при этом программный код, переданный ей в виде избыточных данных. Именно это и является основным смыслом рассматриваемой в данном разделе атаки.
Вместо того, чтобы отправлять бессмысленную строку, состоящую из 1000 символов а, взломщик, скорее, передаст определенный набор кодов, который после переполнения буфера выполнит команду /bin/sh. Если, как мы условились, sendmail работает с привилегиями суперпользователя, то после запуска /bin/sh взломщик сразу же сможет получить доступ в качестве суперпользователя. Возможно, вы никак не можете понять, каким же образом программа sendmail узнает, что ей нужно выполнить команду /bin/sh? Все очень просто. В процессе взлома в качестве параметра команде VRFY передается строка, содержащая некоторый ассемблерный код, призванный вызвать переполнение буфера. При переполнении буфера адрес возврата переустанавливается на код, переданный хакером, что позволяет последнему получить полный контроль над программой. Иными словами, вместо возврата управления из функции по нужному адресу выполняется некоторый код взломщика, передаваемый в этом же пакете данных и запускающий команду /bin/sh.
Конечно, необходимо помнить, что ассемблерный код очень сильно зависит от архитектуры и используемой операционной системы. Поэтому данные, используемые для переполнения буфера системы Solaris, установленной на компьютерах с процессорами Intel, не имеют ничего общего с данными, предназначенных для взлома системы Solaris компьютеров SPARC. Следующий пример показывает, как выглядит машинный код (такой код еще называют "яйцом" (egg), по аналогии с яйцами кукушки, которые она подкладывает в чужие гнезда), предназначенный переполнения буфера на платформе Linux X86.
char shellcode[]=
"\xeb\xlf\x5e\x89\x76\x08\x31\xcO\ x88\x46\x07\x89\x46\xOc\xbO\xOb"
"\x89\xf3\x8d\x4e\x08\x8d\x56\xOc\xcd\ x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
Очевидно, что взлом с помощью переполнения буфера чрезвычайно опасен. Достаточно сказать, что он не раз использовался во многих успешных попытках получения несанкционированного доступа. Приведенный выше пример очень прост. На самом деле работающий код создать очень трудно. Однако зачастую изобретать велосипед не приходится: множество таких "яиц" уже было создано хакерами и помещено в Internet. Описание деталей процесса создания "яйца" выходит за рамки этой книги, поэтому мы советуем познакомиться с упоминавшейся выше статьей хакера Алефа Вана в журнале Phrack Magazine.
Если вы хотите улучшить свои навыки написания ассемблерного кода, прочтите книгу Криса Дрейка (Chris Drake) и Кимберли Браун (Kimberley Brown) Panic — UNIX System Crash and Dump Analysis. Кроме того, группа программистов и специалистов по безопасности Тесо (Teso) разработала несколько утилит, которые позволяют автоматически генерировать подобный код. Эти средства можно найти по адресу
http://teso.scene.at/releases.php.
Контрмеры: защита от атак с использованием переполнения буфера
Практика безопасного кодирования
Лучшим методом зашиты от переполнения буфера является практика кодирования, учитывающего все требования обеспечения безопасности. Хотя на практике невозможно спроектировать и запрограммировать систему таким образом, чтобы в ней не было ни одной ошибки, существуют подходы, способные минимизировать вероятность возникновения переполнения буфера. Среди таких рекомендаций можно выделить следующие.
- При проектировании программы всегда оценивайте ее с точки зрения безопасности. К сожалению, зачастую программы создаются наспех, чтобы успеть к поставленному сроку. В таких ситуациях безопасность — это последнее, о чем думают разработчики. При этом поставщики программного обеспечения даже не беспокоятся о том, чтобы своевременно устранять изъяны по мере их обнаружения. Более подробная информация по этому вопросу приведена в разделе Secure UNIX Program по адресу
http://www.whitefang.com/sup/index.html.
- Рассмотрите возможность использования безопасного компилятора, такого, например, как StackGuard, разработанного в рамках проекта Immunix
. В этом компиляторе используется подход, заключающийся в "вакцинации" программ во время компиляции, что позволяет свести к минимуму риск возникновения переполнения буфера. Кроме того, к механизмам защиты относится динамическая библиотека libsafe, предназначенная для перехвата вызовов уязвимых функций на уровне операционной системы.
Помните о том, что подобные механизмы нельзя рассматривать как "серебряную пулю", так что при их использовании все же не стоит забывать о необходимости обеспечения безопасности.
- Нужно проверять все аргументы, получаемые от пользователя или какой-либо программы. Такая проверка, конечно, может замедлить некоторые приложения, но это не очень высокая цена за безопасность. При проведении проверки особое внимание необходимо уделять принадлежности используемых значений корректным диапазонам, особенно для переменных окружения.
- Используйте безопасные процедуры, такие как fget(),
strncpy() и strncat () и проверяйте коды возврата системных вызовов.
- Уменьшите количество кода, запускаемого с привилегиями root. Этого можно достичь за счет минимизации использования программ, которым требуются права SUID суперпользователя. Если даже злоумышленнику удастся успешно применить к такой программе атаку с переполнением стека, то ему все равно придется повышать полученные привилегии до уровня
root.
- И наконец, применяйте все модули обновления, предоставляемые поставщиком программного обеспечения.
Тестирование и аудит каждой программы
Очень важно выполнять тестирование и аудит каждой программы. Очень часто случается, что программисты даже не задумываются о том, может ли в их программе возникнуть ошибка переполнения буфера. Однако всегда найдется кто-нибудь, кто не только задумается над этим, но и приложит все усилия для того, чтобы найти такие ошибки и воспользоваться ими в своих целях. Одним из лучших примеров тестирования и аудита кода UNIX является проект OpenBSD (www.openbsd.org), которым руководит Тео де Раадт (Theo de Raadt). Программисты, работающие над проектом OpenBSD, постоянно проверяют и перепроверяют исходный код друг друга и уже исправили сотни ошибок, которые могут привести к переполнению буфера, не говоря уже о более серьезных проблемах, имеющих отношение к безопасности. Именно из-за столь грамотного подхода к тщательному аудиту, применяемого разработчиками OpenBSD, эта операционная система заслужила репутацию одной из самых надежных из свободно распространяемых версий UNIX.
Отключение неиспользуемых или потенциально опасных служб
На протяжении этой главы мы будем возвращаться много раз к этому вопросу. Если какие-то неиспользуемые или потенциально опасные службы не являются жизненно необходимыми для работы системы UNIX, отключите их. Помните, что ни один злоумышленник не может проникнуть в систему через неработающую службу. Кроме того, мы настоятельно рекомендуем использовать TCP-оболочки (tcpd) и xinetd
(http://www. synack.net/xinetd/) для того, чтобы можно было применить избирательные списки управления доступом на уровне служб, а также воспользоваться дополнительным возможностями регистрации событий. Конечно, не к каждой службе можно применить оболочку. Однако применение этого средства лишь к некоторым службам может значительно повысить защищенность вашей системы. Кроме того, оцените возможность использования режима фильтрации пакетов на уровне ядра, поддержка которого уже стала стандартной для большинства бесплатных операционных систем UNIX (например, ipchains или netf liter для Linux, ipf для BSD). Хорошие рекомендации по использованию ipchains для обеспечения безопасности можно найти по адресу
http://www.linuxdoc.org/HOWTO/IPCHAINS-HOWTO.html. Пакет ipf Даррена Рида (Darren Reed) является одним из лучших и может быть добавлен во многие версии системы UNIX. Для получения об этом пакете более подробной информации обращайтесь по адресу
http: //www.obfuscation. rg/ ipf / ipf-howto. html.
Отключение режима поддержки выполнения стека
Некоторые радетели чистоты нередко прибегают даже к отключению режима поддержки выполнения стека (stack execution), чтобы обеспечить защиту каждой программы от взлома с помощью переполнения буфера. Хотя такое решение может привести к некоторым побочным эффектам, в большинстве систем оно все же обеспечивает защиту от скрытого использования уязвимых мест. Для Linux имеется модуль обновления, позволяющий отключить режим поддержки выполнения стека, который можно применять в системах с ядром версий 2.0.x и 2.2.x Первым разработал такой модуль хакер Solar Desinger
(http://www.false.com). Этот модуль обновления, ценный в основном для программистов, можно найти по адресу
http: //www. openwall. com/linux/.
Для системы Solaris версии 2.6 и 7 мы настоятельно рекомендуем включить поддержку режима, запрещающего выполнение стека (no-stack execution). Это позволит обезопасить систему Solaris от применения множества методов взлома, приводящих к переполнению буфера. Хотя прикладной двоичный интерфейс (ABI — Application Binary Interface) компаний Intel и SPARC позволяет выполнять код, находящийся в сегменте стека, большинство программ будет работать вполне корректно даже при отключенном стеке. По умолчанию в системах Solaris 2.6 и 7 режим выполнения стека включен. Для того чтобы отключить поддержку этого режима, добавьте следующую строку в файл /etc/system file.
set noexec_user_stack=l
set noexec_user_stack_log=l
Помните, что запрещение выполнения стека — не панацея. Отключив этот режим, обычно можно зарегистрировать любую программу, которая попытается выполнить код. помещенный в стек, и таким образом можно остановить взломщиков с низкой квалификацией. Однако опытные взломщики чрезвычайно изобретательны и вполне могут написать код (и воспользоваться им), который приведет к переполнению буфера с последующим взломом системы, несмотря на то, что в ней запрещено выполнение стека.
В то время как многие администраторы изо всех сил пытаются предотвратить переполнение стека, отключив режим выполнения помещенного в него кода, их подстерегают другие опасности, причиной которых является несовершенный код. В наши планы не входит подробное рассмотрение этого вопроса. Достаточно лишь сказать, что переполнение в свободной памяти (которая называется также кучей (heap) или динамической памятью) также может оказаться достаточно опасным. В этом случае переполняется память, динамически распределенная приложением. Такой тип переполнения отличается от переполнения стека, зависящего от длины фиксированного буфера. К
сожалению, разработчики программного обеспечения, как правило, не предусматривают
аналогичного параметра, позволяющего запретить выполнение стека. Таким образом, просто запретит) выполнение кода, помешенного в стек, нельзя обеспечить достаточно высокий уровень зашиты. Дополнительную информацию о переполнении динамической памяти можно найти в результатах исследований группы
w00w00 по адресу http://www.w00w30.org/files/heaptut/heaptut.txt.
Взлом при отсутствии проверки ввода
В 1996 году Дженифер Майерс (Jennifer Myers) идентифицировал ставший впоследствии широко известным изъян PHF. Хотя атаки с использованием этого изъяна уже отошли в прошлое, на его примере очень хорошо видно, как может осуществляться взлом при отсутствии проверки ввода (input validation attack). Если вы разберетесь в основном механизме этого метода, то сможете применить полученные знания и к другим подобным подходам. В данной главе мы не будем посвящать много времени этой теме, поскольку она подробно рассматривается в главе 15. Наша цель — лишь показать, что из себя представляет взлом при отсутствии проверки ввода и как с его помощью злоумышленник может получить доступ к системе UNIX.
Для осуществления такой атаки необходимо, чтобы выполнялись следующие условия.
- Программа не в состоянии распознать синтаксически некорректные данные.
- Модуль воспринимает посторонние данные.
- Модуль не в состоянии обработать ситуацию отсутствия определенных полей.
- Возникает ошибка корреляции значений полей.
PHF— это сценарий CGI (Common Gateway Interface— интерфейс общего шлюза), ставший стандартом в ранних версиях Web-сервера Apache и сервера HTTPD центра NCSA (National Center for Supercomputing Applications — Национальный центр суперкомпьютерных приложений). К сожалению, эта программа не в состоянии ни правильно провести синтаксический анализ входных данных ни проверить их пригодность. Исходная версия сценария PHF принимала символ новой строки (%0а) и выполняла следующие за ним команды с привилегиями пользователя, запустившего Web-сервер. Поэтому сразу же был изобретен метод взлома PHF, показанный ниже.
/cgi-bin/phf?Qalias=x%0a/bin/cat%20/etc/passwd
На момент написания этой книги данный код не мог выполнить ничего, кроме вывода файлов паролей с помощью команды cat. Конечно, эта информация может использоваться для определения идентификаторов пользователей, а также зашифрованных паролей (при условии, что пароли не содержатся в файле с повышенной защитой shadow). В большинстве случаев этого достаточно, чтобы даже неопытный злоумышленник смог взломать файл паролей и зарегистрироваться в системе. Опытный же взломщик сможет не только проникнуть в систему, но и получить прямой доступ к командной оболочке, как будет показано ниже в этой главе. Помните, что этот изъян позволяет взломщику выполнить любую команду с привилегиями пользователя, от имени которого запущен Web-сервер. В большинстве случаев, конечно, таким пользователем является nobody, однако, к сожалению, нередко встречаются узлы, на которых Web-сервер работает на уровне привилегий суперпользователя root, — ни больше, ни меньше!
В 1996-1997 годах взломы PHF были очень популярны. От этих простых, но очень эффективных приемов пострадали очень многие Web-узлы. Поскольку данный подход пригоден и для проведения других взломов при отсутствии проверки ввода, необходимо хорошо понимать, как именно данный изъян используется злоумышленниками. В системе UNIX имеются метасимволы, зарезервированные для специальных целей. К таким метасимволам относятся следующие (данный перечень не является исчерпывающим).
\ /<>!$% ^ & * | {}[]`"` ~ ;
Если программа (или сценарий CGI) принимает какие-то данные, вводимые пользователем, и не проверяет их корректность, то такая программа может подвергнуться взлому с помощью специально подобранного кода. Этот метод обычно называется "выбросом" (escaping out) в командную оболочку и позволяет передать в качестве параметра один из метасимволов UNIX. Данный подход очень распространен и не ограничивается одними лишь сценариями PHF. Имеются многочисленные примеры незащищенных программ CGI, входящих в базовый комплект поставки Web-серверов. Что еще хуже, многие уязвимые программы создаются "профессиональными" разработчиками Web-узлов, имеющими
весьма смутное представление о безопасности. К сожалению, с развитием электронной коммерческой деятельности и появлением множества соответствующих приложений с дополнительным набором функций (увеличивающих, соответственно, их сложность), количество таких взломов только возрастает.
Контрмеры
Как уже упоминалось раньше, одним из лучшим способов превентивной защиты является разработка программ с учетом требований обеспечения безопасности. Это же правило можно в полной мере применить и к защите от описанного выше взлома. Абсолютно необходимо, чтобы программы и сценарии воспринимали только те данные, которые они должны воспринимать. В разделе часто задаваемых вопросов WWW Security, расположенном по адресу
http://www.w3.org/Security/Faq/www-security-faq.html, содержится важная информация о том, как сделать защищенными программы CGI. Поскольку очень трудно выполнять проверку каждой порции входных данных, лучше, чтобы эти процедуры по умолчанию отбрасывали все критические данные. Кроме того, после компиляции тщательно контролируйте и тестируйте весь программный код.
|