Запуск CodeQL в базе данных

Завершено

С помощью кода, извлеченного в базу данных, теперь его можно проанализировать с помощью запросов CodeQL. Эксперты GitHub, исследователи безопасности и участники сообщества записывают и поддерживают запросы CodeQL по умолчанию. Вы также можете написать собственные запросы.

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

Существует два важных типа запросов:

  • Запросы оповещений выделяют проблемы в определенных расположениях кода.
  • запросы пути описывают поток информации между источником и приемником в коде.

Простой запрос CodeQL

Базовая структура запроса CodeQL содержит расширение файла .ql и содержит предложение select. Ниже приведен пример структуры запросов:

/**
 *
 * Query metadata
 *
 */
import /* ... CodeQL libraries or modules ... */

/* ... Optional, define CodeQL classes and predicates ... */

from /* ... variable declarations ... */
where /* ... logical formula ... */
select /* ... expressions ... */

Настройка запроса

Анализ CodeQL определяется запросами. Хотя вы можете использовать стандартные запросы, предоставляемые GitHub, вы также можете настроить анализ, написав собственные запросы и упорядочив их в пакеты запросов.

Запросы обычно группируются в пакеты запросов, которые содержат запросы, общие библиотеки и файлы конфигурации. Пакет запросов позволяет определить многократно используемый набор правил анализа для проектов. В пакет можно включить отдельные файлы .ql, вспомогательные библиотеки, определяющие повторно используемую логику, и наборы запросов, которые объединяют несколько запросов.

Набор запросов (.qlsфайл) используется для управления выполнением запросов во время анализа. Вместо выполнения запросов по одному вы определяете набор, который перечисляет все запросы, которые требуется выполнить. Рассмотрим пример.

- description: Custom security queries
- queries:
  - ./queries/hardcoded-credentials.ql
  - ./queries/insecure-config.ql

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

Вы можете создавать собственные запросы, записывая .ql файлы. Запрос описывает шаблон в коде, который требуется обнаружить. Обычно он импортирует языковую библиотеку, определяет условия и возвращает результаты с помощью инструкции select .

Например, следующий запрос ищет строковые литералы, которые могут содержать жесткие учетные данные:

/**
 * @name Hardcoded credential detection
 * @description Finds string literals that may contain passwords
 * @kind problem
 * @id example/hardcoded-credentials
 * @severity warning
 */

import javascript

from Literal l
where l.getValue().toString().matches("%password%")
select l, "Possible hardcoded credential"

В этом запросе:

  • Инструкция import загружает языковую модель для JavaScript.
  • Предложение from определяет данные, которые анализируются.
  • Условие where выполняет фильтрацию по совпадающим шаблонам.
  • Инструкция select определяет, какие результаты возвращаются.

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

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

Как минимум, метаданные должны включать:

  • Уникальный идентификатор (@id)
  • Имя (@name)
  • Описание (@description)
  • Тип результата (@kindнапример problem , или path-problem)

Дополнительные свойства, такие как @severity и @precision, помогают определить, как оповещения отображаются в GitHub.

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

После определения запросов или набора запросов их можно включить в конфигурацию анализа. В GitHub Actions вы указываете запросы на шаге инициализации:

- name: Initialize CodeQL
  uses: github/codeql-action/init@v3
  with:
    queries: ./path/to/query-suite.qls

Во время рабочего процесса:

  1. CodeQL создает базу данных.
  2. Выполняет выбранные запросы.
  3. Создает результаты в формате SARIF.
  4. Отправляет результаты в GitHub.

Результаты пользовательского запроса отображаются вместе со стандартными результатами CodeQL на вкладке "Безопасность". Это позволяет расширить анализ по умолчанию с помощью проверок, относящихся к базе кода, и по-прежнему воспользоваться поддерживаемыми наборами запросов GitHub.

Метаданные запроса

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

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

