데이터베이스에서 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 CLI 또는 Visual Studio Code 확장을 사용하여 쿼리를 테스트할 수 있습니다.

CodeQL CLI를 사용하면 이미 만든 데이터베이스에 대해 쿼리를 실행합니다.

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 결과와 함께 보안 탭에 경고로 표시됩니다.

워크플로에서 쿼리를 실행하면 다음이 보장됩니다.

  • 분석은 풀 리퀘스트와 브랜치에서 자동으로 실행됩니다.
  • 코드 변경으로 새 문제가 검색됩니다.
  • 결과는 팀에 표시됩니다.

쿼리 유지 관리 및 업데이트

워크플로에 사용자 지정 쿼리를 추가한 후 결과가 항상 예상한 것은 아니라는 것을 알 수 있습니다.

다음은 그 예입니다.

  • 쿼리가 너무 많은 결과(가양성)를 반환할 수 있습니다.
  • 감지할 것으로 예상한 사례가 누락될 수 있습니다.
  • 리포지토리의 새 코드 패턴은 다루지 않을 수 있습니다.

이러한 경우 쿼리를 업데이트하여 정확도를 향상시킵니다.

먼저 쿼리를 로컬로 실행하고 결과를 검토합니다. 플래그가 지정된 코드 위치를 확인하고 실제 문제를 나타내는지 여부를 결정합니다. 그렇지 않으면 결과 범위를 좁히려면 where 절의 조건을 구체적으로 조정하세요.

예를 들어 다음이 가능할 수도 있습니다.

  • 안전 패턴을 제외하는 추가 조건을 추가합니다.
  • 문자열 일치 또는 데이터 흐름 논리를 조정합니다.
  • 기존 라이브러리의 조건자를 다시 사용하여 정확도를 향상시킵니다.

쿼리를 업데이트한 후 데이터베이스에 대해 다시 실행하여 결과가 개선되었는지 확인합니다.

업데이트된 쿼리를 커밋하면 코드 검색 워크플로에서 자동으로 실행됩니다. 이것은 다음을 의미합니다.

  • 기존 경고는 업데이트되거나 제거될 수 있습니다.
  • 업데이트된 논리에 따라 새 경고가 나타날 수 있습니다.

시간이 지남에 따라 코드베이스가 발전함에 따라 이 프로세스를 반복합니다. 쿼리 유지 관리는 분석이 정확하고 관련성을 유지하는 데 도움이 되는 지속적인 작업입니다.

QL 구문

QL은 선언적 개체 지향 쿼리 언어입니다. 계층적 데이터 구조, 특히 소프트웨어 아티팩트만 나타내는 데이터베이스를 효율적으로 분석할 수 있도록 최적화되었습니다.

QL 구문은 SQL과 유사하지만 QL의 의미 체계는 Datalog를 기반으로 합니다. 데이터 로그는 선언적 논리 프로그래밍 언어로, 쿼리 언어로 자주 사용됩니다. QL은 주로 논리 언어이므로 QL의 모든 작업은 논리 작업입니다. QL은 또한 Datalog에서 재귀 조건자를 상속합니다. QL은 복잡한 쿼리를 간결하고 간단하게 만들기 위해 집계에 대한 지원을 추가합니다.

QL 언어는 논리 수식으로 구성됩니다. and, or, 및 not 같은 일반적인 논리 결합체를 forallexists 같은 수량자와 함께 사용합니다. QL은 재귀 조건자를 상속하므로 기본 QL 구문 및 집계(예count: , sumaverage및)를 사용하여 복잡한 재귀 쿼리를 작성할 수도 있습니다.

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 는 원본과 싱크 간에 데이터가 흐르는 방식을 정의하는 조건자를 포함하는 모듈입니다.
  • FlowMyConfiguration을 기반으로 한 데이터 흐름 계산의 결과입니다.
  • Flow::PathGraph 는 쿼리에 경로 설명을 포함하기 위해 가져와야 하는 결과 데이터 흐름 그래프 모듈입니다.
  • source sink 은 구성에 정의된 대로 그래프의 노드이며 Flow::PathNode 해당 형식입니다.
  • DataFlow::Global<..> 는 데이터 흐름의 호출입니다. TaintTracking::Global<..> 대신 기본 taint 단계 집합을 포함할 수 있습니다.

경로 쿼리를 작성하는 방법

경로 설명을 생성하려면 쿼리에서 경로 그래프를 계산해야 합니다. 이렇게 하려면 라는 edges쿼리 조건자를 정의합니다. 쿼리 프레디킷은 쿼리 어노테이션이 있는 비멤버 프레디킷입니다. 쿼리 주석은 조건자가 평가하는 모든 튜플을 반환합니다.

프레디케이트 edges는 여러분이 컴퓨팅하는 그래프의 에지 관계를 정의합니다. 쿼리에서 생성하는 각 결과와 관련된 경로를 계산하는 데 사용됩니다. 표준 데이터 흐름 라이브러리 중 하나의 경로 그래프 모듈에서 미리 정의된 edges 조건자를 가져올 수도 있습니다.

데이터 흐름 라이브러리에는 경로 그래프 모듈 외에도 데이터 흐름 분석에 일반적으로 사용되는 다른 클래스, 조건자 및 모듈이 포함됩니다. CodeQL 데이터 흐름 라이브러리는 데이터 흐름 그래프를 모델링하거나 데이터 흐름 분석을 구현하여 작동합니다. 일반 데이터 흐름 라이브러리는 각 단계에서 데이터 값이 유지되는 정보 흐름을 분석하는 데 사용됩니다.

다음은 PathGraph가 정의된 데이터 흐름 라이브러리(DataFlow.qll)에서 edges 모듈을 가져오는 예제 문입니다.

