Ошибки при работе с форматной строкой могут дать такой же эффект, как переполнение буфера, хотя переполнением в строгом смысле не являются. Обычно такие ошибки вообще не связаны ни с какими буферами.
Вариантом переполнения буфера является запись в массив без контроля выхода за границы. Если противник сумеет прямо или косвенно подсунуть индекс массива и вы не проверите, что он принадлежит допустимому диапазону, то возможна запись по произвольному адресу в памяти. При этом не только изменяется поток выполнения программы, но могут быть затерты несмежные области памяти, а это сводит на нет все меры противодействия переполнению буфера.
Вот на что нужно обращать внимание в первую очередь:
□ любые входные данные, будь то из сети, из файла или из командной строки;
□ передача данных из вышеупомянутых источников входных данных во внутренние структуры;
□ использование небезопасных функций работы со строками;
□ использование арифметических операций для вычисления размера буфера или числа свободных байтов в нем.
Выявление ошибки на этапе анализа кода
Обнаружить присутствие этого греха во время анализа кода может быть как совсем легко, так и очень сложно. Проще всего проанализировать все случаи употребления функций работы со строками. Надо иметь в виду, что вы можете найти много мест, где функции вызываются безопасно, но наш опыт показывает, что ошибки могут скрываться даже в правильных вызовах. Коэффициент регрессии, характерный для модификации кода с целью перехода исключительно на безопасные функции, обычно очень мал (от одной десятой до одной сотой величины, типичной для исправления ошибки), зато это позволит устранить возможность некоторых видов эксплойтов.
Добиться этого можно, например, поручив выполнение задачи компилятору. Если вы исключите объявления функций strcpy, strcat, sprintf и им подобных из заголовочных файлов, то компилятор укажет все места в коде, где они встречаются. Но имейте в виду, что некоторые приложения полностью или частично переопределяют библиотеку времени исполнения для языка С.
Сложнее отыскать переполнение кучи. Чтобы решить эту задачу, нужно помнить о возможности переполнения целых, о чем пойдет речь в грехе 3. Начать нужно с выявления всех мест, где производится выделение памяти, а затем проверить, с помощью каких арифметических операций вычислялся размер буфера.
Наилучший подход состоит в том, чтобы проследить, как используются все поступающие от пользователя данные, начиная с точки входа в приложение и далее по всем функциям. Очень важно знать, что именно может контролировать противник.
Одной из наиболее эффективных методик является рандомизированное тестирование (fuzz testing), когда на вход подаются полуслучайные данные. Попробуйте увеличить длину входных строк и понаблюдайте за поведением приложения. Обратите внимание на одну особенность: иногда множество неправильных значений входных данных довольно мало. Например, в одном месте программы проверяется, что длина входной строки должна быть меньше 260 байтов, а в другом месте выделяется буфер длиной 256. Если вы подадите на вход очень длинную строку, то она, конечно, будет отвергнута, но стоит попасть точно в неконтролируемый интервал–и можно писать эксплойт. Часто проблему можно найти, используя при тестировании степени двойки или степени двойки плюс–минус единица.
Стоит также поискать те места, где пользователь может задать длину чего–либо. Измените длину так, чтобы она не соответствовала строке, и особое внимание обращайте на возможность переполнения целого: опасность представляют случаи, когда длина +1 = 0.
Для рандомизированного тестирования нужно собрать специальную тестовую версию программы. В отладочные версии часто вставляют утверждения, которые изменяют поток выполнения программы и могут помешать обнаружить условия, при которых возможен экплойт. С другой стороны, современные компиляторы включают в отладочные версии хитроумный код для обнаружения порчи стека. В зависимости от используемого распределителя памяти и операционной системы вы можете также включить более строгую проверку целостности кучи.
Если вы используете утверждения для контроля входных данных, то имеет смысл перейти от такой формы:
...
к следующей
...
if(len >= MAX_PATH)
{
assert(false);
return false;
}
Всегда следует тестировать программу с помощью какой–либо утилиты обнаружения ошибок при работе с памятью, например AppVerifier для Windows (см. ссылку в разделе «Другие ресурсы»). Это позволит выявить ошибки, связанные с небольшим или трудноуловимым переполнением буфера.
Примеры из реальной жизни
Ниже приведены некоторые примеры переполнения буфера, взятые из базы данных типичных уязвимостей и брешей (CVE) на сайте http://cve.mitre.org. Интересно, что когда мы работали над этой книгой, в базе CVE по запросу «buffer overrim» находилось 1734 записи. Поиск по бюллетеням CERT, в которых документируются самые широко распространенные и серьезные уязвимости, по тому же запросу дал 107 документов.
CVE–1999–0042
Цитата из описания ошибки: «Переполнение буфера в реализации серверов IMAP и POP Вашингтонского университета». Эта же ошибка очень подробно документирована в бюллетене CERT за номером СА–1997–09. Переполнение происходит во время аутентификации для доступа к серверам, реализующим протоколы Post Office Protocol (POP) и Internet Message Access Protocol (IMAP). Связанная с ней уязвимость состоит в том, что сервер электронной почты не мог отказаться от избыточных привилегий, поэтому эксплойт давал противнику права пользователя root. Это переполнение поставило под удар довольно много систем.
Контрольная программа, созданная для поиска уязвимых версий этого сервера, обнаружила аналогичные дефекты в программе SLMail 2.5 производства Seattle Labs, о чем помещен отчет на странице www.winnetmag.com/Article/ArticleID/9223/ 9223.html.
CVE–2000–0389 – CVE–2000–0392
Из CVE–2000–0389: «Переполнение буфера в функции krb_rd_req в Kerberos версий 4 и 5 позволяет удаленному противнику получить привилегии root».
Из CVE–2000–0390: «Переполнение буфера в функции krb425_conv_principal в Kerberos 5 позволяет удаленному противнику получить привилегии root».
Из CVE–2000–0391: «Переполнение буфера в программе krshd, входящей в состав Kerberos 5, позволяет удаленному противнику получить привилегии root».
Из CVE–2000–0392: «Переполнение буфера в программе krshd, входящей в состав Kerberos 5, позволяет удаленному противнику получить привилегии root».
Эти ошибки в реализации системы Kerberos производства МТИ документированы в бюллетене CERT СА–2000–06 по адресу www.cert.org/advisories/CA–2000–06.html. Хотя исходные тексты открыты уже несколько лет и проблема коренится в использовании опасных функций работы со строками (strcat), отчет о ней появился только в 2000 году.
CVE–2002–0842, CVE–2003–0095, CAN–2003–0096
Из CVE–2002–0842:
Ошибка при работе с форматной строкой в одной модификации функции mod_dav, применяемой для протоколирования сообщений о плохом шлюзе (например, Oracle9i Application Server 9.0.2), позволяет удаленному противнику выполнить произвольный код, обратившись к URI, для которого сервер возвращает ответ «502 BadGateway». В результате функция dav_lookup_uri() в файле mod_dav.c возвращает спецификаторы форматной строки, которые потом используются при вызове ap_log_rerror().
Из CVE–2003–0095: Переполнение буфера в программе ORACLE.EXE для Oracle Database Server 9i, 8i, 8.1.7 и 8.0.6 позволяет удаленному противнику выполнить произвольный код, задав при входе длинное имя пользователя. Ошибкой можно воспользоваться из клиентских приложений, самостоятельно выполняющих аутентификацию, что и продемонстрировано на примере LOADPSP.
Из CAN–2003–0096: Многочисленные ошибки переполнения буфера в Oracle 9i Database Release 2, Release 1, 8i, 8.1.7 и 8.0.6 позволяют удаленному противнику выполнить произвольный код, (1) задав длинную строку преобразования в качестве аргумента функции TO_TIMESTAMP_TZ, (2) задав длинное название часового пояса в качестве аргумента функции TZ_OFFSET и (3) задав длинную строку в качестве аргумента DIRECTORY функции BFILENAME.
Эти ошибки документированы в бюллетене CERT СА–2003–05 по адресу www.cert.org/advisories/CA–2003–05.html. Их – в числе прочих – обнаружил Дэвид Литчфилд со своими сотрудниками из компании Next Generation Security Software Ltd. Попутно отметим, что не стоит объявлять свои приложения «не–взламываемыми», если за дело берется г–н Литчфилд.
CAN–2003–0352
Из описания в CVE:
Переполнение буфера в одном интерфейсе DCOM, используемом в системе RPC, применяемой в Microsoft Windows NT 4.0,2000, ХР и Server 2003, позволяет удаленному противнику выполнить произвольный код, сформировав некорректное сообщение. Этой ошибкой воспользовались черви Blaster/MSblast/LovSAN and Nachi/Welchia.
Это переполнение интересно тем, что привело к широкому распространению двух весьма разрушительных червей, что повлекло за собой глобальные неприятности в сети Интернет. Переполнялся буфер в куче, доказательством возможности эксплуатации ошибки стало появление очень стабильного червя. Ко всему прочему еще был нарушен принцип предоставления наименьших привилегий, этот интерфейс не должен был быть доступен анонимным пользователям. Интересно еще отметить, что предпринятые в Windows 2003 контрмеры свели последствия атаки к отказу от обслуживания вместо эскалации привилегий. Подробнее об этой проблеме можно прочитать на страницах www.cert.org/ advisories/CA–2003–23.html и www.microsoft.com/technet/security/bulletin/MS03–039.asp.