Метаданные используются CodeQL и средством сканирования кода GitHub для:

  • Определите запрос и его назначение.
  • Определите, как классифицируются результаты (например, problem или path).
  • Назначьте уровни серьезности и точности.
  • Форматирование результатов для отображения в репозитории.

Например, запрос может включать такие метаданные:

/**
 * @name Hardcoded credential detection
 * @description Finds string literals that may contain passwords
 * @kind problem
 * @id example/hardcoded-credentials
 * @severity warning
 */

Если эти метаданные присутствуют:

  • Результаты преобразуются в формат SARIF.
  • Оповещения отображаются в сканировании кода GitHub.
  • Результаты включают контекстную информацию, такую как степень серьёзности и описание.

Если метаданные отсутствуют:

  • Запрос по-прежнему выполняется.
  • Результаты не отображаются в виде оповещений.
  • Выходные данные отображаются только как необработанные таблицы.

Метаданные также определяют, как результаты группируются и отслеживаются по сканированию. Например, запрос @id используется для сопоставления оповещений между запусками.

GitHub содержит рекомендуемое руководство по стилю для метаданных запроса. Его можно найти в документации CodeQL.

В этом примере показаны метаданные для одного из стандартных запросов Java:

Снимок экрана: метаданные запроса для стандартного запроса Java CodeQL.

CodeQL не интерпретирует запросы, у которых нет метаданных. Он отображает эти результаты в виде таблицы и не отображает их в исходном коде.

Написание, тестирование и выполнение запросов

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

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

Локальное тестирование запросов

Вы можете протестировать запросы с помощью интерфейса командной строки CodeQL или расширения Visual Studio Code.

С помощью интерфейса командной строки CodeQL выполняется запросы к уже созданной базе данных:

codeql database analyze <database> <query.ql>

Эта команда выполняет запрос и создает результаты, которые можно просмотреть в SARIF или другом формате вывода.

Можно также запустить:

codeql query run <query.ql> --database=<database>

Локальное тестирование позволяет выполнять следующие действия:

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

Расширение Visual Studio Code обеспечивает более интерактивный интерфейс. Можно сделать следующее:

  • Откройте базу данных.
  • Выполнение запросов непосредственно из редактора.
  • Просмотрите результаты вместе с исходным кодом.

Это упрощает понимание поведения запроса и его быстрой корректировки.

Запуск запросов при сканировании кода в GitHub

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

В GitHub Actions запросы настраиваются на шаге инициализации:

- name: Initialize CodeQL
  uses: github/codeql-action/init@v3
  with:
    queries: ./path/to/query-suite.qls

При запуске рабочего процесса:

  1. CodeQL создает базу данных для репозитория.
  2. Выполняет выбранные запросы.
  3. Преобразует результаты в SARIF.
  4. Отправляет результаты в GitHub.

Результаты отображаются в виде оповещений на вкладке "Безопасность " наряду со стандартными результатами CodeQL.

Выполнение запросов в рабочих процессах гарантирует следующее:

  • Анализ выполняется автоматически для pull request и веток.
  • Новые проблемы обнаруживаются в виде изменений кода.
  • Результаты видны команде.

Обслуживание и обновление запросов

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

Рассмотрим пример.

  • Запрос может возвращать слишком много результатов (ложные срабатывания).
  • Это может пропустить случаи, которые вы ожидали обнаружить.
  • Новые шаблоны кода в репозитории могут не охватываться.

В таких случаях вы обновляете запрос, чтобы повысить его точность.

Начните с локального выполнения запроса и просмотра результатов. Просмотрите расположения кода, помеченные и решите, представляют ли они реальные проблемы. Если нет, укажите условия в where предложении, чтобы сузить результаты.

Например, можно:

  • Добавьте дополнительные условия для исключения безопасных шаблонов.
  • Настройте сопоставление строк или логику потока данных.
  • Повторно используйте предикаты из существующих библиотек для повышения точности.

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

