はじめに
2023/9/1 更新
CodeQL は静的解析ツールの一種で、コードのセキュリティチェックを行います
pre-commit や Super-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)に格納されているので、参考にしてください
悪いコードがある状態で実行すると、 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"
実行の簡易化
コードを変更して再実行する場合、毎回データベースを一旦削除して再作成しなければなりません
面倒なのでスクリプトにしてしまいましょう
#!/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 のスクリプトにしてしまいましょう
"scripts": {
"codeql": "codeql/run.sh"
},
これで、以下のようにすれば簡単に CodeQL が実行できるようになりました
npm run codeql
GitHub Actions で実行
GitHub 公式が用意してくれています
自分で定義ファイルを用意しても良いですし、
リポジトリーの設定から Security & analysis -> Code scanning の Set up をクリックしてブラウザから簡単に設定することも可能です
デフォルトの設定だと保守性等のチェックはしてくれないので、今回はクエリスイートを 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 が動きます
チェック項目の詳細
自分で QL を習得してクエリを書くことも可能ですが、一般的な脆弱性については既に定義が作られているため、これを使えば簡単にセキュリティチェックができます
例えば、 JavaScript のセキュリティ関連のチェック定義は以下の場所に CWE (共通脆弱性タイプ)毎に別れて格納されています
CWE-89 の SQL インジェクションは以下のパスに定義されています
javascript/ql/src/Security/CWE-089/SqlInjection.ql
クエリを一つ一つ指定することもできますが、チェック項目は大量にあるため、一括指定できるようにクエリスイートとしてまとめられています
javascript/ql/src/codeql-suites
例えば、 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 は以下のパスで定義されています
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/
配下の以下のいずれかの条件を満たすクエリが実行される、ということです
-
kind
がproblem
またはpath-problem
かつ、precision
がhigh
またはvery-high
-
kind
がproblem
またはpath-problem
かつ、precision
がmedium
かつ、problem.severity
がerror
またはwarning
-
kind
がdiagnostic
-
kind
がmetric
かつ、tags
にsummary
を含む - ただし、
deprecated
(非推奨)ではなく、パスにexperimental
(実験的)を含まない
SQL インジェクション は kind
が path-problem
で precision
が high
なので実行されます
実行時のログには以下のように実行したクエリが表示されます
[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 を使えばローカルでも簡単に実行できるので、既存のコードなどにもどんどん使ってみましょう