Проблемы Безопасности в ATL. Бюллетень MS09-035 и Схема SDL

Здравствуйте, с вами Майкл Ховард.

Microsoft Security Response Center (MSRC) выпустил два новых бюллетеня безопасности для передачи управляющей информации отдельно от передачи данных - MS09-034 и MS09-035, а также набор советов по информационной безопасности Security Advisory(973882) для устранения уязвимостей в библиотеке Microsoft Active Template Library (ATL). Я хотел бы объяснить, почему невозможно обнаружить эти ошибки с помощью схемы SDL.

Я всегда повторяю, что любая ошибка - это возможность узнать что-то новое и при необходимости применить это далее на практике. В этой статье будет рассказано в общих чертах об уязвимостях в ATL. О механизме глубоко эшалонированной защиты (defense-in-depth) , добавленном в Internet Explorer вместе с обновлением MS09-034 можно прочесть в статье Дэйва Росса на блоге Security Research & Defense.

Но прежде чем рассказывать об этих уязвимостях, я уделю несколько минут описанию того, что представляет из себя библиотека ATL.

Active Template Library - набор облегченных классов C++ , изначально созданный для более простой работы с COM-объектами. ATL с легкостью контролирует подсчет ссылок на объекты и справляется со стандартными задачами COM. Но библиотека ATL не ограничена COM-технологией, в ней есть классы для работы с интеллектуальными указателями(smart pointers), изображениями(images), системным реестром(registry), списками контроля доступа(ACLs) и много других возможностей.

Когда разработчик создает новый проект в Visual Studio, он может выбрать создание проекта ATL. В этом случае основные заголовки будут подключены автоматически.

Еще один ключевой момент состоит в том, что исходный код библиотеки ATL доступен для просмотра. В случае использования Visual Studio 2008 он находится в папке %ProgramFiles%\Microsoft Visual Studio 9.0\vc\atlmfc.

Теперь давайте перейдем к рассмотрению уязвимостей.

Уязвимость #1: Опечатка.

Это главная проблема элемента управления ActiveX MSVidCtl. Ошибка является модификацией уязвимости более старой версии ATL и встречается не в публичной версии кода ATL, а в версии с индивидуальным обновлением.

Справка. В чем отличия ActiveX и COM ?

Этот абзац вы можете пропустить, я добавил его лишь потому, что часто отвечаю на этот вопрос. Объектная модель компонентов COM - это бинарная спецификация, определяющая взаимодействие объектов. Объект ActiveX является объектом COM. Главное свойство объекта ActiveX - это возможность его использования в скриптовых языках. Эту функцию часто называют "автоматизацией". Объекты ActiveX используют интерфейс COM IDispatch, который позволяет машине сценариев принимать решения и вызывать методы объектов во время исполнения программы. Это называется динамическим связыванием.

Данная уязвимость представляет из себя просто опечатку. Интересно, а вы сможете ее найти? Я специально убрал не относящийся к делу код и проверку ошибок, чтобы опечатку было легче заметить. И убрал ссылки на переменную psa (оригинально она была структурой SAFEARRAYBOUND, если кому интересно).

 

    1:  
    2: __int64 cbSize;
    3: hr = pStream->Read((void*) &cbSize, sizeof(cbSize), NULL);
    4: BYTE *pbArray;
    5: HRESULT hr = SafeArrayAccessData(psa, reinterpret_cast<LPVOID 
    6: *>(&pbArray));
    7: hr = pStream->Read((void*)&pbArray, (ULONG)cbSize, NULL);
    8: Даю вам еще одну подсказку - это ошибка всего в одном символе.
    9: Сдаетесь? Посмотрите на последнюю строчку. Первый аргумент функции неверен, 
   10: должно быть:
   11: hr = pStream->Read((void*)pbArray, (ULONG)cbSize, NULL);

Лишний символ "&" в уязвимом коде дает возможность записать потенциально опасную информацию объема cbSize по адресу указателя на массив pbArray, вместо того чтобы поместить информацию в массив. А указатель находится в стеке. Таким образом, это уязвимость переполнения стека.

Это крайне сложно заметить это при обзоре кода, и это не отслеживается компилятором C/C++ из-за приведения типов (void*). Если же приведения типов убрать, то компилятор выдаст ошибку наподобие такой:

 

    1: C2664: '<function>' : cannot convert parameter 1 from 'BYTE **' to 'BYTE *'

Я крайне не люблю приведение типов в стиле C, т.к. оно совершенно небезопасно. Приведение типов С++ куда более безопасно несмотря на то, что оператор reinterpret_cast так же плох, как и приведение в стиле C.

