Автор: Сергей Васильев

Уязвимость с точки зрения компьютерной безопасности - это недостаток в системе, позволяющий кому-либо нарушить целостность или умышленно вызвать сбой в работе программы. Практика показывает, что даже кажущаяся незначительной ошибка может оказаться серьезной уязвимостью. Уязвимости можно избежать, используя различные методы валидации и верификации программного обеспечения, включая статический анализ. В этой статье будет затронута тема того, как PVS-Studio справляется с задачей поиска уязвимостей.

PVS-Studio - это инструмент, предотвращающий не только ошибки, но и уязвимости

Позже в статье я расскажу, как мы пришли к такому выводу. Но сначала я хотел бы сказать несколько слов о самом PVS-Studio.

PVS-Studio - статический анализатор кода, который ищет ошибки (и уязвимости) в программах, написанных на языках C, C ++ и C #. Он работает под Windows и Linux и может быть интегрирован в Visual Studio IDE как плагин. На данный момент в анализаторе более 450 диагностических правил, каждое из которых описано в документации.

К моменту публикации статьи мы проверили более 280 проектов с открытым кодом, в которых нашли более 11 000 ошибок. Довольно интересно, количество этих ошибок, которые являются настоящими уязвимостями…

Вы можете скачать PVS-Studio на официальном сайте и попробовать сами.

Кстати, мы предлагаем экспертам по безопасности лицензии на PVS-Studio. Если вы являетесь экспертом в области безопасности и ищете уязвимости, вы можете связаться с нами для получения лицензии. Подробнее об этом предложении читайте в статье Выдача лицензий на анализатор PVS-Studio экспертам по безопасности.

Терминология

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

CWE (Common Weakness Enumeration) - сводный список дефектов безопасности. Common Weakness Enumeration (CWE), предназначенный как для сообщества разработчиков, так и для сообщества специалистов по безопасности, представляет собой формальный список или словарь общих слабых мест программного обеспечения, которые могут возникать в архитектуре, дизайне, коде или реализации программного обеспечения, которые могут привести к использованию уязвимостей безопасности. CWE был создан, чтобы служить общим языком для описания слабых мест в безопасности программного обеспечения; в качестве стандартной меры для инструментов безопасности программного обеспечения, нацеленных на эти слабые места; и предоставить общий базовый стандарт для усилий по выявлению, смягчению и предотвращению слабых мест.

CVE (Common Vulnerabilities and Exposures) - программные ошибки, которые могут напрямую использоваться хакерами.

Корпорация MITER начала работу над классификацией уязвимостей программного обеспечения в 1999 году, когда был составлен список общих уязвимостей и программных обязательств (CVE). В 2005 году в рамках дальнейшего развития системы CVE коллектив авторов начал работу по предварительной классификации уязвимостей, атак, сбоев и других проблем безопасности с целью определения общих дефектов безопасности программного обеспечения. Однако, несмотря на самодостаточность классификации, созданной в рамках CVE, она оказалась слишком грубой для определения и классификации методов оценки защищенности кода, используемых анализаторами. Таким образом, список CWE был создан для решения этой проблемы.

PVS-Studio: другая точка зрения

Задний план

Исторически мы позиционировали PVS-Studio как инструмент для поиска ошибок. В статьях об анализе нашего проекта мы всегда использовали соответствующую терминологию: баг, ошибка, опечатка. Понятно, что разные ошибки имеют разную степень серьезности: могут быть некоторые фрагменты кода, содержащие избыточный или вводящий в заблуждение код, но есть некоторые ошибки, которые приводят к сбою всей системы в 5 часов утра каждые три дня. Все было ясно, эта концепция давно не развивалась - ошибки были просто ошибками.

Однако со временем выяснилось, что некоторые из ошибок, обнаруженных PVS-Studio, могут быть более серьезными. Например, неправильно используемая функция printf может вызвать гораздо больше негативных последствий, чем вывод неправильного сообщения в stdout. Когда стало ясно, что целый ряд диагностических правил может обнаруживать не только ошибки, но и слабые места (CWE), мы решили исследовать этот вопрос более подробно и посмотреть, как диагностические правила PVS-Studio могут быть связаны с CWE.

