Привет, Хабр!

Как вы все уже знаете, в области безопасности приложений без статических анализаторов исходного кода (SAST) совсем никуда. SAST-сканеры занимаются тем, что проверяют код приложения на различные типы программных ошибок, которые могут скомпрометировать систему, предоставить злоумышленнику непредвиденные возможности для доступа к данным, либо для нарушения работы приложения. В основном анализ безопасности кода строится на изучении его семантической структуры, путей прохождения данных от момента пользовательского ввода до обработки. Однако есть и обычная для таких инструментов возможность поиска наиболее часто встречающихся небезопасных паттернов.

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

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

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

Содержание

1. Что такое CodeQL
2. Сценарии использования CodeQL
3. Демонстрация работы
4. Консоль LGTM
5. Установка CodeQL
6. Кодовая база
7. Как выглядит простой CodeQL запрос
8. Что дальше?
9. Дополнительные материалы

Что такое CodeQL

CodeQL – это open-source инструмент и язык запросов, немного напоминающий SQL и позволяющий программным образом обращаться к тем или иным участкам кода и выполнять заданные аналитиками проверки графа потоков данных/управления и структуры исходного кода в целом. Аналогом этому подходу являются конфигурируемые правила поиска уязвимостей в инструментах SAST (Static Application Security Testing).

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

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

Изначально CodeQL – это разработка компании Semmle, которая в сентябре 2020 была куплена GitHub и внедрена в их Security Lab. С этого момента развитием продукта занимается сам GitHub и небольшое сообщество энтузиастов. На данный момент официальный репозиторий содержит суммарно свыше 2000 QL-запросов, которые покрывают большое количество разнообразных проблем с кодом, начиная от поиска некорректных регулярных выражений в JavaScript и заканчивая обнаружением использования небезопасных криптографических алгоритмов в коде на C++.

Одним из преимуществ CodeQL является то, что он может не просто искать проблемные участки кода по определенному шаблону (как, например, инструмент SemGrep), но также и «понимать» структуру кода на уровне отдельных инструкций и выражений, отличать вызов функции от вызова метода класса, а также отслеживать путь прохождения значения через выражения и дальнейшие операции с этими выражениями (например присвоение значения переменной, вызов функции с этой переменной в качестве параметра и последующее присвоение результата новой переменной).

На данный момент с разной степенью полноты поддерживаются следующие языки: C/C++, C#, Java, Go, Python, JavaScript/TypeScript. Помимо этого для каждого языка есть набор поддерживаемых фреймворков, упрощающий написание запросов.

CodeQL предоставляется в нескольких вариантах:

  1. Консольная утилита, позволяющая встроить проверки в CI/CD цикл и осуществлять сканирование кода из командной строки.

  2. Расширение для Visual Studio Code для удобного написания и ad-hoc исполнения запросов.

  3. Онлайн-консоль LGTM, позволяющая писать запросы и проводить тестовое сканирование приложения из заданного GitHub-репозитория.

Кроме этого можно подключить сканирование своих репозиториев непосредственно в CI/CD на GitHub.

Всё ещё не убеждены попробовать? Тогда подкину дополнительную мотивацию. GitHub проводит соревнования CTF и вознаграждаемые bug bounty программы для энтузиастов, которые предлагают новые запросы, помогающие обнаруживать как уже известные и документированные уязвимости (CVE), так и 0-day уязвимости.

Сценарии использования CodeQL

Давайте посмотрим, какие есть потенциальные варианты использования CodeQL в нашем проекте:

  1. Самый простой сценарий состоит в том, что мы просто запускаем сканер со всем набором стандартных запросов и разбираем результаты, среди которых будут и проблемы с качеством кода, и проблемы с безопасностью.

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

  3. Самый продуктивный сценарий включает в себя модификацию базовых запросов и/или написание собственных новых запросов, исходя из специфики конкретного приложения и появляющихся угроз.
    Например при выходе очередной 0-day уязвимости аналитик безопасности может составить запрос, который будет проверять все проекты на ее наличие. Или при анализе дефектов, найденных в процессе внутреннего аудита, каждый такой дефект может быть переписан на языке QL, чтобы не допустить проблем в других проектах.

  4. Также CodeQL может быть использован для исследования кода в целом (например определить все точки входа в приложение, чтобы впоследствии эту информацию передать аналитикам, занимающимся динамическим анализом приложения).

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

Демонстрация работы

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

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

Простой запрос CodeQL по поиску пустых блоков
Простой запрос CodeQL по поиску пустых блоков
Обнаруженный пустой участок кода
Обнаруженный пустой участок кода

Или более сложный случай, когда мы ищем проблемы с Cross-Site Scripting:

Запрос CodeQL, обнаруживающий XSS путём отслеживания путей недоверенных данных
Запрос CodeQL, обнаруживающий XSS путём отслеживания путей недоверенных данных

