Часть 2. Как искать ошибки в чужом коде
В предыдущей статье мы рассматривали фреймворки и методологии для безопасной разработки собственного кода. Но, что делать, если вы не пишете код самостоятельно, а используете в своей работе чужой исходный код, например, если вам необходимо контролировать качество кода, предоставляемого подрядчиком или собственным подразделением разработчиков. В случае с собственными разработчиками вы можете, конечно, порекомендовать им использовать те методологии, о которых мы говорили в первой статье и, возможно, качество кода действительно улучшится. Но как можно проверить код, поставляемый сторонним подрядчиком?
А что, собственно, ищем?
Еще недавно, когда разговор заходил о доверенной разработке программного обеспечения, все обычно вспоминали о требованиях нормативных актов, прежде всего о приказах ФСТЭК и требованиях об отсутствии недекларированных возможностей в используемых операционных системах и прикладном программном обеспечении. Считалось, что доверять можно программному обеспечению, которое разработано крупными компаниями, имеющими постоянный штат программистов, много лет работающим на рынке и имеющим большой опыт в разработке ПО. Для таких разработчиков дорога репутация и они сами следят за качеством выпускаемого кода и не устанавливают какие-либо закладки в свое ПО.
Однако последние геополитические события наглядно показывают опасность такого подхода. Программные закладки стали появляться не только в ПО с закрытым исходным кодом, но даже в репозиториях программного обеспечения с открытым исходным кодом. И если для защиты от закладок в приложениях с закрытым кодом помогут только наложенные средства защиты (например, межсетевые экраны и сканеры уязвимостей), то в открытом коде мы можем попробовать самостоятельно найти подозрительный код и удалить его.
Ну и не стоит забывать о “классических” уязвимостях и ошибках в коде, которые также создают немалую угрозу.
Переполнение буфера
Одной из наиболее распространенных уязвимостей является переполнение буфера, когда программа записывает данные за пределами выделенного в памяти буфера. Переполнение буфера обычно возникает из-за неправильной работы с данными, полученными извне, и памятью, при отсутствии жесткой защиты со стороны подсистемы программирования (компилятора или интерпретатора) и операционной системы. В результате переполнения могут быть испорчены данные, расположенные следом за буфером (или перед ним).
Вот простейший пример:
#include <string.h>
int main(int argc, char *argv[])
{
char buf[12];
strcpy(buf, argv[1]);
return 0;
}
Программа получает на вход какие-то данные и помещает их в массив buf длиной 12 байт. Все бы ничего, на вход может прийти больше 12 байт и тогда избыточные данные повредят содержимое памяти.
Уязвимости форматной строки
Еще одна распространенная ошибка - это уязвимость форматной строки. Если программа копирует в буфер в качестве аргумента для printf
(или любой из связанных функций, включая sprintf
, fprintf
и т. д.), полученные на вход данные, может произойти запись в произвольные адреса памяти.
#include<stdio.h>
int main(int argc, char** argv)
{
char buffer[100];
strncpy(buffer, argv[1], 100);
// Мы передаем командную строку
// аргумент для printf
printf(buffer);
return 0;
}
Поскольку printf
имеет переменное количество аргументов, он должен использовать строку формата для определения количества аргументов. В приведенном выше случае злоумышленник может передать строку «% p% p% p% p% p% p% p% p% p% p% p% p% p% p% p» и обмануть printf, заставляя ее думать имеет 15 аргументов.
$ ./a.out 'AAAA%10$p’
AAAA0x41414141
И другие ошибки
Многие программисты неаккуратно работают с библиотеками. Например, при работе с файлами забывают, что для каждого fopen
нужен fclose
. И в результате файловая переменная теряется раньше, чем файл закрывается.
Также к числу наиболее распространенных ошибок можно отнести неопределённое поведение программы — когда неинициализированные переменные, обращаются к NULL-указателям. Но о таких простейших случаях сигнализируют и компиляторы.
И в завершении перечисления основных ошибок программистов стоит упомянуть утечки памяти и других ресурсов. Конечно, выявить утечку памяти с помощью анализа исходного кода не всегда бывает возможно и зачастую с этим лучше справится динамический анализатор поведения программы, но во многих случаях данную ошибку можно выявить на этапе анализа исходного кода.
Например:
Traverser *t = new Traverser(Name);
if (!t->Valid())
{
return FALSE; // Получается, что t не будет удалена и так и останется в памяти
delete t;
}
В приведенном примере при каждом возвращении FALSE
у нас в памяти будет оставаться экземпляр данных на которые указывала t
, что в конечном счете может привести к утечке памяти.
А еще, возвращаясь к теме закладок, хотелось бы отметить, что многие программисты сами оставляют в рабочих версиях приложений закладки для удобства собственной работы. Например, программисту лень каждый раз при тестировании приложения вводить учетные данные, и он просто делает в программе проверку: если в поле ввода указан определенный логин, то пускать сразу без пароля. В результате, для входа программист каждый раз указывает только короткий логин и входит в систему. Все бы ничего, но часто такие лазейки остаются и в промышленных версиях приложений, а это уже недопустимо.
Чуть более “защищенным” вариантом являются “захардкоженные” учетные данные. Программист может жестко указать в коде программы логин и пароль также для удобства своей работы. Многие при передаче в промышленную эксплуатацию просто комментируют соответствующие строки, не удаляя их. И в том, и в другом случае, если злоумышленник получит доступ к исходному коду хотя бы на чтение, он сможет получить доступ в целевую систему.
Анализаторы кода
Итак, мы определились с основными типами ошибок, с которыми нам необходимо бороться. Теперь посмотрим какие решения для анализа исходного кода есть на сегодняшний день на рынке. По понятным причинам в список были включены только российские решения.
Пожалуй, основным критерием для систем анализа исходного кода является количество поддерживаемых языков программирования, поэтому для каждой из систем будет указана прежде всего эта характеристика.
Эшелон AppChecker
Статический анализатор кода, предназначенный для автоматизированного поиска дефектов в исходном коде приложений, разработанных на С#, C/C++, Java, PHP. Осуществляет поиск дефектов кода с помощью постоянно обновляемой базы сигнатур, имеется интеграция с системами управления версиями git, Subversion.
Infowatch Appercut
Система автоматизированного контроля исходного кода бизнес-приложений на соответствие требованиям, предъявляемым к заказной разработке. Система поддерживает следуюшие языки программирования: 1C, C#, Java, JavaScript, SQL, PHP, Python, SAP Abap4 и LotusScript.
Positive Technologies Application Inspector
Система автоматического анализа исходного кода Application Inspector позволяет не только выявлять ошибки в исходном коде, но и интегрироваться в действующие процессы разработки, а также гибко встраиваться в существующие процессы разработки. Решение поддерживает следующие языки программирования: PHP, Java, C#, JavaScript, Kotlin, VB.NET, Python, Objective-C, Swift, C/C++, Go, SQL (PL/SQL, T-SQL, MySQL).
Solar inCode
Solar inCode — инструмент статического анализа кода, предназначенный для выявления уязвимостей информационной безопасности и недекларированных возможностей в исходных текстах программного обеспечения. Поддерживаются следующие языки: Java, JavaScript, Scala, PHP, Python, Ruby, HTML5, PL/SQL, T/SQL, Java for Android, Swift, Objective C, С#, C/C++, VB 6.0, Delphi, ABAP, Solidity, Groovy, Kotlin.
Заключение
Ошибки программистов и преднамеренные закладки являются серьезной проблемой при разработке приложений. При наличии исходного кода подобные уязвимости можно выявлять с помощью автоматизированных средств анализа, что позволяет сделать разрабатываемое приложение безопаснее.
И напоследок хочу напомнить о том, что 13 апреля мои коллеги из Otus проведут бесплатный вебинар, на котором расскажут о том, что такое ИБ (от уровня «не открывайте письма от неизвестных отправителей» до уровня «товарищ майор, мы не специально») и на что опираться (стандарты, законы), как COVID-19 пришёл и почему вечер у специалистов по ИБ перестал быть томным, коротко расскажут почему в технологии DevOps нужен Sec. Регистрация на вебинар доступна по ссылке.