Связь PVS-Studio и CWE

По результатам выявления корреляции предупреждений PVS-Studio и CWE мы создали следующую таблицу:

Приведенный выше вариант таблицы не является окончательным, но он дает некоторое представление о том, как некоторые из предупреждений PVS-Studio связаны с CWE. Теперь понятно, что PVS-Studio успешно обнаруживает (и всегда обнаруживал) не только ошибки в коде программы, но и потенциальные уязвимости, то есть CWE. На эту тему было написано несколько статей, они перечислены в конце этой статьи.

Базы CVE

Потенциальная уязвимость (CWE) еще не является реальной уязвимостью (CVE). Реальные уязвимости, обнаруженные как в открытых, так и в проприетарных проектах, собраны на сайте http://cve.mitre.org. Там вы можете найти описание конкретной уязвимости, дополнительные ссылки (обсуждения, бюллетень исправлений уязвимостей, ссылки на коммиты, исправление уязвимостей и т. Д.). При желании базу данных можно скачать в необходимом формате. На момент написания статьи .txt файл базы уязвимостей имел размер около 100 МБ и более 2,7 млн ​​строк. Впечатляет, да?

Изучая эту статью, я обнаружил довольно интересный ресурс, который может быть полезен тем, кому это интересно, - http://www.cvedetails.com/. Это удобно за счет таких особенностей, как:

  • Поиск CVE по идентификатору CWE;
  • Поиск CVE в определенном продукте;
  • Просмотр статистики появления / исправления уязвимостей;
  • Просмотр различных таблиц данных, так или иначе связанных с CVE (например, рейтинг компаний, в продуктах которых было обнаружено наибольшее количество уязвимостей);
  • И многое другое.

Некоторые CVE, которые можно было найти с помощью PVS-Studio

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

Мы никогда не исследовали, можно ли использовать определенный дефект, обнаруженный PVS-Studio, в качестве уязвимости. Это довольно сложно, и мы никогда не ставили перед собой такую ​​задачу. Поэтому поступлю иначе: возьму несколько уже обнаруженных и описанных уязвимостей и покажу, что их можно было бы избежать, если бы код регулярно проверялся PVS-Studio.

Примечание. Описанные в статье уязвимости обнаружены не в синтетических примерах, а в реальных исходных файлах, взятых из старых ревизий проекта.

иллюминатор

Первая уязвимость, о которой мы поговорим, была обнаружена в исходном коде проекта illumos-gate. illumos-gate - проект с открытым исходным кодом (доступен в репозитории GitHub), образующий ядро ​​операционной системы, основанный на Unix в BSD.

Уязвимость носит название CVE-2014–9491.

Описание CVE-2014–9491: функция devzvol_readdir в illumos не проверяет возвращаемое значение вызова strchr, что позволяет удаленным злоумышленникам вызвать отказ в обслуживании (разыменование указателя NULL и паника) через неопределенные векторы.

Код проблемы находился в функции devzvol_readdir:

static int devzvol_readdir(....)
{
  ....
  char *ptr;
  ....
  ptr = strchr(ptr + 1, '/') + 1;
  rw_exit(&sdvp->sdev_contents);
  sdev_iter_datasets(dvp, ZFS_IOC_DATASET_LIST_NEXT, ptr);
  ....
}

Функция strchr возвращает указатель на вхождение первого символа, переданный в качестве второго аргумента. Однако функция может вернуть нулевой указатель, если символ не был найден в исходной строке. Но этот факт забыли или не приняли во внимание. В результате к возвращаемому значению просто добавляется 1, результат записывается в переменную ptr, а затем указатель обрабатывается как есть. Если полученный указатель был нулевым, то, добавив к нему 1, мы получим недействительный указатель, проверка которого на соответствие NULL не будет означать его достоверность. При определенных условиях этот код может вызвать панику ядра.

PVS-Studio обнаруживает эту уязвимость с помощью диагностического правила V769, согласно которому указатель, возвращаемый функцией strchr, может быть нулевым, и при этом он повреждается (из-за добавления 1):