Результат включает в себя все промежуточные шаги (в данном примере только стартовый и конечный), которые привели данные от места ввода до вывода и участки кода, в которых заключена проблема:

А вот так тот же результат выглядит в расширении для VSCode:

На приведенном скриншоте мы видим инструкции CodeQL (верхняя панель), которые отслеживают путь данных от удалённых точек ввода пользовательских данных (например параметры GET-запросов) до конструкций кода, способных отобразить эти недоверенные данные пользователю. При этом отдельной инструкцией isSanitizer указывается, что в коде присутствует санитизирующая функция и соответственно если поток подозрительных данных проходит через эту функцию, он не должен дальше восприниматься как недоверенный. Это один из нескольких способов, которыми мы можем уменьшить количество заведомо ложных срабатываний.

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

Консоль LGTM

Чтобы поэкспериментировать с языком без локальной установки пакета CodeQL можно воспользоваться онлайн-консолью LGTM (Looks Good To Me). Она включает в себя простой редактор запросов и возможность выполнить этот запрос на уже предустановленных кодовых базах нескольких open-source проектов, либо на своем GitHub-проекте.

Давайте сразу попробуем исполнить простейшие запросы и начать практическое знакомство с CodeQL:

  1. Переходим в онлайн-консоль: https://lgtm.com/query/.

  2. Выбираем в качестве языка JavaScript, а в качестве проекта meteor/meteor.

  3. Копируем нижеприведенные запросы.

  4. Нажимаем Run и смотрим результаты в панели под полем ввода кода.

Простой запрос, отображающий все места в анализируемом исходном коде, где объявляются классы выглядит так:

import javascript
from ClassExpr ce
select ce

Более сложный запрос, который покажет нам все места в файле client.js, где происходит вызов функции eval(), а также аргументы этой функции:

import javascript
from CallExpr call
where call.getCalleeName() = "eval" 
and call.getLocation().getFile().getRelativePath().matches("%client.js")
select call, call.getAnArgument()

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

Установка CodeQL

Для использования в своих проектах на постоянной основе консоль LGTM не очень удобна, поэтому есть смысл установить CodeQL CLI и библиотеки локально.

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

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

  1. Установить VSCode и CodeQL extension.

  2. Скачать и распаковать CodeQL CLI в директорию, например, codeql.

  3. Прописать путь до директории codeql в %PATH%.

  4. Скачать стартовый воркспейс VSCode для работы с CodeQL (впоследствии можно будет сделать свой, но для начала работы можно использовать готовый):
    git clone https://github.com/github/vscode-codeql-starter/

    git submodule update --init --remote

    В нем мы будем работать (то есть писать наши запросы) в папке, соответствующей интересующему нас языку (например для JS это codeql-custom-queries-javascript).

  5. Скачиваем тестовую кодовую базу (то есть базу, в которой определенным образом хранятся все необходимые данные о коде и внутренних взаимосвязях в нем, о чем подробнее будет рассказано ниже), например (для JS) https://github.com/githubsatelliteworkshops/codeql/releases/download/v1.0/esbenabootstrap-pre-27047javascript.zip
    Чуть ниже мы посмотрим как создавать свои собственные кодовые базы для наших проектов.

  6. Опционально распаковываем архив с кодовой базой.

  7. В VSCode выбираем Open workspace и открываем файл стартового воркспейса.

  8. В VSCode на закладке CodeQL добавляем папку (или архив) с кодовой базой, против которой будет запускаться анализ кода.

  9. Готово. Теперь в соответствующей папке воркспейса (см. шаг 4) можно открыть файл example.ql и в нем начать писать свои запросы.

  10. Исполняем тестовый запрос и убеждаемся, что всё работает

import javascript
from Expr e
select “Wazzup!”

Кодовая база

В CodeQL весь анализируемый код должен быть специальным образом организован в т. н. кодовую базу, к которой мы будем впоследствии выполнять запросы. В ней содержится полное иерархическое представление этого кода, включая абстрактное синтаксическое дерево (AST), граф потока данных и граф потока управления. Языковые библиотеки CodeQL задают классы, которые добавляют уровень абстракции относительно таблиц в этой базе. Другими словами – у нас появляется возможность писать запросы к кодовой базе, используя принципы ООП. Это как раз та особенность, которая отличает CodeQL от инструментов, которые ищут проблемы в коде при помощи заранее заданных шаблонов и regex'ов.

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

Для разных языков процесс создания базы немного отличается. Например создание кодовой базы для JS в папке my-js-codebase выполняется следующей командой в директории, которая содержит исходный код:

codeql database create my-js-codebase --language=javascript

Для компилируемых языков требуется, чтобы в системе был соответствующий компилятор и сборщик (например Maven для Java)

Следующий шаг – загрузить информацию о базе в VSCode. Это делается в редакторе на соответствующей вкладке CodeQL > “Choose Database from Folder”

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

Как выглядит простой CodeQL запрос

