LoginSignup
5
4

CodeQL を Docker と GitHub Actions で実行し、コードの脆弱性を検出する

Last updated at Posted at 2021-09-03

はじめに

2023/9/1 更新

CodeQL は静的解析ツールの一種で、コードのセキュリティチェックを行います

pre-commitSuper-Linter を使えばコードの文法や保守性・可読性のチェックはできますが、

CodeQL ではそれだけでなく、クロスサイトスクリプティングや SQL インジェクションの原因となる記述を検出してくれるのです

CodeQL を活用すれば、プロジェクトメンバーの経験不足によるセキュリティホールの発生を防げます

今回は CodeQL を Docker (ローカル上)と GitHub Actions で実行してみましょう

実装例はこちら

処理の流れ

CodeQL では、まずコードを解析してデータベース化します

その上で、構築したデータベースに対して QL という言語で書かれたクエリを発行し、脆弱性に該当するコードがないか探すわけです

どのようなクエリが発行されるかは後述します

CodeQL は CLI が提供されており、ローカルで実行する場合は以下のように実行します

ただ、ローカルにインストールしたりするのが面倒なので、今回は Docker と GitHub Actions で実行します

データベースの作成

codeql database create \
  --language=<解析対象の言語> \
  <データベースの作成先> \
  -s <コードのルートパス>

解析対象の言語には以下のものが指定可能です

  • cpp: C++
  • csharp: C#
  • go: Go
  • java: Java
  • javascript: JavaScript, TypeScript
  • python: Python
  • ruby: Ruby
  • swift: Swift

解析の実行

codeql database analyze \
  <データベースの作成先> \
  <クエリスイート> \
  --format <結果の出力フォーマット> \
  --output <結果の出力先>

指定できるクエリスイート(実行するチェックの一式) は以下のいずれかです

  • javascript-code-scanning.qls
  • javascript-lgtm-full.qls
  • javascript-lgtm.qls
  • javascript-security-and-quality.qls
  • javascript-security-experimental.qls
  • javascript-security-extended.qls

Docker で実行

Microsoft が CodeQL Container というものを作ってくれているので、こちらを使います

README に実行コマンドやシェルを用意してくれていますが、毎回長々オプションを指定したくないので、 docker-compose で定義してしまいます

処理の流れで説明したように、データベースの作成と解析の実行、2段階あるため、それぞれ定義します

データベースの作成定義

今回は JavaScript の解析のみを行う場合です

codeql/docker-compose-create-db.yml

version: '3'
services:
  linter:
    build:
      context: .
      dockerfile: Dockerfile.patch
    environment:
      - CODEQL_CLI_ARGS=database create --language=javascript /opt/results/source_db -s /opt/src
    volumes:
      - ../:/opt/src
      - ./results:/opt/results

解析の実行定義

クエリスイートには javascript-security-and-quality.qls を指定しています

セキュリティ以外にも保守性などをチェックします

また、過度にリソースを食わないように threads は 4 にしていますが、ここは環境に合わせて調整してください

codeql/docker-compose-analyze.yml

version: '3'
services:
  linter:
    build:
      context: .
      dockerfile: Dockerfile.patch
    environment:
      - CODEQL_CLI_ARGS=database analyze /opt/results/source_db javascript-security-and-quality.qls --format csv --output /opt/results/codeql.csv --threads 4
    volumes:
      - ./results:/opt/results

コンテナのイメージをそのまま実行すると setup.py に実行権限がないというエラーが発生するため、パッチをあてます

# hadolint ignore=DL3007
FROM mcr.microsoft.com/cstsectools/codeql-container:latest

USER root

RUN chmod +x /usr/local/startup_scripts/setup.py

USER codeql

コンテナの実行

上記ファイルを準備して、以下のように実行すれば CodeQL を動かすことができます

実行にはそこそこ時間がかかるのと、 Docker にそれなりにメモリを割り当てていないとメモリ不足で落ちることもあります

  • データベースの作成
$ docker compose -f codeql/docker-compose-create-db.yml up
Recreating codeql_linter_1 ... done
Attaching to codeql_linter_1
linter_1  | Initializing database at /opt/results/source_db.
linter_1  | Running command [/usr/local/codeql-home/codeql/javascript/tools/autobuild.sh] in /opt/src.
linter_1  | Finalizing database at /opt/results/source_db.
linter_1  | Successfully created database at /opt/results/source_db.
linter_1  | [2021-08-25 05:56:28] [build] Single-threaded extraction.
linter_1  | [2021-08-25 05:57:50] [build] Extracting /opt/src/webpack-sample/dist/index.html