V769 Указатель ‘strchr (ptr + 1,‘ / ’)’ в выражении ‘strchr (ptr + 1,‘ / ’) + 1’ может иметь значение nullptr. В таком случае полученное значение будет бессмысленным и не может быть использовано.

Сетевая аудиосистема

Сетевая аудиосистема (NAS) - прозрачная для сети, клиент-серверная аудиотранспортная система, исходный код которой доступен на SourceForge. NAS работает под Unix и Microsoft Windows.

Обнаруженная в этом проекте уязвимость имеет кодовое имя CVE-2013–4258.

Описание CVE-2013–4258: уязвимость строки формата в функции osLogMsg в server / os / aulog.c в сетевой аудиосистеме (NAS) 1.9.3 позволяет удаленным злоумышленникам вызвать отказ в обслуживании (сбой) и, возможно, выполнить произвольный код с помощью спецификаторов строки формата в неопределенных векторах, связанных с syslog.

Код был следующий:

....
if (NasConfig.DoDaemon) {   /* daemons use syslog */
  openlog("nas", LOG_PID, LOG_DAEMON);
  syslog(LOG_DEBUG, buf);
  closelog();
} else {
  errfd = stderr;
....

В этом фрагменте неправильно используется функция syslog. Объявление функции выглядит следующим образом:

void syslog(int priority, const char *format, ...);

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

Если верить записям в базе SecurityFocus, уязвимость обнаружилась в Debian и Gentoo.

А что же тогда с PVS-Studio? PVS-Studio обнаруживает эту ошибку диагностическим правилом V618 и выдает предупреждение:

V618 Опасно вызывать функцию «syslog» таким образом, так как передаваемая строка может содержать спецификацию формата. Пример безопасного кода: printf («% s», str);

Встроенный в анализатор механизм аннотации функций помогает обнаруживать подобные ошибки; количество аннотированных функций более 6500 для C и C ++ и более 900 для C #.

Вот правильный вызов этой функции, устраняющей эту уязвимость:

syslog(LOG_DEBUG, "%s", buf);

Он использует строку формата «% s», что делает вызов функции syslog безопасным.

Ytnef (программа чтения потокового видео Йерасе в формате TNEF)

Ytnef - программа с открытым исходным кодом, доступная на GitHub. Он предназначен для декодирования потоков TNEF, например, созданных в Outlook.

За последние несколько месяцев было обнаружено немало уязвимостей, которые описаны здесь. Рассмотрим одну из CVE, приведенных в этом списке - CVE-2017–6298.

Описание CVE-2017–6298: проблема была обнаружена в ytnef до 1.9.1. Это связано с патчем, описанным как «1 из 9. Возвращаемое значение Null Pointer Deref / calloc не проверено».

Все фиксированные фрагменты, которые могли содержать разыменование нулевого указателя, были примерно одинаковы:

vl->data = calloc(vl->size, sizeof(WORD));
temp_word = SwapWord((BYTE*)d, sizeof(WORD));
memcpy(vl->data, &temp_word, vl->size);

Во всех этих случаях уязвимости вызваны неправильным использованием функции calloc. Эта функция может возвращать нулевой указатель в случае, если программе не удалось выделить запрошенный блок памяти. Но полученный указатель не проверяется на NULL и используется с учетом того, что calloc всегда будет возвращать ненулевой указатель. Это немного необоснованно.

Как PVS-Studio обнаруживает уязвимости? Довольно просто: в анализаторе есть множество диагностических правил, обнаруживающих работу с нулевыми указателями.

В частности, описанные выше уязвимости будут обнаружены диагностикой V575. Вот как выглядит предупреждение:

V575 Потенциальный нулевой указатель передается в функцию memcpy. Проверьте первый аргумент.

Анализатор обнаружил, что потенциально нулевой указатель, полученный в результате вызова функции calloc, передается в функцию memcpy без проверки на NULL .

Так PVS-Studio обнаружил эту уязвимость. Если бы анализатор регулярно использовался при написании кода, этой проблемы можно было бы избежать до того, как он попадет в систему контроля версий.

MySQL

MySQL - это система управления реляционными базами данных с открытым исходным кодом. Обычно MySQL используется как сервер, к которому имеют доступ локальные или удаленные клиенты; однако в комплект поставки входит библиотека внутреннего сервера, позволяющая встраивать MySQL в отдельные программы.

Рассмотрим одну из уязвимостей, обнаруженных в этом проекте - CVE-2012–2122.

Описание CVE-2012–2122: sql / password.c в Oracle MySQL 5.1.x до 5.1.63, 5.5.x до 5.5.24 и 5.6.x до 5.6.6 и MariaDB 5.1.x до 5.1.62, 5.2.x до 5.2.12, 5.3.x до 5.3.6 и 5.5.x до 5.5.23, при работе в определенных средах с определенными реализациями memcmp , позволяет удаленным злоумышленникам обходить аутентификацию путем многократной аутентификации с одним и тем же неверным паролем, что в конечном итоге приводит к успешному сравнению токенов из-за неправильно проверенного возвращаемого значения.

Вот код с уязвимостью:

typedef char my_bool;
my_bool
check_scramble(const char *scramble_arg, const char *message,
               const uint8 *hash_stage2)
{
  ....
  return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE);
}

