Правила код-ревью по безопасности для команд разработки: чек-листы по языкам и типовым ошибкам

Правила код-ревью по безопасности для команд разработки: чек-листы по языкам и типовым ошибкам

Изображение: recraft

Код-ревью (от англ. code review – обзор кода) – это процесс проверки исходного кода программного обеспечения другими разработчиками (не автором) с целью поиска ошибок, приведения кода к единым стандартам и обмена опытом. Хотя код-ревью обычно считают средством улучшения стиля и качества кода, сегодня это один из мощнейших способов предотвращения угроз. Системный анализ безопасности на этапе проверки позволяет устранить большинство критических уязвимостей еще до того, как код попадет в рабочую среду.

Почему обычного код-ревью недостаточно?

Во многих командах ревью ограничивается проверкой логики, читаемости и производительности. Безопасность при этом рассматривается либо фрагментарно, либо полностью перекладывается на автоматические сканеры. Такой подход приводит к типовым проблемам:

  • уязвимости бизнес-логики не обнаруживаются SAST-инструментами;
  • небезопасные паттерны использования сторонних библиотек проходят ревью как «рабочие»;
  • ошибки конфигурации и обработки данных остаются незамеченными.

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

Универсальный чек-лист безопасности

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

Работу с входными/выходными данными:

  1. происходит ли приведение данных к безопасному, единому формату (нормализация) с последующей валидацией;
  2. используются ли «белые списки» (allow-list) вместо «черных списков» (black-list);
  3. отсутствует ли слепое доверие данным клиента;
  4. безопасна ли десериализация структурированных данных (JSON/XML/YAML) на предмет выполнения кода на сервере;
  5. применяются ли prepared statements и плейсхолдеры для предотвращения SQL-инъекций при работе с БД;
  6. происходит ли экранирование данных перед их выводом в интерфейс пользователя с целью предотвращения XSS.

>> Любые данные, приходящие извне, по умолчанию считаются опасными и ненадежными.

Аутентификацию и авторизацию:

  1. проверяются ли права доступа на стороне сервера при каждом запросе, независимо от логики интерфейса;
  2. исключается ли доверие к ролям и правам, передаваемым в параметрах или флагах со стороны клиента;
  3. четко ли разграничены процессы идентификации, аутентификации и авторизации;
  4. ограничивается ли срок жизни сессий и токенов и защищены ли они от подделки и кражи из браузера;
  5. блокируются ли попытки массового перебора паролей и выдает ли система единую ошибку при неудачном входе;
  6. происходит ли полная и немедленная инвалидация серверной сессии при выходе пользователя.

>> Никогда не верь клиенту в вопросах доступа: личность должна быть подтверждена, а каждое действие – санкционировано сервером.

Обработку ошибок и логирование:

  1. не раскрываются ли детали реализации (стек-трейсы, версии библиотек, структура БД) в сообщениях об ошибках;
  2. исключается ли попадание в логи секретов и конфиденциальных данных (паролей, токенов, ключей и ПДн);
  3. используется ли централизованная обработка исключений для обеспечения единообразия ответов и предотвращения необработанных сбоев;
  4. соблюдается ли достаточный уровень логирования событий безопасности (входы, отказы в доступе, изменение прав);
  5. проводится ли очистка (маскирование) данных в логах при необходимости зафиксировать сам факт передачи объекта, содержащего чувствительную информацию.

>> Пиши в логи только то, что поможет восстановить ход событий, не раскрывая секретов и деталей защиты.

Работу с секретами:

  1. отсутствуют ли в исходном коде любые жестко заданные (hardcoded) пароли, API-ключи, токены или сертификаты;
  2. загружаются ли секреты из специализированных хранилищ (vault) или защищенных переменных окружения;
  3. удалены ли из истории репозитория любые «временные» секреты, тестовые учетные данные и отладочные ключи доступа;
  4. используются ли файлы-игнорирования (например, .gitignore), чтобы исключить случайное попадание локальных файлов с секретами в общую ветку;
  5. предусмотрена ли возможность оперативной замены секретов без необходимости внесения изменений в сам программный код.

>> Пиши код так, будто завтра он окажется в открытом доступе, и в нем не останется ни одного ключа от целевой системы.

Типовые ошибки по языкам и платформам

Java / Kotlin

  • использование небезопасных десериализаторов (ObjectInputStream, старые версии Jackson/Fastjson);
  • игнорирование проверки цепочки сертификатов и использование самоподписанных TLS-сертификатов;
  • сборка запросов через конкатенацию строк;
  • использование предсказуемых генераторов случайных чисел (java.util.Random) в криптографии.

C / C++

  • выход за границы буфера (buffer overflow) при работе с массивами;
  • использование небезопасных функций без контроля длины: strcpy(), sprintf(), gets();
  • отсутствие проверок возвращаемых значений системных вызовов и функций выделения памяти;
  • использование после освобождения (use-after-free) и двойное освобождение памяти;
  • целочисленное переполнение (integer overflow), приводящее к некорректному выделению буфера.

Python

  • использование eval(), exec() и ast.literal_eval() для обработки данных пользователя;
  • небезопасная загрузка сериализованных объектов через pickle, marshal или shelve;
  • формирование команд shell через f-строки или конкатенацию (с использованием os.system, subprocess);
  • отключение проверки TLS-сертификатов «для удобства» в библиотеках типа requests;
  • выход за пределы директории (path traversal) при открытии файлов через open() без валидации пути.

JavaScript / TypeScript

  • прямая вставка данных в DOM через innerHTML или dangerouslySetInnerHTML;
  • отсутствие проверки схем JSON-ответов и входных данных в runtime;
  • хранение секретов и JWT-токенов в localStorage;
  • неправильная настройка CORS (Access-Control-Allow-Origin: * для приватных API).

Go

  • игнорирование возвращаемых ошибок (if err != nil);
  • утечки горутин (goroutine) из-за отсутствия контекста (context.Context) или таймаутов;
  • доступ к общим ресурсам без использования мьютексов;
  • чрезмерное использование пакета unsafe;
  • использование io.ReadAll() на внешних потоках данных без ограничения размера.

Что сделать для внедрения код-ревью безопасности?

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

Назначьте Security Champion’а в команде, отвечающего за качество ревью в разрезе безопасности.

Интегрируйте SAST и SCA для автоматизации и возможности сконцентрироваться на логике и угрозах.

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

Чисто там, где не сорят

Безопасность кода – не скучный надзор «безопасников», а общее дело всей команды. Когда правила проверки понятны, а под рукой есть удобный чек-лист, поиск уязвимостей превращается из рутины в полезную привычку. Чем раньше мы заметим «слабое звено», тем меньше правок придется вносить в последний момент. В конечном итоге, качественный и защищенный код – это прежде всего наш спокойный сон и доверие пользователей.

Развивайтесь, и пусть ваши билды всегда выдают exit code 0!

Автор: Лариса Карпан, старший специалист по безопасной разработке Angara Security.

Angara Security
Автор: Angara Security
Группа компаний Angara, представленная системным интегратором Angara Technologies Group и сервис-провайдером Angara Professional Assistance, оказывает полный спектр услуг по информационной безопасности: поставку оборудования и ПО, проектирование, внедрение, сопровождение ИТ- и ИБ-систем клиентов, а также предлагает востребованные сервисы по обеспечению информационной безопасности по модели подписки.
Комментарии: