2022年11月21日 - Tomo Masakura    

SASTで脆弱性を発見しよう!

静的検査による脆弱性検出のGitLab SASTの有効化から、SASTの仕組みや設定方法などを解説します。

GitLabのStatic Application Security Testing (SAST)はソースコードやそれをコンパイルしたオブジェクトファイルを解析し、脆弱性の疑いのあるコードを報告します。わずか数分で使い始められますので、積極的に活用し、あなたのアプリケーションの脆弱性を少しでも取り除きましょう!

SASTを使ってみる

SASTは簡単に使い始められますので、説明を読むよりも使ってみることをおすすめします。まずは、SASTを試すための新しいGitLabプロジェクトを作成します。

プロジェクト名などは適当に埋めた上でプロジェクトを作成ボタンをクリックします。このとき、Enable Static Application Security Testing (SAST)には絶対にチェックを入れないでください。

SASTお試し用のプロジェクトの作成

次にSASTを有効にします。左サイドメニューからCI/CD -> Editorを開き、Configure pipelineボタンをクリックします。表示されているコードをすべて削除し、次のコードを入力し、ページの下にあるCommit changesボタンをクリックしてコミットします。

include:
  - template: Security/SAST.gitlab-ci.yml

SASTの有効化

SASTの有効化はたったこれだけですが、検査対象のソースコードが何もありませんので、検査は行われません。脆弱性のあるソースコードを追加して、SASTを再度実行します。

プロジェクトのトップページへ移動し、ページの中央あたりにある+ボタンをクリック後、新規ファイルをクリックし、次のコードインジェクションの脆弱性のあるコードを貼り付けます。ファイル名はsample.jsとします。

function run(code) {
  eval(code);
}

Commit changesボタンをクリックして、ファイルをコミットします。

左サイドメニューからCI/CD ->パイプラインを開くと、SASTのGitLab CIジョブが実行された(もしくは実行されている)のが確認できます。

SASTのGitLab CIパイプラインの実行結果

最新のパイプライン行の右にあるダウンロードボタンをクリックしたら表示されるsemgrep-sast-:sastをクリックして、脆弱性レポートをダウンロードします。

SASTレポートのダウンロード

レポートはSAST独自のJSON形式です。次のようにsample.jsの二行目にコードインジェクションの脆弱性があると指摘しています。(そのままだと読みにくいので抜粋した上で整形しています)