Тип возвращаемого значения функции memcmp - int, а тип возвращаемого значения check_scramble - my_bool, но на самом деле - char. В результате происходит неявное преобразование int в char, во время которого значимые биты теряются. . Это привело к тому, что в 1 из 256 случаев можно было войти в систему с любым паролем, зная имя пользователя. Ввиду того, что 300 попыток подключения заняли менее секунды, эта защита почти ничем не отличается. Вы можете найти более подробную информацию об этой уязвимости по ссылкам, указанным на следующей странице: CVE-2012–2122.

PVS-Studio обнаруживает эту проблему с помощью диагностического правила V642. Предупреждение следующее:

V642 Неуместно сохранять результат функции «memcmp» внутри переменной типа «char». Значимые биты могут быть потеряны, нарушая логику программы. password.c

Как видите, обнаружить эту уязвимость удалось с помощью PVS-Studio.

iOS

iOS - мобильная операционная система для смартфонов, планшетов и портативных плееров, разработанная и производимая Apple.

Рассмотрим одну из уязвимостей, обнаруженных в этой операционной системе; CVE-2014–1266. К счастью, фрагмент кода, по которому мы можем увидеть, в чем проблема, общедоступен.

Описание уязвимости CVE-2014–1266: функция SSLVerifySignedServerKeyExchange в libsecurity_ssl / lib / sslKeyExchange.c в функции безопасного транспорта в компоненте безопасности данных в Apple iOS 6. x до 6.1.6 и 7.x до 7.0.6, Apple TV 6.x до 6.0.2 и Apple OS X 10.9.x до 10.9.2 не проверяет подпись в сообщении об обмене ключами сервера TLS, что позволяет Злоумышленники типа «человек посередине» подделывают серверы SSL, (1) используя произвольный закрытый ключ для шага подписи или (2) пропуская шаг подписи.

Фрагмент кода, вызвавший уязвимость, имел следующий вид:

static OSStatus
SSLVerifySignedServerKeyExchange(SSLContext *ctx, 
                                 bool isRsa, 
                                 SSLBuffer signedParams,
                                 uint8_t *signature, 
                                 UInt16 signatureLen)
{
  OSStatus err;
  ....
  if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
    goto fail;
  if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
    goto fail;
    goto fail;
  if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
    goto fail;
  ....
fail:
  SSLFreeBuffer(&signedHashes);
  SSLFreeBuffer(&hashCtx);
  return err;
}

Проблема заключается в двух операторах goto, написанных близко друг к другу. Первый относится к оператору if, а второй - нет. Таким образом, независимо от значений предыдущих условий, поток управления перейдет к метке «сбой». Из-за второго оператора goto значение err будет успешным. Это позволило злоумышленникам «злоумышленник посередине» подделать серверы SSL.

PVS-Studio обнаруживает эту проблему с помощью двух диагностических правил - V640 и V779. Это предупреждения:

  • V640 Операционная логика кода не соответствует его форматированию. Оператор смещен вправо, но выполняется всегда. Возможно, что фигурные скобки отсутствуют.
  • V779 Обнаружен недостижимый код. Возможно, ошибка присутствует