Почему мы упустили данную уязвимость?

Наша утилита статистического анализа не замечает эту ошибку, так как приведение типов не считается потенциально опасным. Я специально проверил несколько дюжин примеров использования приведения типов, подобных этому, в различных базах исходных текстов, и все они были корректны. Так что проверка подобных ситуаций в коде изначально привела бы к ложному срабатыванию, и в большинстве случаев безопасный код расценивался бы как уязвимый.

В схеме SDL мы требуем от команд разработчиков проверять их элементы управления fuzz-тестами. Но наши fuzz-утилиты не находят эту уязвимость. Так как изначально в тесте на вход приложения подаются специально подготовленные данные с сигнальными байтами, которые выявляют определенную уязвимость. Подробнее о недостатках fuzz-утилит я рассказывал здесь. В данный момент мы совершенствуем инструмент fuzz-тестов, добавляем больше эвристики, чтобы программа сама включала в проверку COM-специфичные байты, когда это необходимо.

Устранение запрещенных API (Banned API Removal) не видит эту ошибку, так как в данном случае нет запрещенных API.

Некоторые средства защиты, такие как ASLR или DEP в Windows могут подействовать. Такой ответ покажется неопределенным, но я употребил слово "могут", так как ATL - это библиотека шаблонов исходного кода, которая используется при построении программ, и разработчики сами выбирают, применять эти средства защиты или нет. Владельцы Internet Explorer 8 на Windows Vista SP1 и выше защищены лучше, потому что ASLR и DEP у них активны по умолчанию.

Можно даже откомпилировать код с флагом /GS, но это не поможет. Так как в этой уязвимости нет локальных переменных для защиты, и соответственно, нет и подходящего stack cookie для отслеживания такой функции.

Уязвимость #2: Использование карт свойств ATL для создания экземпляра COM-объекта.

ATL дает объектам возможность очень просто сохранять свои свойства в поток байтов. В дальнейшем объект может перезаписать этот байтовый поток. ATL реализует эту функцию при помощи "карты свойств". Поток может быть составлен из серии кортежей. При использовании кортежей первые байты обозначают тип данных, и, основываясь на типе, размер данных (к примеру, n-байтовая строка [VT_BSTR]), а далее идут сами данные.

Если тип данных - VT_DISPATCH или VT_UNKNOWN, то элемент управления может быть уязвимым.

Уязвимый код есть в исходных данных библиотеки ATL, а именно в методе

 

    1: CComVariant::ReadFromStream()

Как мы это пропустили? Схема SDL не дает никаких указаний или рекомендаций по поводу использования карт свойств ATL, по сути, SDL дает мало инструкций по поводу COM-контейнеров, прежде всего из-за того, что их немного. Самый широко известный COM-контейнер - это Internet Explorer. Тем не менее мы призываем команды разработчиков использовать утилиты, чтобы определять свои безопасные для скриптинга(Safe-for-Scripting) и безопасные для создания экземпляра(Safe-for-Instantiation) управляющие элементы.

В теории, fuzz-тесты должны были обнаружить данную уязвимость, но наша fuzz-утилита не формирует подходящий для этого поток входящих данных (см. предыдущую уязвимость).

Какие меры мы предпринимаем?

Я хочу отметить, что в данный момент все это меняется благодаря нашему непрерывному выпуску бюллетеней безопасности, и мы работаем над улучшением схемы SDL.

Прежде всего, мы обновляем наши fuzz-утилиты для нахождения потоковых уязвимостей COM, и далее обновим схему SDL, чтобы разработчики проверяли fuzz-тестами все COM-объекты, написанные с использованием небезопасных интерфейсов (таких как IPersistStream*, IPersistStorage и др.)

Во вторых , мы предупредим разработчиков, чтобы они использовали только новые версии библиотеки ATL. Сейчас у нас существует требование "минимальный набор инструментов компилятора и компоновщика", но оно те указывает разработчикам, какую библиотеку ATL необходимо использовать. Это будет исправлено.

В заключение, я хотел бы заняться подробнее проблемами приведения типов. Поэтому в ближайшие несколько месяцев я просмотрю базы данных ошибок на предмет похожих проблем. Также я переговорю с различными специалистами статистического анализа и языка С/С++ в Майкрософт и за пределами для выяснения их догадок и соображений. Если у вас есть профессиональные соображения по поводу проблем приведения типов, можете высказывать их через этот блог!

Оригинал: https://blogs.msdn.com/sdl/archive/2009/07/28/atl-ms09-035-and-the-sdl.aspx

Перевод: Антон Зайцев