// ...
{
  "id": "9a304751e66ff817b1cafa82312bf91959f790484719cb2e0867308ce78c5545",
  "category": "sast",
  "message": "Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')",
  "description": "Detected eval(variable), which could allow a malicious actor to run arbitrary code.\n",
  "cve": "",
  "severity": "Medium",
  "scanner": { "id": "semgrep", "name": "Semgrep" },
  "location": { "file": "sample.js", "start_line": 2 }
  // ...

Ultimateプランでは、このレポートをマージリクエストで確認したり、脆弱性の指摘をセキュリティダッシュボードや脆弱性レポートで管理したりすることができます。詳しくは公式ドキュメントのStatic Application Security Testing (SAST)をご覧ください。

Ultimateの

SASTは使えるのか?

SASTはあらゆる脆弱性を検出できるわけではありません。どれくらい検出できるのか気になる方も多いと思いますが、むしろ検出できないことの方が多いのです。

SQLインジェクションなどのインジェクション系の脆弱性の検出は得意ですが、OWASP Top 10:2021のトップのアクセス制御の不備の検出は得意ではありません。

得意とするインジェクション系の脆弱性も、あなたが開発しているアプリケーションが利用しているフレームワークにSASTが対応していなければ検出できません。静的解析はパターン検出ですので、コードの書き方次第ではパターンから外れてしまい検出できません。

とはいえ、.gitlab-ci.ymlファイルにたった二行書くだけで、一つでも脆弱性が発見できれば儲けものと考えることができます。少しかもしれませんが、レビュアーの負担を軽減できます。

SASTには絶大な効果はありませんが、導入が簡単なため、費用対効果はそれなりに高く、活用しない手はありません。

SASTが対応している言語やフレームワークをプロジェクトで使っているなら、SASTを有効にすることをおすすめします。

SASTの詳細

SASTによる脆弱性の静的解析はSAST analyzerと呼ばれるプログラムによって行われています。SAST analyzerは、リポジトリにコードがプッシュされるたびに、GitLab CIジョブで静的検査を実行し、結果をレポートファイルgl-sast-report.jsonに出力します。このレポートファイルはGitLabのSASTレポートとしてアップロードされ、プロジェクトメンバーが確認できるようになります。

SAST analyzerは、.NET用セキュリティスキャナーのSecurity Code Scanを利用したsecurity-code-scan .NET analyzerや、Java系用セキュリティスキャナーのFind Security Bugsを利用したspotbugs analyzerなど、それぞれカバーしている言語やフレームワークが異なるものがいくつか提供されています。

すべてのSAST analyzerが実行されるわけではなく、リポジトリにあるソースコードファイルの言語やフレームワークを自動判別し、適切なSAST analyzerが使われます。

対応している言語やフレームワークは、公式ドキュメントのSupported languages and frameworks をご覧ください。

オンプレミスのGitLab

SASTはGitLab CIで動作しますので、Docker executorなGitLab Runnerを必要とします。

GitLab.comはあらかじめ用意されていますので、事前の準備は不要です。プロジェクトリーダーによってGitLab Runnerが無効化されていることもありますので、うまく動かなければプロジェクトリーダーにお問い合わせください。

オンプレミスのGitLabではGitLab Runnerが別途必要です。GitLab Runnerが用意されている場合もありますので、まずは試してみて、うまく動かなければプロジェクトリーダーやGitLabの管理者にお問い合わせください。

SASTを設定する

SAST全体の設定は次のようにグローバルセクションのvariablesに値を設定します。

variables:
  SAST_EXCLUDED_ANALYZERS: "semgrep" # Semgrep Analyzerを実行しません。
  SAST_DISABLED: 1 # すべてのSAST Analyzerを実行しません。

SASTの設定を上書きするためには、次のように規定のSASTジョブ名のジョブを追加し、変数を上書きします。

include:
  - template: Security/SAST.gitlab-ci.yml

spotbugs-sast:
  variables:
    FAIL_NEVER: 1

細かく上書きする代わりに、ジョブテンプレートを利用しないでSASTを実行することもできます。

my-semgpre-sast:
  stage: test
  image: "$CI_TEMPLATE_REGISTRY_HOST/security-products/semgrep:3"
  variables:
    SEARCH_MAX_DEPTH: 20
  allow_failure: true
  script:
    - /analyzer run
  artifacts:
    reports:
      sast: gl-sast-report.json

より細かい設定をするために、.gitlab/sast-ruleset.tomlファイルを利用できます。

  • 検出ルールの個別の無効化
  • 検出ルールを個別に上書きする
  • SAST analyzerが利用しているセキュリティスキャナーへ設定を渡す

詳しくは公式ドキュメントのCustomize rulesetsをご覧ください。

脆弱性の指摘を無視する

静的解析ツールによる指摘は完璧ではなく、未検出や誤検出が少なくありません。誤検出は次回以降の検査結果にあっても無視する必要がありますが、これを記憶やメモに頼るのは現実的ではありません。

このような、誤検出を無視する場合、ツールの無視機能を使います。SASTでの無視方法は次のとおりです。

  • セキュリティダッシュボードを利用する(Ultimateのみ)
  • .gitlab/sast-ruleset.tomlファイルで検査ルールを無効化する
  • セキュリティスキャナーの機能を使う

.gitlab/sast-ruleset.tomlファイルで、特定の検査ルール(例:コードインジェクション)だけ無効化することができます。しかし、この方法では特定の指摘だけを無視することはできないことに注意してください。

セキュリティスキャナーには検査ルールを無視したり特定の行だけ検査から除外する機能があり、これを利用することもできます。セキュリティスキャナーごとに方法は異なりますが、柔軟な設定が可能です。

例えば、Semgrep analyzerはセキュリティスキャナーにSemgrepを利用していますので、Semgrepの方法が利用できます。

function run(code) {
  eval(code); // nosemgrep: eslint.detect-eval-with-expression
}

SAST analyzerごとの無視方法は、後ほどいくつかのSAST analyzerを紹介しますので、そちらであらためて紹介します。

SAST以外のGitLabのセキュリティ機能

GitLabではSAST以外にもセキュリティ機能が提供されています。Ultimateプラン限定のものも多いですが、SASTでは検出できない脆弱性もこれらの機能で検出できるかもしれませんので、ぜひご検討ください。

SAST analyzerの紹介

このブログを書くために調査したSAST analyzerを紹介します。

公式のSAST analyzerの一覧はSAST analyzersをご覧ください。

Semgrep analyzer

セキュリティスキャナーの仕様や動作は各製品ごとに大きく異なります。これらをGitLabのSASTとして同じように機能するようまとめ上げるのは大変なようで、GitLabは複数の言語やフレームワークに対応した新しいSAST analyzerの開発に着手しました。

新しいSAST analyzerはSemgrep analyzerと呼ばれ、名前の通り静的解析ツールにSemgrepを利用しています。

Semgrepは様々なプログラム言語に対応したオープンソースソフトウェアのソースコード静的解析ツールです。標準で脆弱性検査の検査ルールも用意されていますが、Semgrep analyzerではこれらを利用せず、GitLabが独自に開発した検査ルールを利用しています。

検査ルールは、Security Code Scan analyzerなどの他のSAST analyzerが検出できる脆弱性と同じものが検出できるように移植されています。検査ルールの移植が完了した言語から順次Semgrep analyzerに切り替えられています。

Supported languages and frameworksのリストでScan toolSemgrepとなっている言語やフレームワークに対応しています。

検出できる脆弱性はrulesをご覧ください。

Semgrepの仕様やGitLabの方針により、元になったSAST analyzerから移植できなかった(移植しなかった)検査ルールもあります。移植できた検査ルールでも、検出率や誤検出の面で劣る傾向があります。

Semgrep analyzerで脆弱性の指摘を無視するには次のようにします。

function run(code) {
  eval(code); // nosemgrep: eslint.detect-eval-with-expression
}

特定のファイルやディレクトリ全体を無視するには.semgrepignoreにファイル名やディレクトリ名を列挙します。

詳しくはSemgrep公式ドキュメントのIgnoring files, folders, or parts of codeをご覧ください。

security-code-scan .NET analyzer (.NET用)

security-code-scan .NET analyzerは.NET用の静的解析セキュリティスキャナーのSecurity Code Scanを利用した.NET用のSAST analyzerです。C#だけでなく、Visual Basicにも対応しています。

検出できる脆弱性はSecurity Code Scan公式ドキュメントのRulesをご覧ください。ただし、.NET Frameworkへの対応は不完全です。これはこのSAST analyzerがLinuxで動作しているためです。

Security Code Scanはソースコードをコンパイルしたオブジェクトファイルを解析します。コンパイルできない場合は検査できないことに注意してください。

Security Code Scanの他にない特徴として、コードやクラスを深く追跡し、ASP.NETのエンドポイント(API / WebForms / MVC など)から呼び出され、汚染されたデータが混入する場合のみ脆弱性として検出するというものがあります。詳しくはSecurity Code Scan のちょっとすごいところをご覧ください。

このSAST analyzerで検出できる脆弱性はSemgrep analyzerにも移植されています。GitLab15.4から、C#の場合のみSemgrep AnalyzerとSecurity Code Scan Analyzerの両方が実行されるようになりました。

このSAST analyzerの検査ルールはSemgrep analyzerに移植されていますが、次の違いがあります。

  • コードを追跡しません。一般的なセキュリティスキャナーと同様に、実行されるかや汚染されたデータが混入されたかを確認せずに脆弱性を報告します。
  • Visual Basicには非対応です。
  • .NET Frameworkでも.NETでも同じレベルの検出をします。.NET Frameworkにしかない機能には非対応です。
  • Semgrepの仕様やGitLabの方針により移植されていないルールがあります。

このSAST analyzerで脆弱性の指摘を無視するには次のようにします。

    [HttpPost]
#pragma warning disable SCS0016
    public void Post() {}
#pragma warning restore SCS0016

詳しくはMicrosoft公式ドキュメントのコード分析の警告を抑制する方法をご覧ください。

ESLint analyzer (非推奨 / JavaScript / TypeScript 用)

ESLint analyzerはJavaScriptやTypeScript用の静的解析のセキュリティースキャナーのESLint security plugineslint-plugin-reactを利用したAnalyzerです。

検出できる脆弱性はRules (ESLint security plugin)List of supported rules (eslint-plugin-react)をご覧ください。ただし、eslint-plugin-reactは脆弱性だけに絞っています。

  • このSAST AnalyzerはHTML埋め込みJavaScriptに対応していますが、Semgrep analyzerは対応していません。
  • Semgrepの仕様やGitLabの方針により、移植されていないルールがあります。

GitLab 15.3より、このSAST analyzerは非推奨となり、デフォルトで実行されなくなりました。Semgrep analyzerで検出できない脆弱性を検出したい場合はマニュアルで実行できます。しかし、検出ルールが古いままなことにご注意ください。

検出された脆弱性を無視するには次のようにします。

function runCode(code) {
  // eslint-disable-next-line security/detect-eval-with-expression
  eval(code);
}

詳しくはESLint公式ドキュメントのDisabling Rulesをご覧ください。

NodeJsScan analyzer (Node.js Express)

NodeJsScan analyzerは静的解析のセキュリティースキャナーにnjsscanを利用したAnalyzerです。検出できる脆弱性はrulesをご覧ください。

njssscanはウェブアプリケーションのフレームワークのExpressを利用したアプリケーションの脆弱性を検出します。

  • Express middlewareの脆弱性を検出します。逆に、middleware以外に書いたコードの脆弱性は検出しません。
  • EJBなどのExpressで使われているテンプレートエンジン各種に対応しています。

このSAST analyzerの検査ルールはまだSemgrep analyzerに移植されていません。

検出された脆弱性を無視するには次のようにします。

function a1(req, res) {
  eval(req.params.command); // njsscan-ignore: eval_nodejs
}

詳しくはSuppress Findingsをご覧ください。 また、設定ファイル.njsscanで、無視するディレクトリやファイルなどを列挙できます。

spotbugs analyzer (Java / Kotlin / etc…)

spotbugs analyzerはJava系言語用の静的解析ツールの SpotBugsとそのプラグインでセキュリティスキャナーのFind Security Bugsを利用しています。SpotBugs自体には標準の検査ルールがありますが、これらは脆弱性の検出用ではありませんので使われていません。Find Security Bugsの検査ルールだけが利用されています。検出できる脆弱性はBug Patternsをご覧ください。

Javaだけでなく、KotlinやScalaやGroovyにも対応しています。Find Security Bugs自体はJSPもサポートしていますが、GitLab SASTでは動作しないようです。

SpotBugsもSecurity Code Scanと同様にコンパイルされたオブジェクトファイルを解析します。しかし、Security Code Scanほど細かくコードを追いかけたりはしないようです。

なお、JavaはSemgrep analyzerの利用が推奨されていて、spotbugs analyzerは起動しないようになっています。それ以外のKotlinなどの言語ではspotbugs analyzerが起動します。

Semgrepの仕様やGitLabの方針で、Semgrep analyzerに移植されていないルールがあります。これらのSemgrep analyzerで検出できないものを検出したい場合は、spotbugs analyzerをマニュアルで起動する必要があります。

検出された脆弱性を無視するには次のようにします。

@SuppressFBWarnings({"COMMAND_INJECTION"})
public static void run(String command) throws IOException {
    Runtime runtime = Runtime.getRuntime();
    runtime.exec(command);
}

事前コンパイルで高速化

security-code-scan .NET analyzerやspotbugs analyzerはソースコードではなく、それをコンパイルしたオブジェクトファイルを解析します。コンパイルが必要な分、ASTを利用した他のSAST analyzerと比べると検査に時間がかかります。

ほとんどの場合、GitLabでコンパイルジョブが別途動作していると思います。このコンパイルされたオブジェクトをファイルを利用し、SASTジョブの実行時間を減らすことができます。

詳しくはPre-compilationをご覧ください。

まとめ

GitLabのStatic Application Security Testing (SAST)はソースコード(もしくはコンパイルされたオブジェクトファイル)を静的解析し、脆弱性を検査します。うまく検出できないことの方が多いですが、数分もあればあなたのGitLabプロジェクトでSASTを有効にできます。少しでも脆弱性を発見し修正できれば、レビュアーの負担軽減に繋がります。

Ultimateプラン以外ではGitLabのWeb画面で脆弱性を確認することはできませんが、脆弱性レポートをダウンロードして確認できるだけでも十分に役に立ちます。

あなたのプロジェクトでSASTを有効にして、脆弱性を発見し、取り除いていきましょう!

Gitlab x icon svg