Ejecución de CodeQL en una base de datos

Completado

Con el código extraído en una base de datos, ahora puede analizarlo mediante consultas CodeQL. Los expertos de GitHub, los investigadores de seguridad y los colaboradores de la comunidad escriben y mantienen las consultas de CodeQL predeterminadas. También puede escribir sus propias consultas.

Puede usar consultas codeQL en el análisis de análisis de código para encontrar problemas en el código fuente e identificar posibles vulnerabilidades de seguridad. También puede escribir consultas personalizadas para identificar los problemas de cada idioma que use en el código fuente.

Hay dos tipos importantes de consultas:

  • Las consultas de alerta resaltan problemas en ubicaciones específicas del código.
  • Las consultas de ruta describen el flujo de información entre un origen y un destino en tu código.

Consulta simple de CodeQL

La estructura básica de consulta CodeQL tiene la extensión .ql de archivo y contiene una select cláusula . Esta es una estructura de consulta de ejemplo:

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

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

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

Personalización de consultas

El análisis de CodeQL está controlado por consultas. Aunque puede usar las consultas estándar proporcionadas por GitHub, también puede personalizar el análisis escribiendo sus propias consultas y organizándolas en paquetes de consultas.

Las consultas se agrupan normalmente en paquetes de consultas, que son directorios que contienen consultas, bibliotecas compartidas y archivos de configuración. Un paquete de consultas permite definir un conjunto reutilizable de reglas de análisis para los proyectos. Dentro de un paquete, puede incluir archivos individuales .ql , bibliotecas auxiliares que definen lógica reutilizable y conjuntos de consultas que agrupan varias consultas.

Un conjunto de consultas (.qls archivo) se usa para controlar qué consultas se ejecutan durante el análisis. En lugar de ejecutar consultas una por una, se define un conjunto que enumera todas las consultas que desea ejecutar. Por ejemplo:

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

Este conjunto agrupa varias consultas para que se puedan ejecutar juntas como parte de un único análisis.

Puede crear sus propias consultas escribiendo .ql archivos. Una consulta describe un patrón en el código que desea detectar. Normalmente importa una biblioteca de lenguajes, define condiciones y devuelve resultados mediante una select instrucción .

Por ejemplo, la consulta siguiente busca literales de cadena que pueden contener credenciales codificadas de forma dura:

