Ручной анализ исходного кода – это проверка кода на наличие уязвимостей и проблем, связанных с бизнес-логикой, которую выполняет квалифицированный специалист. Выполнять анализа кода вручную можно «в лоб», просматривая весь код от начала и до конца, или придерживаться определенного подхода — например, определив сначала поверхность атаки.

Поверхность атаки программного обеспечения (ПО) — совокупность интерфейсов и реализующих их модулей ПО, посредством прямого или косвенного использования которых могут реализовываться угрозы безопасному функционированию ПО. Для определения поверхности атаки обычно используются статические анализаторы исходного кода.

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

Цель анализа исходного кода

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

  1. Проверка наличия уязвимостей в ПО. При разработке исходного кода разработчики могут допускать различные ошибки, которые впоследствии способны привести к таким уязвимостям, как SQL инъекции, межсайтовый скриптинг (XXS), инъекции командной строки и многие другие.  При проверке исходного кода можно обнаружить эти недостатки и вовремя их исправить;

  2. Защита информации и активов компании. При эксплуатации уязвимости злоумышленник влияет на инфраструктуру компании и может нанести ей вред, например, украсть какие-то данные, привести сервис в неработоспособное состояние и т.д.;

  3. Уменьшение расходов на исправление ошибок. Статический анализ кода проводится на ранних этапах разработки ПО, поэтому исправление найденных уязвимостей может стоить гораздо дешевле, чем, например, доработки на этапе поставки ПО;

  4. Соответствие требованиям регуляторов. В соответствии с различными требованиями, например, ГОСТ Р ИСО/МЭК 15408 «Информационная технология. Методы и средства обеспечения безопасности. Критерии оценки безопасности информационных технологий» или ГОСТ Р 56939-2016 «Защита информации. Разработка безопасного программного обеспечения. Общие требования», ПО не должно содержать уязвимостей — чтобы это доказать, необходимо провести анализ;

  5. Снижение репутационных рисков. Различные атаки и утечки данных могут негативно сказаться на репутации компании.

Этапы обнаружения уязвимостей при ручном статическом анализе

Для обнаружения уязвимостей вручную необходимо:

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

    • вызов функций,

    • параметры и возвращаемые значения,

    • глобальные переменные,

    • сетевое взаимодействие.

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

    ТАБЛИЦА 1. Перечень тем для рассмотрения при проведении аудита

    Архитектура, проектирование и моделирование угроз

    Жизненный цикл разработки безопасного ПО

    Аутентификация

    Управление сессиями

    Контроль доступа

    Входные и выходные данные

    Криптографическая защита

    Обработка ошибок, ведение журнала и аудит

    Защита информации и конфиденциальность

    Передача данных

    Бизнес-логика

    Безопасная загрузка файлов

    Архитектура API

    Аутентификация

    Парольная защита

    Жизненный цикл аутентификатора

    Хранение учетных данных

    Восстановление учетных данных

    Проверочные коды

    Второй фактор аутентификации

    OTP

    Криптографический верификатор

    Технические УЗ

    Управление сессиями

    Создание сессии

    Завершение сессии

    Cookie

    Токены

    Контроль доступа

    Ролевая политика

    Форматно-логический контроль, нейтрализация и кодировка

    Контроль входных данных

    Нейтрализация и изоляция

    Кодирование выходных данных

    Хранимая криптография

    Категории информации

    Алгоритмы

    Случайные значения

    Управление секретами

    Обработка ошибок и ведение журнала

    Содержимое журнала

    Обработка журнала

    Защита журнала

    Обработка ошибок

    Защита информации

    Базовые меры

    Защита данных на стороне клиента

    Персональные данные

    Передача данных

    Безопасность подключений со стороны клиента

    Безопасность подключений со стороны сервера

    Файлы и ресурсы

    Загрузка файлов на сервер

    Целостность файлов

    Исполнение файла

    Хранение файлов

  2. Составить модель угроз (далее – МУ) приложения на основе собранной информации.

    При составлении МУ следует учитывать:

    • библиотеки и другие зависимости, которые используются при разработке ПО,

    • точки входа,

    • области интереса злоумышленника (т.е. информация или активы),

    • возможные поверхности атаки,

    • уровни доступа,

    • потоки данных,

    • транзакции (подключения).

    Один из популярных подходов построения МУ – это STRIDE (Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of Privilege). Эта модель классифицирует разные типы угроз, такие как спуфинг, модификация, отказ от авторства, раскрытие информации, отказ в обслуживании, повышение привилегий, и позволяет анализировать недостатки и возможные уязвимости ПО.

  3. Определить, какие есть точки входа в программу и найти эти точки в исходном коде. Точками входа могут быть различные страницы, на которых пользователь вводит свои данные, API и пр. Именно с помощью пользовательского ввода злоумышленник может эксплуатировать различные уязвимости (SQL инъекции, командные инъекции, проводить XSS и XML атаки и т.д.).

    В качестве примера рассмотрим простую SQL инъекцию: из окна авторизации считываются имя пользователя (username) и пароль (password) и выполняется запрос к базе данных:

    username = getRequestString("username");

    password = getRequestString("password");

    request = 'SELECT * FROM Users WHERE username = " ' + username + ' " AND password= " ' + password ' " '

  4. Составить и проанализировать граф потока управления (control flow graph, CFG) программы, а также выполнить трассировку вызовов функций.

    Основная задача анализа потока данных — определить в каждой точке программы некоторую информацию о данных, которыми оперирует код. Информация может быть разная: тип данных, значение и т.д. Далее следует составить CFG и проследить, как распространяется информация по программе во время ее работы, особенно данные, полученные извне.

    Рассмотрим пример построения графа для простой функции на языке программирования Python:

    (1)          def check_numbers(arr):

    (2)              even_numbers = []

    (3)              odd_numbers = []

    (4)              for num in arr:

    (5)                  if num % 2 == 0:

    (6)                      even_numbers.append(num)

    (7)                  else:

    (8)                      odd_numbers.append(num)

    (9)              return even_numbers, odd_numbers

    В данном коде можно выделить несколько основных блоков:

    • Блок А – входной блок: начало функции;

    • Блок B – строка 1: функция принимает на вход массив arr;

    • Блок C – строка 2: инициализация переменной even_numbers;

    • Блок D – строка 3: инициализация переменной odd_numbers;

    • Блок E – строка 4: начало цикла и инициализация счетчика num;

    • Блок F – строка 5: проверка выполнения условия;

    • Блок G – строка 6: выполнение операции при выполнении условия;

    • Блок H – строка 7-8: выполнение операции при невыполнении условия;

    • Блок I – строка 9, выходной блок: возвращение параметров.

    Граф потока управления представлен на рисунке 1.

    Рисунок 1. Граф потока управления
    Рисунок 1. Граф потока управления

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

  5. Выполнить ручную проверку исходного кода на основе собранной информации. При анализе следует обратить внимание на следующие области:

    • проверка входных данных и кодирование выходных данных,

    • аутентификация и авторизация,

    • криптография,

    • управление сеансами,

    • обработка исключений и ошибок.