...

linter_1  | [2021-08-25 05:57:58] [build] Extracting /usr/local/codeql-home/codeql/javascript/tools/data/externs/lib/should.js
linter_1  | [2021-08-25 05:57:58] [build] Done extracting /usr/local/codeql-home/codeql/javascript/tools/data/externs/lib/should.js (34 ms)
linter_1  | 
codeql_linter_1 exited with code 0
  • 解析の実行
$ docker compose -f codeql/docker-compose-analyze.yml up
Recreating codeql_linter_1 ... done
Attaching to codeql_linter_1
linter_1  | Running queries.
linter_1  | Compiling query plan for /usr/local/codeql-home/codeql-repo/javascript/ql/src/LanguageFeatures/InvalidPrototype.ql.
linter_1  | [1/172] Found in cache: /usr/local/codeql-home/codeql-repo/javascript/ql/src/LanguageFeatures/InvalidPrototype.ql.
linter_1  | Compiling query plan for /usr/local/codeql-home/codeql-repo/javascript/ql/src/LanguageFeatures/WithStatement.ql.

...

linter_1  | Compiling query plan for /usr/local/codeql-home/codeql-repo/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql.
linter_1  | [172/172] Found in cache: /usr/local/codeql-home/codeql-repo/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql.
linter_1  | Starting evaluation of codeql-javascript/LanguageFeatures/InvalidPrototype.ql.
linter_1  | Starting evaluation of codeql-javascript/LanguageFeatures/WithStatement.ql.
linter_1  | Starting evaluation of codeql-javascript/LanguageFeatures/DeleteVar.ql.
linter_1  | Starting evaluation of codeql-javascript/LanguageFeatures/SyntaxError.ql.
linter_1  | [1/172 eval 1.3s] Evaluation done; writing results to codeql-javascript/LanguageFeatures/SyntaxError.bqrs.
linter_1  | [2/172 eval 1.3s] Evaluation done; writing results to codeql-javascript/LanguageFeatures/DeleteVar.bqrs.

...

linter_1  | [171/172 eval 2.3s] Evaluation done; writing results to codeql-javascript/Security/CWE-918/RequestForgery.bqrs.
linter_1  | [172/172 eval 2.8s] Evaluation done; writing results to codeql-javascript/Security/CWE-400/RemotePropertyInjection.bqrs.
linter_1  | Shutting down query evaluator.
linter_1  | Interpreting results.
linter_1  | 
codeql_linter_1 exited with code 0

codeql/results/codeql.csv に結果が出ますが、何もエラーがなければ空です

わざと以下のような悪いコード( SQL インジェクション)を入れてみましょう

残念ながら eslint ではこれを検出することはできませんが、 CodeQL ならやってくれます

const app = require("express")();
const pg = require("pg");
const pool = new pg.Pool();

app.get("search", function handler(req, res) {
  pool.query(
    "SELECT * FROM TABLE_A WHERE XXX='" + req.params.xxx + "' ORDER BY YYY",
    []
  );
});

ちなみに、悪いコードの例や、その修正例(良い例)は以下のようなパス(各分類以下の examples)に格納されているので、参考にしてください

https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-089/examples/SqlInjection.js

悪いコードがある状態で実行すると、 codeql/results/codeql.csv に以下のように悪かった箇所が出てきます

"Database query built from user-controlled sources","Building a database query from user-controlled sources is vulnerable to insertion of malicious code by the user.","error","This query depends on [[""a user-provided value""|""relative:///webpack-sample/src/hello.js:7:43:7:56""]].","/webpack-sample/src/hello.js","7","5","7","75"
"Missing rate limiting","An HTTP request handler that performs expensive operations without restricting the rate at which operations can be carried out is vulnerable to denial-of-service attacks.","error","This route handler performs [[""a database access""|""relative:///webpack-sample/src/hello.js:6:3:9:3""]], but is not rate-limited.","/webpack-sample/src/hello.js","5","19","5","46"

実行の簡易化

コードを変更して再実行する場合、毎回データベースを一旦削除して再作成しなければなりません

面倒なのでスクリプトにしてしまいましょう

codeql/run.sh

#!/bin/bash
rm -rf codeql/results/source_db

docker-compose -f codeql/docker-compose-create-db.yml up
docker-compose -f codeql/docker-compose-analyze.yml up

ついでに npm のスクリプトにしてしまいましょう