Давайте разберем, из чего вообще состоит типичный запрос в CodeQL на примере кодовой базы на языке JavaScript.

Самый базовый запрос, который выводит все jQuery-функции с именем “$“ (типа $(arg1, arg2)) и их первый аргумент, выглядит так, как показано ниже. Вы можете самостоятельно выполнить его для любой кодовой базы с jQuery:

/**
* @name QueryName
* @kind problem
* @id my_id_1
*/
// -- метаданные

import javascript 
//  Выражение для подключения библиотеки CodeQL для работы 
//   с конструкциями JavaScript.
// Также есть возможность подключения других библиотек для 
//   работы с другими языками, фреймворками и технологиями.
// Например semmle.javascript.NodeJS или python.

from CallExpr dollarCall, Expr dollarArg 
// Объявление переменной dollarCall типа CallExpr 
//   и переменной dollarArg типа Expr.
// CallExpr - это класс из стандартной библиотеки, представляющий 
//   коллекцию всех вызовов функций в интересующем нас коде.
// Expr - класс, представляющий коллекцию всех выражений. 
// Например в Object.entries = function(obj) выражениями являются 
//   вся строка целиком, Object, Object.entries, entries, 
//   function(obj), obj.

where dollarCall.getCalleeName() = "$"
// Логические формулы, которые мы применяем к объявленным переменным.
// Мы проверяем, что результат выполнения предиката (т.е. логической 
//   инструкции) getCaleeName() (который возвращает название 
//   вызываемой функции) нашего объекта dollarCall (который содержит 
//   все вызовы функций) равен "$"

and dollarArg = dollarCall.getArgument(0)
// Эта логическая формула операцией AND соединяется с предыдущей 
//   и уточняет условие, применяемое в запросе.
// В итоге мы из всех вызовов функций, в названии которых есть $ 
//   выбираем в переменную dollarArg первые аргументы (как сущности, 
//   а не как конкретные значения аргументов).

select dollarCall, dollarArg 
// Указание на то, какие выражения (значение каких переменных или 
//   предикатов) мы хотим вывести в результате.

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

Что дальше?

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

Рекомендую поэкспериментировать с запросами на разных приложениях (либо собственных, либо open-source) хотя бы в онлайн-консоли LGTM.

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

https://lab.github.com/githubtraining/codeql-u-boot-challenge-(cc++) – интерактивный курс по работе с CodeQL на примере C/C++

https://lab.github.com/githubtraining/codeql-for-javascript:-unsafe-jquery-plugin – интерактивный курс по анализу JavaScript Bootstrap с помощью CodeQL.

Плюс к этому для начала подойдет очень полезный двухчасовой мастер-класс от GitHub, на котором рассматривается база CodeQL и где при помощи лектора зритель учится шаг за шагом писать запрос для поиска небезопасной десериализации в коде Java-приложения (фреймворк XStream):

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

It is dangerous to go alone! CodeQL – инструмент достаточно сложный, с большим количеством нюансов и, к сожалению, с пока еще не очень большой экспертизой в мире. Поэтому мы бы хотели уже сейчас заняться развитием русскоязычного сообщества экспертов в CodeQL для обмена опытом и совместного решения проблем (которые, разумеется, тоже существуют). Для этой цели мы создали отдельный канал в Telegram, посвященный обсуждениям нюансов этого инструмента и расширению круга экспертизы. Там же мы публикуем новости, обучающие материалы и другую информацию по CodeQL.

Присоединяйтесь - https://t.me/codeql !

Дополнительные материалы

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

https://help.semmle.com/codeql/ – общая помощь по CodeQL от изначальных разработчиков.
https://help.semmle.com/QL/ql-handbook/ – референс по синтаксису языка.
https://help.semmle.com/QL/learn-ql/ – детальная помощь по CodeQL для разных языков.
https://securitylab.github.com/get-involved – информация по тому, как можно узнать больше про CodeQL, помочь его развитию, а также по тому, как получить инвайт в Slack-канал (англоязычный) с ведущими экспертами со всего мира и разработчиками самого CodeQL.

Disclaimer

Стоит отдельно заметить вопрос с лицензией. Формально GitHub не разрешает бесплатное использование CodeQL для своих проектов в ряде случаев. Разрешается использование своих кодовых баз для тестирования запросов, которые сами в свою очередь должны распространяться под OSI-approved лицензией. В остальных случаях необходимо связаться с соответствующей командой GitHub:

GitHub CodeQL can only be used on codebases that are released under an OSI-approved open source license, or to perform academic research, or to generate CodeQL databases for or during automated analysis, continuous integration (CI) or continuous delivery (CD) in the following cases: (1) on any Open Source Codebase hosted and maintained on GitHub.com, and (2) to test CodeQL queries you have released under an OSI-approved open source software license. It can't be used for automated analysis, continuous integration or continuous delivery, whether as part of normal software engineering processes or otherwise, except in the express cases set forth herein. For these uses, contact the sales team.

Подробнее