import DataFlow::PathGraph

CodeQL에 포함된 다른 많은 라이브러리를 가져올 수 있습니다. 다양한 공통 프레임워크 및 환경에서 데이터 흐름 분석을 구현하도록 특별히 설계된 라이브러리를 가져올 수도 있습니다.

이 클래스 PathNode 는 데이터 흐름 분석을 구현하도록 설계되었습니다. Node 호출 컨텍스트(싱크 제외), 액세스 경로 및 구성으로 보강됩니다. "소스에서 도달할 수 있는 값만 생성됩니다."

가져오기 경로의 예는 다음과 같습니다.

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-queriescodeql database interpret-results 배관 명령의 효과를 결합합니다.

또는 소스 코드 경고로 해석되는 요구 사항을 충족하지 않는 쿼리를 실행할 수 있습니다. 이렇게 하려면 다음을 사용합니다.

  • codeql database run-queries
  • codeql query run

그런 다음 다음을 사용합니다.

codeql bqrs decode

원시 결과를 읽을 수 있는 표기법으로 변환합니다.

CodeQL CLI 설명서에서 사용 가능한 CodeQL CLI 명령의 전체 목록을 가져올 수 있습니다.

범주가 있는 SARIF 파일 사용

CodeQL은 정적 분석 결과를 공유하기 위해 SARIF를 지원합니다. SARIF는 광범위한 정적 분석 도구의 출력을 나타내도록 설계되었습니다.

CodeQL 분석에 SARIF 출력을 사용할 때 범주 를 지정해야 합니다. 범주는 동일한 커밋 리포지토리 및 다른 언어 또는 코드의 다른 부분에서 수행되는 여러 분석을 구분할 수 있습니다. 그러나 범주가 동일한 SARIF 파일은 서로를 덮어씁 수 있습니다.

분석 실행 간에 범주 값이 일치할 때 CodeQL을 사용하여 각 SARIF 출력 파일을 검색하여 동일한 코드 베이스 내에서 다른 언어를 분석할 수 있습니다. 검색되는 언어를 범주의 식별자로 사용하는 것이 좋습니다.

예를 들어, 카테고리 값은(이미 후행 슬래시가 있지 않은 경우 후행 슬래시가 추가되어) 다음과 같이 표시됩니다:

  • SARIF v1의 <run>.automationId
  • <run>.automationLogicalId SARIF v2의
  • <run>.automationDetails.id SARIF v2.1.0에서

GitHub에 SARIF 결과 게시

데이터베이스가 준비되면 대화형으로 쿼리할 수 있습니다. 또는 쿼리 제품군을 실행하여 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(연속 통합) 서버에 사용할 CodeQL CLI에 대한 GitHub 앱 또는 개인용 액세스 토큰이 있는지 확인합니다. 쓰기 권한이 있는 액세스 토큰 또는 GitHub 앱을 security_events 사용해야 합니다.

CI 서버가 이미 이 범위의 토큰을 사용하여 GitHub에서 리포지토리를 체크 아웃하는 경우 CodeQL CLI가 동일한 토큰을 사용하도록 허용할 수 있습니다. 그렇지 않으면 쓰기 권한이 있는 새 토큰을 security_events 만들고 이 토큰을 CI 시스템의 비밀 저장소에 추가합니다.

보안 모범 사례로 플래그를 --github-auth-stdin 사용하고 표준 입력을 통해 명령에 토큰을 전달합니다.

SARIF 결과 업로드

GitHub 리포지토리에 Microsoft가 아닌 정적 분석 도구의 결과를 표시하기 위한 코드 검색의 경우 결과는 SARIF 2.1.0 JSON 스키마의 특정 하위 집합을 지원하는 SARIF 파일에 저장되어야 합니다. 코드 검색 API 또는 CodeQL CLI를 사용하여 결과를 업로드할 수 있습니다.

새 코드 검색 결과를 업로드할 때마다 CodeQL은 결과를 처리하고 리포지토리에 경고를 추가합니다. 동일한 문제에 대한 중복 경고를 방지하기 위해 코드 검색은 SARIF partialFingerprints 속성을 사용하여 실행 간에 결과를 일치시키는 방식으로 선택한 분기에 대한 최신 실행에서 한 번만 표시됩니다.

중복을 제거하면 파일을 편집할 때 경고를 올바른 코드 줄과 일치시킬 수 있습니다.

결과에 대한 규칙 ID는 분석에서 동일해야 합니다. 지문 데이터는 CodeQL 분석 워크플로 또는 CodeQL 실행기를 통해 만든 SARIF 파일에 자동으로 포함됩니다.

SARIF 사양은 명명된 지문 유형에서 지문까지의 사전인 JSON 속성 이름을 partialFingerprints사용합니다. 이 속성에는 최소한 기본 위치의 컨텍스트에 따라 지문을 제공하는 primaryLocationLineHash 값이 포함됩니다.

작업 partialFingerprints을 사용하여 SARIF 파일을 업로드할 때 이 데이터가 누락된 경우, GitHub는 원본 파일에서 upload-sarif 필드를 채우려고 합니다.

또한 API 엔드포인트를 사용하여 /code-scanning/sarifs 지문 데이터 없이 SARIF 파일을 업로드하는 경우 코드 검사 경고가 처리되고 표시될 때 사용자에게 중복 경고가 표시될 수 있습니다.

정적 분석 도구를 사용하는 동안 중복 경고를 방지하려면 SARIF 파일을 업로드하기 전에 지문 데이터를 계산하고 속성을 채웁니다 partialFingerprints . 유용한 시작점은 upload-sarif 작업과 동일한 스크립트를 사용하는 것입니다.