package.json

  "scripts": {
    "codeql": "codeql/run.sh"
  },

これで、以下のようにすれば簡単に CodeQL が実行できるようになりました

npm run codeql

GitHub Actions で実行

GitHub 公式が用意してくれています

自分で定義ファイルを用意しても良いですし、

リポジトリーの設定から Security & analysis -> Code scanningSet up をクリックしてブラウザから簡単に設定することも可能です

repository.png

デフォルトの設定だと保守性等のチェックはしてくれないので、今回はクエリスイートを security-and-quality に変えてみましょう

GitHub Actions 設定の定義

CodeQL の設定ファイルを作成します

.github/codeql/codeql-config.yml

name: "Security and Quality"

queries:
  - uses: security-and-quality

そして、それを参照するように GitHub Actions の定義を作ります

.github/workflows/codeql-analysis.yml

---
name: CodeQL

on: [pull_request]

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: ['javascript']

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v2
        with:
          languages: ${{ matrix.language }}
          config-file: ./.github/codeql/codeql-config.yml
      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v2

GitHub Actions の実行

プルリクエストすると、 CodeQL が動きます

actions.png

チェック項目の詳細

自分で QL を習得してクエリを書くことも可能ですが、一般的な脆弱性については既に定義が作られているため、これを使えば簡単にセキュリティチェックができます

例えば、 JavaScript のセキュリティ関連のチェック定義は以下の場所に CWE (共通脆弱性タイプ)毎に別れて格納されています

queries.png

CWE-89 の SQL インジェクションは以下のパスに定義されています

javascript/ql/src/Security/CWE-089/SqlInjection.ql

クエリを一つ一つ指定することもできますが、チェック項目は大量にあるため、一括指定できるようにクエリスイートとしてまとめられています

javascript/ql/src/codeql-suites

suites.png

例えば、 javascript-security-and-quality.qls は以下のように定義されています

- description: Security-and-quality queries for JavaScript
- qlpack: codeql-javascript
- apply: security-and-quality-selectors.yml
  from: codeql/suite-helpers

で、 selectors は以下のパスで定義されています

misc/suite-helpers

security-and-quality-selectors.yml

- description: Selectors for selecting the security-and-quality queries for a language
- include:
    kind:
    - problem
    - path-problem
    precision:
    - high
    - very-high
- include:
    kind:
    - problem
    - path-problem
    precision: medium
    problem.severity:
      - error
      - warning
- include:
    kind:
    - diagnostic
- include:
    kind:
    - metric
    tags contain:
    - summary
- exclude:
    deprecated: //
- exclude:
    query path: /^experimental\/.*/

先程の SQL インジェクションの定義の先頭コメントを見ると以下のようになっていて、 selectors はここを見て対象を集めていることが分かります

/**
 * @name Database query built from user-controlled sources
 * @description Building a database query from user-controlled sources is vulnerable to insertion of
 *              malicious code by the user.
 * @kind path-problem
 * @problem.severity error
 * @security-severity 8.8
 * @precision high
 * @id js/sql-injection
 * @tags security
 *       external/cwe/cwe-089
 */

つまり、 javascript-security-and-quality.qls を指定すると、 javascript/ql/src/ 配下の以下のいずれかの条件を満たすクエリが実行される、ということです

  • kindproblem または path-problem かつ、 precisionhigh または very-high
  • kindproblem または path-problem かつ、 precisionmedium かつ、 problem.severityerror または warning
  • kinddiagnostic
  • kindmetric かつ、 tagssummary を含む
  • ただし、 deprecated (非推奨)ではなく、パスに experimental (実験的)を含まない

SQL インジェクション は kindpath-problemprecisionhigh なので実行されます

実行時のログには以下のように実行したクエリが表示されます

[170/172 eval 2s] Evaluation done; writing results to codeql-javascript/Security/CWE-200/FileAccessToHttp.bqrs.
[171/172 eval 1.4s] Evaluation done; writing results to codeql-javascript/Security/CWE-400/RemotePropertyInjection.bqrs.
[172/172 eval 2.8s] Evaluation done; writing results to codeql-javascript/Security/CWE-400/RemotePropertyInjection.bqrs.

まとめ

CodeQL を使うことで、コードの単なる形式チェックだけでなく、脆弱性までチェックすることができました

Public リポジトリーであれば GitHub Actions で無償利用できるため、どんどん使っていきましょう

また、 Docker を使えばローカルでも簡単に実行できるので、既存のコードなどにもどんどん使ってみましょう

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4