Таким образом, анализатор предупреждает о нескольких вещах, которые ему показались подозрительными.

  • логика программы не соответствует форматированию кода: судя по выравниванию, создается впечатление, что оба оператора goto относятся к оператору if, но не т так. Первый goto действительно находится в состоянии, а второй - нет.
  • недостижимый код: поскольку второй goto выполняется без условия, следующий за ним код не будет выполнен.

Оказывается, здесь PVS-Studio также успешно справился со своей работой.

Эффективное использование статического анализа

Цель данной статьи, как я уже упоминал ранее, - показать, что анализатор PVS-Studio успешно обнаруживает уязвимости. Подход, выбранный для достижения этой цели, заключается в демонстрации того, что анализатор находит некоторые хорошо известные уязвимости. Материал был необходим для подтверждения того, что поиск уязвимостей возможен с помощью статического анализа.

Теперь я хотел бы поговорить о том, как сделать это более эффективно. В идеале уязвимости следует обнаруживать до того, как они превратятся в уязвимости (т. Е. Когда кто-то обнаружит их и поймет, как их можно использовать); чем раньше они будут обнаружены, тем лучше. При правильном использовании статического анализа уязвимости могут быть обнаружены еще на этапе кодирования. Ниже приводится описание того, как этого можно достичь.

Примечание. В этом разделе я буду использовать слово «ошибка» для единообразия. Но, как мы уже видели, простые ошибки могут быть потенциальными, а затем и реальными уязвимостями. Пожалуйста, не забывай об этом.

В общем, чем раньше будет обнаружена и исправлена ​​ошибка, тем меньше будет стоимость ее исправления. На рисунке представлены данные из книги Каперса Джонса «Applied Software Measurement».

Как видно на графиках, примерно 85% ошибок совершаются на этапе кодирования, когда стоимость исправления минимальна. Поскольку ошибка продолжает существовать в коде, стоимость ее исправления постоянно растет; если исправление ошибки на этапе кодирования стоит всего 25 долларов, то после выпуска программы эта цифра увеличивается до десятков тысяч долларов. Не говоря уже о стоимости уязвимостей, обнаруженных после релиза.

Отсюда следует простой вывод - чем раньше ошибка будет обнаружена и исправлена, тем лучше. Целью статического анализа является как можно более раннее обнаружение ошибок в коде. Статический анализ - это не замена других инструментов валидации и верификации, а отличное дополнение к ним.

Как получить максимальную отдачу от статического анализатора? Первое правило - код необходимо регулярно проверять. В идеале ошибка должна быть исправлена ​​на этапе кодирования, прежде чем она будет зафиксирована в системе контроля версий.

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

Но даже при таком использовании анализатора есть вероятность попадания ошибки в систему контроля версий. Вот почему так важно иметь «второй уровень защиты» - использовать статический анализатор на сервере сборки. Например, чтобы интегрировать анализ кода в процесс ночных сборок. Это позволит проверять проекты ночью, а утром собирать информацию об ошибках, попавших в систему контроля версий. Здесь важно немедленно исправить обнаруженные таким образом ошибки - желательно на следующий день. Иначе со временем на новые ошибки никто не обратит внимания и толку от таких проверок будет мало.

Внедрение статического анализа в процесс разработки может показаться нетривиальной задачей, если проект разрабатывается не с нуля. В статье Как быстро интегрировать статический анализ в большой проект? Дается четкое объяснение того, как правильно начать использовать статический анализ.

Вывод

Надеюсь, мне удалось это показать:

  • даже кажущаяся простая ошибка может оказаться серьезной уязвимостью;
  • PVS-Studio успешно справляется не только с обнаружением ошибок в коде, но и с CWE и CVE.

И если стоимость простой ошибки со временем увеличивается, цена уязвимости может быть огромной. При этом с помощью статического анализа многие уязвимости можно исправить еще до того, как они попадут в систему контроля версий; не говоря уже о том, что кто-то найдет их и не начнет эксплуатировать.

Напоследок я хотел бы порекомендовать попробовать PVS-Studio на своем проекте - а что, если вы найдете что-то, что спасет ваш проект от попадания в базу CVE?

Дополнительные ссылки