Когда вы фиксируете обновлённый запрос, этот запрос автоматически выполняется в процессе сканирования кода. Это означает следующее:

  • Существующие оповещения могут быть обновлены или удалены.
  • Новые оповещения могут отображаться на основе обновленной логики.

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

Синтаксис QL

QL — это декларативный объектно-ориентированный язык запросов. Она оптимизирована для эффективного анализа иерархических структур данных и, в частности, баз данных, представляющих артефакты программного обеспечения.

Синтаксис QL аналогичен SQL, но семантика QL основана на журнале данных. Журнал данных — это декларативный язык программирования логики, который часто используется в качестве языка запросов. Поскольку QL является главным образом языком логики, все операции в QL являются логическими операциями. QL также наследует рекурсивные предикаты из журнала данных. QL добавляет поддержку агрегатов, чтобы сделать даже сложные запросы краткими и простыми.

Язык QL состоит из логических формул. В нем используются распространенные логические соединители, такие как and, orи not, а также квантификаторы, такие как forall и exists. Так как QL наследует рекурсивные предикаты, можно также создавать сложные рекурсивные запросы с помощью базового синтаксиса QL и агрегатов, таких как count, sumи average.

Дополнительные сведения о языке QL см. в документации CodeQL.

Запросы пути

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

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

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

Запрос пути требует определенных метаданных, предикатов запросов и структур инструкций select. Многие встроенные запросы пути в CodeQL соответствуют базовой структуре. Структура зависит от того, как CodeQL моделирует язык, который вы анализируете.

Ниже приведен пример шаблона для запроса пути:

/**
 * ...
 * @kind path-problem
 * ...
 */

import <language>

// For some languages (Java/C++/Python/Swift), you need to explicitly
// import the data-flow library, such as:
// import semmle.code.java.dataflow.DataFlow
// import codeql.swift.dataflow.DataFlow

...

module Flow = DataFlow::Global<MyConfiguration>;
import Flow::PathGraph

from Flow::PathNode source, Flow::PathNode sink
where Flow::flowPath(source, sink)
select sink.getNode(), source, sink, "<message>"

В этом шаблоне:

  • MyConfiguration — это модуль, содержащий предикаты, определяющие, как данные передаются от источника к получателю.
  • Flow результат вычисления потока данных на основе MyConfiguration.
  • Flow::PathGraph — это результирующий модуль графа потока данных, который необходимо импортировать для включения объяснений пути в запрос.
  • source и sink являются узлами в графе, как определено в конфигурации, и Flow::PathNode их тип.
  • DataFlow::Global<..> — это вызов потока данных. Вместо этого вы можете включить набор шагов по умолчанию с помощью TaintTracking::Global<..>.

Как написать запрос на поиск пути

Запрос должен вычислить граф пути для создания объяснений пути. Для этого определите предикат запроса с именем edges. Предикат запроса — это нечленский предикат с аннотацией запроса. Аннотация запроса возвращает все кортежи, которые вычисляет предикат.

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

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

Ниже приведен пример инструкции, которая импортирует модуль PathGraph из библиотеки потока данных (DataFlow.qll), в которой определен edges:

import DataFlow::PathGraph

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

Класс PathNode предназначен для реализации анализа потока данных. Node Он дополнен контекстом вызова (за исключением приемников), пути доступа и конфигурации. Создаются только значения PathNode, которые доступны из источника.

Ниже приведен пример пути импорта:

import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl

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

Анализ базы данных

При использовании запросов для анализа базы данных CodeQL вы получаете значимые результаты в контексте исходного кода. Результаты оформляются как оповещения или пути в SARIF или другом интерпретированном формате.

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

codeql database analyze \
  --format=<format> \
  --output=<output> \
  [--threads=<num>] \
  [--ram=<MB>] \
  <options>... \
  -- <database> <query|dir|suite>...