/**
 * @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"

En esta consulta:

  • La import instrucción carga el modelo de lenguaje para JavaScript.
  • La from cláusula define los datos que se analizan.
  • La where cláusula filtra los patrones coincidentes.
  • La select instrucción define qué resultados se devuelven.

Puede crear consultas personalizadas empezando por consultas estándar y modificando sus condiciones o salida.

Para usar una consulta con el análisis de código de GitHub, debe incluir metadatos de la consulta. Los metadatos se definen en un bloque de comentarios en la parte superior del archivo y controla cómo se interpretan y muestran los resultados.

Como mínimo, los metadatos deben incluir:

  • Un identificador único (@id)
  • Un nombre (@name)
  • Una descripción (@description)
  • Tipo de resultado (@kind, como problem o path-problem)

Propiedades adicionales como @severity y @precision ayudan a determinar cómo aparecen las alertas en GitHub.

Los metadatos son necesarios para la integración con el análisis de código. Cuando los metadatos están presentes, los resultados se muestran como alertas en el repositorio. Si faltan metadatos, CodeQL sigue ejecutando la consulta, pero los resultados solo se muestran como salida sin procesar y no se muestran como alertas de examen de código.

Una vez que haya definido las consultas o el conjunto de consultas, puede incluirlas en la configuración de análisis. En Acciones de GitHub, especifique las consultas durante el paso de inicialización:

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

Durante el flujo de trabajo:

  1. CodeQL crea la base de datos.
  2. Ejecuta las consultas seleccionadas.
  3. Genera resultados en formato SARIF.
  4. Carga los resultados en GitHub.

Los resultados de la consulta personalizados aparecen junto con los resultados estándar de CodeQL en la pestaña Seguridad. Esto le permite ampliar el análisis predeterminado con comprobaciones específicas del código base mientras sigue beneficiándose de los conjuntos de consultas mantenidos de GitHub.

Metadatos de consulta

En la sección anterior, ha agregado metadatos a una consulta para que se pueda usar en el examen de código. En esta sección se explica cómo se usan los metadatos y cómo afecta a los resultados de la consulta.

Los metadatos de consulta se definen en un bloque de comentarios en la parte superior de un .ql archivo. Proporciona información sobre la consulta y controla cómo se interpretan y muestran los resultados.

Los metadatos se utilizan en CodeQL y en el análisis de código de GitHub para:

  • Identifique la consulta y su propósito.
  • Determine cómo se clasifican los resultados (por ejemplo, problem o path).
  • Asigne niveles de gravedad y precisión.
  • Dar formato a los resultados para mostrarlos en el repositorio.

Por ejemplo, una consulta podría incluir metadatos como este:

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

Cuando estos metadatos están presentes:

  • Los resultados se convierten en formato SARIF.
  • Las alertas se muestran en el análisis de código de GitHub.
  • Los resultados incluyen contexto como gravedad y descripción.

Cuando faltan metadatos:

  • La consulta todavía se ejecuta.
  • Los resultados no se muestran como alertas.
  • La salida solo se muestra como tablas en bruto.

Los metadatos también determinan cómo se agrupan los resultados y se hace su seguimiento entre análisis. Por ejemplo, la consulta @id se usa para buscar coincidencias de alertas entre ejecuciones.

GitHub tiene una guía de estilo recomendada para los metadatos de consulta. Puede encontrarlo en la documentación de CodeQL.

En este ejemplo se muestran los metadatos de una de las consultas de Java estándar:

Captura de pantalla de los metadatos de consulta de una consulta estándar Java CodeQL.

CodeQL no interpreta las consultas que no tienen metadatos. Muestra esos resultados como una tabla y no los muestra en el código fuente.

Escritura, prueba y ejecución de consultas

Después de crear consultas personalizadas, el siguiente paso es probarlas, ejecutarlas en los flujos de trabajo y mantenerlas con el tiempo.

Al escribir una consulta, va a definir un patrón que CodeQL debe detectar en el código base. La forma más eficaz de desarrollar consultas es iterar localmente antes de añadirlas al repositorio.

Probar consultas localmente

Puede probar las consultas mediante la CLI de CodeQL o la extensión Visual Studio Code.

Con la CLI de CodeQL, se ejecutan consultas en una base de datos que ya ha creado:

codeql database analyze <database> <query.ql>

Este comando ejecuta la consulta y genera resultados que puede revisar en SARIF u otro formato de salida.

También puede ejecutar:

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

Las pruebas localmente le permiten:

  • Compruebe que la consulta devuelve los resultados esperados.
  • Refinar la lógica de consulta.
  • Identificar falsos positivos o casos que faltan.

La extensión Visual Studio Code proporciona una experiencia más interactiva. Ustedes pueden:

  • Abra una base de datos.
  • Ejecute consultas directamente desde el editor.
  • Vea los resultados junto con el código fuente.

Esto facilita comprender cómo se comporta la consulta y ajustarla rápidamente.

Ejecutar consultas en el análisis de código de GitHub

Una vez que la consulta genere los resultados esperados, puede incluirlos en el flujo de trabajo de análisis de código.

En Acciones de GitHub, las consultas se configuran en el paso de inicialización:

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

Cuando se ejecuta el flujo de trabajo:

  1. CodeQL crea una base de datos para el repositorio.
  2. Ejecuta las consultas seleccionadas.
  3. Convierte los resultados en SARIF.
  4. Carga los resultados en GitHub.

Los resultados aparecen como alertas en la pestaña Seguridad , junto con los resultados estándar de CodeQL.

La ejecución de consultas en flujos de trabajo garantiza que:

  • El análisis se ejecuta automáticamente en las solicitudes de extracción y las ramas.
  • Se detectan nuevos problemas a medida que cambia el código.
  • Los resultados son visibles para el equipo.

Mantenimiento y actualización de consultas

Después de agregar una consulta personalizada al flujo de trabajo, es posible que observe que los resultados no siempre son lo que espera.

Por ejemplo:

  • Una consulta puede devolver demasiados resultados (falsos positivos).
  • Es posible que pierda los casos que esperaba que detecte.
  • Es posible que los nuevos patrones de código de su repositorio no estén contemplados.

En estos casos, actualizas la consulta para mejorar su precisión.

Comience ejecutando la consulta localmente y revisando los resultados. Examine las ubicaciones de código marcadas y decida si representan problemas reales. Si no es así, restrinja las condiciones de la where cláusula para restringir los resultados.

Por ejemplo, es posible que:

  • Agregue condiciones adicionales para excluir patrones seguros.
  • Ajuste la coincidencia de cadenas o la lógica de flujo de datos.
  • Vuelva a usar predicados de bibliotecas existentes para mejorar la precisión.

Después de actualizar la consulta, vuelva a ejecutarla en la base de datos para confirmar que los resultados han mejorado.

Al confirmar la consulta actualizada, se ejecuta automáticamente en el flujo de trabajo de análisis de código. Esto significa lo siguiente:

  • Las alertas existentes se pueden actualizar o quitar.
  • Las nuevas alertas pueden aparecer en función de la lógica actualizada.

Con el tiempo, repite este proceso a medida que evoluciona el código base. Mantener las consultas es una tarea en curso que ayuda a garantizar que el análisis sigue siendo preciso y relevante.

Sintaxis de QL

QL es un lenguaje de consulta declarativo orientado a objetos. Está optimizado para permitir un análisis eficaz de estructuras de datos jerárquicas y, en particular, bases de datos que representan artefactos de software.

La sintaxis de QL es similar a SQL, pero la semántica de QL se basa en Datalog. Datalog es un lenguaje de programación lógica declarativo, que a menudo se usa como lenguaje de consulta. Dado que QL es principalmente un lenguaje lógico, todas las operaciones de QL son operaciones lógicas. QL también hereda predicados recursivos de Datalog. QL agrega compatibilidad con agregados para que incluso consultas complejas sean concisas y sencillas.

El lenguaje QL consta de fórmulas lógicas. Usa conectivos lógicos comunes, como and, ory not, junto con cuantificadores como forall y exists. Dado que QL hereda predicados recursivos, también puede escribir consultas recursivas complejas mediante la sintaxis básica de QL y agregados como count, sumy average.

Para obtener más información sobre el lenguaje QL, consulte la documentación de CodeQL.

Consultas de ruta de acceso

La forma en que fluye la información a través de un programa es importante. Los datos que parecen benignos pueden fluir de maneras inesperadas que permiten su uso malintencionado.

La creación de consultas de ruta de acceso puede ayudarle a visualizar el flujo de información a través de un código base. Una consulta puede rastrear el recorrido que siguen los datos desde sus posibles puntos de partida (origen) hasta sus posibles puntos finales (sumidero). Para modelar rutas de acceso, la consulta debe proporcionar información sobre el origen, el receptor y los pasos de flujo de datos que los vinculan.

La manera más fácil de empezar a escribir tu propia consulta de ruta es usar una de las consultas existentes como plantilla. Para obtener estas consultas para los idiomas admitidos, consulte la documentación de CodeQL.

La consulta de ruta de acceso necesitará determinados metadatos, predicados de consulta y estructuras de instrucciones select. Muchas de las consultas de ruta de acceso integradas en CodeQL siguen una estructura básica. La estructura depende de cómo CodeQL modele el lenguaje que está analizando.

Aquí tienes una plantilla de ejemplo para una consulta de ruta:

/**
 * ...
 * @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>"

En esa plantilla:

  • MyConfiguration es un módulo que contiene los predicados que definen cómo fluyen los datos entre el origen y el receptor.
  • Flow es el resultado del cálculo de flujo de datos basado en MyConfiguration.
  • Flow::PathGraph es el módulo resultante del grafo de flujo de datos que debe importar para incluir las explicaciones de ruta en la consulta.
  • source y sink son nodos del gráfico tal como se define en la configuración y Flow::PathNode es su tipo.
  • DataFlow::Global<..> es una invocación del flujo de datos. Puede usar TaintTracking::Global<..> en su lugar para incluir un conjunto predeterminado de pasos taint.

Cómo redactar una consulta de ruta

La consulta debe calcular un grafo de camino para generar explicaciones de la ruta. Para ello, defina un predicado de consulta denominado edges. Un predicado de consulta es un predicado no miembro con una anotación de consulta. La anotación de consulta devuelve todas las tuplas que evalúa el predicado.

El edges predicado define las relaciones perimetrales del grafo que está calculando. Se usa para calcular las rutas de acceso relacionadas con cada resultado que genera la consulta. También puede importar un predicado predefinido edges desde un módulo de grafo de caminos en una de las bibliotecas de flujo de datos estándar.

Las bibliotecas de flujo de datos, además del módulo de grafo de caminos, contienen las otras clases, predicados y módulos que se usan habitualmente en el análisis de flujo de datos. Las bibliotecas de flujo de datos de CodeQL funcionan mediante el modelado del grafo de flujo de datos o implementando el análisis de flujo de datos. Las bibliotecas de flujo de datos normales se usan para analizar el flujo de información en el que se conservan los valores de datos en cada paso.

Esta es una instrucción de ejemplo que importa el PathGraph módulo desde la biblioteca de flujo de datos (DataFlow.qll), en la que edges se define:

import DataFlow::PathGraph

Puede importar muchas otras bibliotecas incluidas con CodeQL. También puede importar bibliotecas diseñadas específicamente para implementar el análisis de flujo de datos en varios marcos y entornos comunes.

La clase PathNode está diseñada para implementar el análisis de flujo de datos. Se trata de un elemento Node aumentado con un contexto de llamada (excepto para receptores), una ruta de acceso y una configuración. Solo se generan valores PathNode que son alcanzables desde un origen.

Este es un ejemplo de la ruta de importación:

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

Opcionalmente, puede definir un nodes predicado de consulta, que especifica los nodos del gráfico de rutas de acceso para todos los lenguajes. Al definir nodes, los nodos seleccionados definen solo bordes con puntos de conexión. Si no define nodes, debe seleccionar todos los extremos posibles de las aristas.

Análisis de base de datos

Al usar consultas para analizar una base de datos CodeQL, recibirá resultados significativos en el contexto del código fuente. Los resultados tienen un estilo de alertas o rutas de acceso en SARIF u otro formato interpretado.

Este es un ejemplo de un comando de base de datos CodeQL que analiza la base de datos ejecutando consultas seleccionadas en ella e interpretando los resultados:

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

Este comando combina el efecto de los comandos de fontanería codeql database run-queries y codeql database interpret-results .

Como alternativa, puede ejecutar consultas que no cumplan los requisitos para interpretarse como alertas de código fuente. Para ello, use:

  • codeql database run-queries
  • codeql query run

A continuación, use:

codeql bqrs decode

para convertir los resultados sin procesar en una notación legible.

Puede obtener una lista completa de los comandos de la CLI de CodeQL disponibles en el manual de la CLI de CodeQL.

Uso de un archivo SARIF con categorías

CodeQL admite SARIF para compartir resultados de análisis estáticos. SARIF está diseñado para representar la salida de una amplia gama de herramientas de análisis estáticos.

Debe especificarse una categoría al utilizar la salida SARIF para el análisis de CodeQL. Las categorías pueden distinguir varios análisis realizados en el mismo repositorio de confirmación y en distintos lenguajes o partes diferentes del código. Sin embargo, los archivos SARIF con la misma categoría se sobrescriben entre sí.

Puede examinar cada archivo de salida SARIF mediante CodeQL para analizar diferentes idiomas dentro de la misma base de código cuando el valor de categoría es coherente entre las ejecuciones de análisis. Se recomienda usar el idioma que se examina como identificador de la categoría.

Por ejemplo, aparece el valor de categoría (con una barra diagonal final anexada si aún no está presente) como:

  • <run>.automationId en SARIF v1
  • <run>.automationLogicalId en SARIF v2
  • <run>.automationDetails.id en SARIF v2.1.0

Publicación de los resultados de SARIF en GitHub

Una vez lista la base de datos, puede consultarla de forma interactiva. También puede ejecutar un conjunto de consultas para generar un conjunto de resultados en formato SARIF y cargar los resultados en un repositorio de destino en 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>...

Para cargar los resultados en GitHub, asegúrese de que cada servidor de integración continua (CI) tenga una aplicación de GitHub o un token de acceso personal para que la CLI de CodeQL la use. Debe usar un token de acceso o una aplicación de GitHub con el security_events permiso de escritura.

Podría permitir que la CLI de CodeQL use el mismo token si los servidores de CI ya usan un token con este ámbito para consultar repositorios de GitHub. De lo contrario, cree un token con el permiso de escritura security_events y agréguelo al almacén de secretos del sistema de CI.

Como práctica recomendada de seguridad, use el indicador --github-auth-stdin y pase el token al comando a través de la entrada estándar.

Carga de resultados de SARIF

Para que el análisis de código muestre los resultados de una herramienta de análisis estático que no es de Microsoft en el repositorio de GitHub, los resultados deben almacenarse en un archivo SARIF que admita un subconjunto específico del esquema JSON SARIF 2.1.0. Puede cargar los resultados mediante la API de examen de código o la CLI de CodeQL.

Cada vez que cargue los resultados de un nuevo examen de código, CodeQL procesa los resultados y agrega alertas al repositorio. Para evitar alertas duplicadas para el mismo problema, el examen de código usa la propiedad SARIF partialFingerprints para que coincidan con los resultados entre ejecuciones para que aparezcan solo una vez en la última ejecución de la rama seleccionada.

La eliminación de duplicados permite hacer coincidir las alertas con la línea de código correcta cuando se editan los archivos.

El identificador de regla de un resultado debe ser el mismo en todos los análisis. Los datos de huellas digitales se incluyen automáticamente en los archivos SARIF creados mediante el flujo de trabajo de análisis de CodeQL o el ejecutor de CodeQL.

Las especificaciones SARIF usan el nombre de propiedad JSON partialFingerprints, un diccionario de tipos de huellas digitales con nombre para la huella digital. Esta propiedad contiene, como mínimo, un valor para primaryLocationLineHash, que proporciona una huella digital basada en el contexto de la ubicación principal.

GitHub intenta rellenar el partialFingerprints campo de los archivos de origen si carga un archivo SARIF mediante la upload-sarif acción y faltan estos datos.

Además, si cargas un archivo SARIF sin datos de huella digital mediante el punto de conexión de la API /code-scanning/sarifs, es posible que los usuarios vean alertas duplicadas cuando las alertas de análisis de código se procesan y se muestran.

Para evitar alertas duplicadas mientras se trabaja con herramientas de análisis estáticos, calcule los datos de huellas digitales y rellene la partialFingerprints propiedad antes de cargar el archivo SARIF. Un punto de partida útil es usar el mismo script que la upload-sarif acción.