Базовый и минимальный список проверок, которые обязательно следует выполнить:

  1. Проверка входных и выходных данных:

    • все данные, введенные пользователем, должны быть проверены,

    • при составлении запросов к базам данных использовать параметризованные запросы,

    • использование хранимых процедур для минимизации риска SQL-инъекции,

    • при выводе данных использовать контекстно-зависимое кодирование, например, экранирование SQL.

  2. Аутентификация и управление сеансами:

    • длина пароля должна быть не менее 12 символов,

    • пароли должны быть скрыты специальными символами, например, «*»,

    • пароли должны хешироваться, а также «подсаливаться»,

    • сеансовые токены должны иметь ограниченный срок действия и надежно храниться,

    • если используются аутентификаторы, то создаваемые системой начальные пароли должны иметь длину не менее 6 символов и формироваться криптографически случайным образом.

  3. Защита данных. К конфиденциальным данным должно применяться шифрование при передаче и хранении;

  4. Журналирование. Конфиденциальная информация не должна журналироваться;

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

  6. Поиск небезопасных алгоритмов, которые используются в исходном коде. К таким алгоритмам относятся MD5, SHA-1. DES и пр. и для их обнаружения можно воспользоваться поиском по ключевым словам;

  7. Некорректная обработка исключений. При обработке исключений и выводе пользователю информации об ошибке необходимо фильтровать вывод и не выводить конфиденциальную информацию;

  8. Приоритизация уязвимостей. Прежде чем исправлять найденные уязвимости, следует определить, какие уязвимости исправлять в первую очередь, а какие можно исправить немного позже. Уязвимости можно сгруппировать разными способами, но чаще всего их группируют по общей системе оценки уязвимостей (Common Vulnerability Scoring System, CVSS). У каждой уязвимости определяется степень критичности и в зависимости от нее период для исправления: критический (до 7 дней), высокий (до 30 дней), средний (до 90 дней), низкий (до 120 дней).

  9. Устранение найденных уязвимостей. На этом этапе принимаются меры для нейтрализации выявленных уязвимостей (установка патчей, обновлений, выполнение компенсирующих мер, исправление исходного кода).

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

Автор: Анастасия Камалова, инженер по безопасности приложений УЦСБ

Комментарии (1)


  1. id117
    04.04.2024 05:22

    Кому не сложно, посоветуйте, пожалуйста, материалы по теме.
    Из того, с яем уже ознакомился, могу посоветовать материалы pvs studio и ростелеком-солар.