Эта команда объединяет эффекты codeql database run-queries и codeql database interpret-results команд сантехники.

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

  • codeql database run-queries
  • codeql query run

Затем используйте:

codeql bqrs decode

для преобразования необработанных результатов в читаемую нотацию.

Полный список доступных команд CLI CodeQL можно получить в руководстве по cli CodeQL.

Использование ФАЙЛА SARIF с категориями

CodeQL поддерживает SARIF для совместного использования результатов статического анализа. SARIF предназначен для представления выходных данных широкого спектра статических средств анализа.

Необходимо указать категорию при использовании выходных данных SARIF для анализа CodeQL. Категории могут различать несколько анализов, проводимых в одном репозитории, и на разных языках или в различных частях кода. Однако ФАЙЛЫ SARIF с той же категорией перезаписывают друг друга.

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

Например, отображается значение категории (с косой чертой, добавленной, если она еще не присутствует) как:

  • <run>.automationId в SARIF версии 1
  • <run>.automationLogicalId в SARIF версии 2
  • <run>.automationDetails.id в SARIF версии 2.1.0

Публикация результатов SARIF в GitHub

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

codeql github upload-results \
  --sarif=<file> \
  [--github-auth-stdin] \
  [--github-url=<url>] \
  [--repository=<repository-name>] \
  [--ref=<ref>] \
  [--commit=<commit>] \
  [--checkout-path=<path>] \
  <options>...

Чтобы передать результаты на GitHub, убедитесь, что на каждом сервере непрерывной интеграции (CI) есть приложение GitHub или личный маркер доступа для используемого интерфейса командной строки CodeQL. Необходимо использовать маркер доступа или приложение GitHub с разрешением на запись security_events.

Вы можете разрешить CLI CodeQL использовать тот же токен, если серверы CI уже используют токен с той же областью для извлечения репозиториев из GitHub. В противном случае создайте новый маркер с разрешением на запись security_events и добавьте этот маркер в хранилище секретов системы CI.

В качестве рекомендации по обеспечению безопасности используйте --github-auth-stdin флаг и передайте маркер команде через стандартные входные данные.

Отправка результатов SARIF

Чтобы результаты сканирования кода из средства статического анализа, не от компании Microsoft, отображались в вашем репозитории GitHub, ваши результаты должны храниться в файле SARIF, поддерживающем определенное подмножество схемы JSON SARIF 2.1.0. Результаты можно отправить с помощью API сканирования кода или интерфейса командной строки CodeQL.

Каждый раз, когда вы отправляете результаты проверки нового кода, CodeQL обрабатывает результаты и добавляет оповещения в репозиторий. Чтобы предотвратить дублирование оповещений для одной и той же проблемы, сканирование кода использует свойство SARIF partialFingerprints для сопоставления результатов между запусками, чтобы они отображались только один раз в последнем запуске выбранной ветви.

Устранение дубликатов позволяет сопоставить оповещения с правильной строкой кода при изменении файлов.

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

Спецификации SARIF используют имя свойства JSON partialFingerprints, в котором словарь сопоставляет именованные типы отпечатков с самими отпечатками. Это свойство содержит, как минимум, значение для primaryLocationLineHash, которое предоставляет отпечаток пальца на основе контекста основного местоположения.

GitHub пытается заполнить поле partialFingerprints из исходных файлов, если вы загружаете файл SARIF с помощью действия upload-sarif и эти данные отсутствуют.

Кроме того, если загрузить файл SARIF без данных fingerprint через конечную точку API /code-scanning/sarifs, пользователям могут отображаться повторяющиеся оповещения при обработке и отображении оповещений сканирования кода.

Чтобы избежать повторяющихся оповещений при работе со статическими средствами анализа, вычислите данные отпечатков пальцев и заполните partialFingerprints свойство перед отправкой ФАЙЛА SARIF. Полезной отправной точкой является использование того же скрипта, что и действие upload-sarif.