Переполнение буфера — весьма распространенный тип атаки; встречается даже в коде, где, по утверждению поставщика, были найдены и уничтожены все потенциальные угрозы. Возможно, вы также слышали о таких разновидностях переполнения буфера, как атаки на функции форматирования строк и атаки на кучи. В данной статье при помощи аналогий из повседневной жизни я объясню принципы этих атак. Я позаимствовал идею из книги Брюса Шнайера «Секреты и ложь» (Bruce Schneier, Secrets and Lies), хотя, как истинный взломщик, я скорректирую и обобщу эту идею.
|
Глупый работник
Шнайер объясняет переполнение буфера, сравнивая память компьютера с блокнотом из отдельных листков, где содержатся инструкции для работника магазина самообслуживания. На каждой странице приводится одна инструкция, например «Поприветствуйте клиента», «Пробейте чек», «Примите оплату» и т.п. Предположим, что работник магазина не блещет умом и точно следует инструкциям.
Это делает магазин уязвимым для простой атаки. Злоумышленник подходит к прилавку и, пока работник перелистывает инструкции, незаметно вкладывает страницу с указанием «Выньте все деньги из кассового аппарата и передайте их клиенту». Можно надеяться, что, поскольку работник следует инструкциям, он заметит здесь ошибку, верно?
На страницах моей памяти...
В отличие от большинства работников магазина компьютеры точно следуют инструкциям и совершенно лишены здравого смысла. Если злоумышленник сможет незаметно добавить дополнительные инструкции, то компьютер неукоснительно выполнит их. В этом и заключается смысл атаки типа «переполнение буфера».
Существуют три распространенных варианта переполнения буфера: атаки на стек, атаки на функции форматирования строк и атаки на кучи. Все атаки сходны между собой, но воздействуют на разные участки компьютерной памяти. Чтобы вы смогли понять различия между этими видами атак, я в общих словах расскажу, как работает память компьютера.
Когда программа начинает выполняться, операционная система выделяет ей огромный участок виртуальной памяти. Это напоминает чистую книгу, в которую записывается программа, начиная с инструкций и заканчивая данными. По завершении этой операции записи в книге остается много пустых страниц. Пустые страницы сразу же после данных программы называются «кучей», а в самом конце книги — «стеком». Куча растет по направлению к концу книги, а стек — по направлению к началу, как если бы вы использовали блокнот с обоих концов. «Книга», то есть виртуальная память, настолько велика, что куча никогда не достигает стека и наоборот.
Расположения внутри виртуальной памяти («страницы» блокнота) — в программе, стеке, куче или между ними — определяются адресом, который выражается шестнадцатиричными символами; например, наибольший адрес в памяти объемом два гигабайта будет 8FFFFFFF. Небольшие участки этого адресного пространства зарезервированы для ввода данных в программу. Эти участки, которые могут иметь адреса в стеке или куче, называются буферами. Ага! Мы нашли первую причину того, почему такие атаки называются «переполнением буфера». Такие фразы, как «это переполнение буфера в стеке», «это срыв стека» или «это переполнение буфера в куче», уточняют место возникновения проблемы внутри памяти, выделенной определенной программе.
Атаки на стек
В стеке хранится временная информация. Опять же можно проиллюстрировать принципы его работы, воспользовавшись примером из книги Шнайера. Представьте, что вы пишите заметки о проекте, над которым работаете, и вдруг звонит телефон. Звонящий предоставляет запрошенные вами сведения, поэтому вы берете еще один лист бумаги, помещаете его поверх первого и записываете данные. Вы еще не положили трубку, когда входит ваш начальник, отвлекает ваше внимание и просит что-то сделать, когда вы закончите разговор. Вы берете еще один листок и записываете просьбу начальника. Теперь у вас есть небольшая стопка бумаги (стек), где на каждом листе записаны инструкции и данные. Завершая каждую задачу, вы сминаете соответствующий листок и бросаете в мусорную корзину. Вы используете стопку бумаги (стек) во многом так же, как и при переполнении буфера.
В компьютере, конечно, нет листов бумаги — только память (ОЗУ). Данные действительно добавляются на вершину стека, а затем удаляются. При атаке с переполнением буфера в стеке взломщик добавляет в стек больше данных, чем ожидалось, перезаписывая информацию, которую, по убеждению программиста, никогда нельзя будет заменить. Например, в ходе своего выполнения программа достигла этапа, когда ожидается использование почтового индекса, получаемого из веб-формы, которая заполняется заказчиками. Даже самый длинный почтовый индекс содержит менее двенадцати знаков. Но в данном случае веб-форму заполнил злоумышленник. Вместо того, чтобы указать почтовый индекс, он 256 раз ввел букву «А», а затем — несколько команд. Когда программа получает эту слишком длинную строку, бессмысленные данные переполняют буфер, выделенный для почтового индекса (напоминаю, что буферы — это участки памяти, зарезервированные для ввода данных), и команды злоумышленника попадают в стек. Так же как грабитель магазина незаметно вставляет страницу «Отдайте мне деньги», при переполнении буфера в программу добавляются инструкции, которые она обычно не выполняет. Конечно, компьютер воспринимает инструкции буквально, и, если они неидеальны, произойдет сбой программы. Если же инструкции безупречны, программа слепо выполняет команды злоумышленника.
В идеале программисты защищаются от переполнения буфера, оценивая все данные, поступающие в программу, и гарантируя их соответствие специально выделенной памяти. (В приведенном выше примере с почтовым индексом можно было бы задать в программе отбрасывание всех введенных данных после двенадцатого символа). В реальной жизни программисты часто забывают о злоумышленниках или иногда не полностью осознают, что ожидаемые программой данные поступают из ненадежного источника. Чем крупнее и сложнее становится программа, тем чаще случается непредвиденное.
Атаки на функции форматирования строк
Атака на функции форматирования строк также влияет на стек, но подразумевает гораздо меньшие изменения, чем переполнение буфера в стеке, которое мы уже обсудили. Форматирование заключается в подготовке данных для отображения на экране или для печати, но инструкции по форматированию настолько гибки, что некоторые злоумышленники нашли способы использования это процесса для записи в память. При атаке на функции форматирования строк в память обычно добавляется один адрес, который указывает на другой адрес в памяти, куда злоумышленник добавил новые команды. Вернемся опять к нашей аналогии с глупым работником и предположим, что книга инструкций содержит 25 страниц, но сразу после страницы «Возьмите деньги клиента и откройте кассовый аппарат» грабитель вставил инструкцию «Перейдите прямо на страницу 26». Грабитель мог подготовить инструкции на нескольких страницах, например «Отдайте клиенту всю наличность» и «Дайте клиенту спокойно уйти», и поместить их в конец книги. Если глупый работник следует этим указаниям, это аналогично тому, что происходит, когда злоумышленник заставляет программу перейти к определенному адресу в памяти и выполнять все инструкции, содержащиеся там.
Кучи проблем
Атака на кучи совсем не затрагивает стек. Вспомним аналогию с выбрасываемыми заметками: стек использует временную память. В отличие от него «кучей» в программировании обозначается память, которая не является временной, а предназначена для использования в ходе выполнения программы. Данные можно считывать со страниц кучи и записывать на них, и злоумышленники пользуются этим, вставляя вредоносные команды на страницы кучи, а затем обманом заставляя компьютер следовать этим инструкциям. Эта атака принципиально ничем не отличается от атаки на стек, за исключением того, что она происходит в куче, а не в стеке.
Способы защиты: жучки и канарейки
Мы уже знаем, что лучший способ защиты от любых атак подобного типа — безупречно написанные программы. В идеале каждое поле в любой программе должно разрешать ввод только определенного количества символов (принцип контроля границ) и только ожидаемых символов (почему программа должна принимать буквы или метасимволы, такие как %, в телефонном номере?). Мы также знаем, что программы неидеальны, в них есть ошибки (англ. «bugs» — жучки), некоторые из которых создают возможности для атак. Поскольку программы небезупречны, программисты придумали схемы для защиты от переполнения буфера.
Простейшая схема предписывает компьютеру использовать стек и кучу только для данных и никогда не выполнять инструкции, обнаруженные в стеке и в куче. Хотя такой подход отлично работает во многих системах UNIX, его нельзя использовать в системах Windows. Более того, эта схема заставляет системного администратора UNIX изменять параметры конфигурации на каждом отдельном сервере. Этот способ защиты проще использовать на отличных от Intel процессорах (например, на процессорах Sparc компании Sun Microsystem).
Другая популярная схема защищает от атак типа переполнение буфера, но только от тех, что перезаписывают стек. Этот прием заключается в использовании осведомителя (англ. «canary» — канарейка). Помните истории о шахтерах, которые брали с собой в угольные шахты канареек? В угольных шахтах часто случаются выбросы метана, который не имеет запаха, но вызывает потерю сознания и в итоге удушье. Если новый участок выработки содержал метан, то канарейка первой теряла сознание. Это служило шахтерам предупреждением, что нужно покинуть эту зону.
Для защиты стека осведомитель вставляется в чувствительные области памяти (рядом с адресами возврата в стеке, при помощи которых компьютер узнает, где находятся очередные команды, подлежащие выполнению, после завершения текущей функции). Перед использованием обратных адресов программа проверяет состояние осведомителя. Если осведомитель был затерт, это свидетельствует о неполадках, и программа завершает свою работу.
Общая идея применения осведомителей принадлежит группе разработчиков Linux, которые создали версию Linux (Immunix.com), использующую Stackguard для добавления осведомителей во все компоненты операционной системы и входящие в нее программы. Новый компилятор для среды Visual C от Microsoft также позволяет добавлять осведомителей в стек.
Осведомители действительно помогают, но они не могут защитить от атак на кучи. Атаки на кучи не влияют на стек вовсе, поэтому осведомители не позволяют обнаружить их. Программисты должны написать код, который будет копировать в буфер ровно столько данных, сколько он может вместить (то есть, другими словами, нужны идеальные программы). Это самый эффективный способ защиты.
Пользователи устройства Firebox от WatchGuard имеют дополнительную линию защиты. При настройке Firebox для использования шлюзов приложений (которые на языке WatchGuard называются «прокси») программное обеспечение устройства обнаруживает ввод слишком длинных данных в защищенных службах: электронной почте, HTTP, FTP и DNS. Не являясь идеальной защитой, прокси, тем не менее, могут блокировать многие атаки типа переполнения буфера. При использовании вместо прокси фильтрации пакетов, даже с отслеживанием состояния, данное преимущество будет потеряно.
Будем надеяться, этот краткий рассказ поможет вам понять смысл переполнения буфера и других типов атак, а также суть некоторых мер противодействия и причины того, что эти меры не всегда эффективны. Но вы не беззащитны. Если в вашем брандмауэре предусмотрены шлюзы приложений или прокси, используйте их. Когда информационные оповещения LiveSecurity предупреждают вас о наиболее известных факторах уязвимости, связанных с переполнением буфера, внесите исправления в свои приложения. Применяя эти меры и полученные новые сведения о переполнении буфера, вы сможете избежать проблем.
Рик Фэрроу (Rik Farrow), Watchguard Technologies, консультант по вопросам Интернет-безопасности
|