脆弱性のリリースを防ぐ:SemgrepによるSASTの自動化

Security tutorial - IT technology blog
Security tutorial - IT technology blog

深夜2時の緊急呼び出し

かつて私は、本番サーバーへのブルートフォース攻撃を阻止するために、必死になって早早朝を過ごしたことがあります。その経験から私の考え方は変わりました。リアクティブな(後手に回る)セキュリティは負け戦です。アラートが鳴るのを待っているようでは、すでに手遅れなのです。

コードそのものに防御策を組み込む必要があります。NIST(米国国立標準技術研究所)のデータによると、本番環境で脆弱性を修正するコストは、開発中に見つける場合よりも30倍から100倍も高くなると言われています。小規模なチームにとって、それは軽微なパッチで済むか、1週間の生産性を失うかの大きな違いとなります。

私は、開発チームが納期に追われるあまり、誤ってAWSのシークレットキーやSQLインジェクションの脆弱性をプッシュしてしまうのを何度も見てきました。手動のコードレビューは不可欠ですが、人間は間違いを犯すものです。特に金曜日の午後4時ならなおさらです。そこで Static Application Security Testing (SAST) が役立ちます。メインブランチにマージされる前に、これらの脆弱性の混入を食い止めるために最も効果的なツールはSemgrepであると私は確信しています。

なぜ欠陥は見逃されるのか

セキュリティ負債は通常、怠慢の結果ではありません。チェックよりも開発環境の変化が速すぎるために発生します。私は、ほとんどの漏洩を引き起こす3つの具体的なボトルネックを特定しました。

  • 主観的なレビュー: ある開発者は入力バリデーションの不備に気づくかもしれませんが、別の開発者は命名規則しかチェックしないかもしれません。自動化がなければ、セキュリティのレベルはプルリクエスト(PR)を誰がレビューするかに完全に依存してしまいます。
  • フレームワークの盲点: モダンなスタックは複雑です。Reactの dangerouslySetInnerHTML やDjangoの mark_safe のような特定のメソッドが、クロスサイトスクリプティング(XSS)への直接的な経路を作ってしまうことを忘れがちです。
  • 「リリース直前」のボトルネック: ステージング環境でのみ重いスキャンを実行すると、リリース直前に大量のバグが積み上がることになります。これではセキュリティが機能ではなく、単なる障害物になってしまいます。

フィードバックループの問題

開発者は500ページもあるPDFレポートなど必要としていません。レガシーなセキュリティツールは「ブラックボックス」のように機能し、実行に3時間かかった挙挙句、40%もの誤検知を返すことがよくあります。「狼少年」のようなツールをエンジニアは信頼しなくなります。効果を発揮するためには、セキュリティのフィードバックは迅速かつ透明で、GitHub、GitLab、Bitbucketなどのコードが存在する場所で提供される必要があります。

Semgrepとレガシー・ツールの比較

私はSemgrepに決める前に、いくつかのスキャナーをテストしました。多くの伝統的なツールはコードを単なるテキストとして扱いますが、Semgrepは抽象構文木(AST)を見ることでコードの構造を理解します。これにより、たとえ変数の定義が数十行離れていても、exec(user_input) がリスクであることを認識できます。

機能 手動レビュー レガシーSAST (例: SonarQube) Semgrep (モダンSAST)
スキャン速度 数日 20分以上 2分未満
ルール言語 人間の脳 独自形式 / Java シンプルなYAML
ノイズレベル 中程度 高い(誤検知が多い) 低い(高精度)
セットアップ時間 即時 数時間〜数日 5分

Semgrepは、10万行のコードに対して1,000以上のセキュリティルールを数秒で処理できます。このスピードにより、夜間のビルドだけでなく、すべてのコミットに対して実行することが可能になります。

GitHub ActionsへのSemgrepの統合

目標は、Semgrepを「ゲートキーパー」にすることです。PRに重大な欠陥が含まれている場合、ビルドを即座に失敗させるべきです。以下は、私が本番環境で使用しているワークフローです。

1. ローカルテスト

CIランナーを待つよりも、自分のマシンでエラーを見つける方が速いです。HomebrewやPipでSemgrepをインストールして始めましょう。

# pip経由でインストール
python3 -m pip install semgrep

# コミュニティで検証済みのルールを使用してスキャンを実行
semgrep scan --config auto

--config auto フラグは非常に便利です。プロジェクトの言語を自動的に検出し、Semgrep Registryから関連するセキュリティポリシーをプルします。

2. GitHub Actionsのワークフロー

この設定を .github/workflows/semgrep.yml に配置してください。これにより、すべてのプルリクエストでトリガーされ、main ブランチに新しい脆弱性が混入するのを防ぎます。

name: セキュリティスキャン

on:
  pull_request:
    branches: ["main"]
  push:
    branches: ["main"]

jobs:
  semgrep_scan:
    runs-on: ubuntu-latest
    container:
      image: returntocorp/semgrep
    steps:
      - name: コードをチェックアウト
        uses: actions/checkout@v4

      - name: Semgrepを実行
        run: semgrep scan --config p/default --error

      - name: セキュリティレポートを生成
        run: semgrep scan --config p/default --sarif --output=semgrep.sarif

      - name: GitHubセキュリティタブにアップロード
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: semgrep.sarif
        if: always()

--error フラグはこのスクリプトで最も重要な部分です。重大度の高い問題が見つかった場合にゼロ以外の終了コードを返し、コードが修正されるまでマージを効果的にブロックします。

チームの標準に合わせたカスタムルール

どのチームにも特定の「禁止事項」があるはずです。例えば、衝突攻撃の恐れがあるためMD5ハッシュを禁止したい、あるいは特定の内部ライブラリの誤用を防ぎたいといったケースです。

semgrep-rules.yaml を作成します:

rules:
  - id: ban-insecure-hashing
    patterns:
      - pattern: hashlib.md5(...)
    message: "MD5は安全ではありません。すべてのハッシュ化にはSHA-256を使用してください。"
    languages: [python]
    severity: ERROR

これにより、内部のセキュリティポリシーが、ルールを決して忘れない実行可能なコードへと変わります。

誤検知の管理

セキュリティツールは予言者ではありません。フラグが立てられた exec() 呼び出しが、特定の管理タスクにおいて真に必要である場合もあります。スキャナーを無効にするのではなく、局所的な無視設定を使用して監査証跡を維持しましょう。

# nosemgrep: python.lang.security.audit.dangerous-exec
exec(internal_config_string) # 起動プロセス中に検証済み

私は常に、# nosemgrep タグを付ける際には理由を説明するコメントを添えるようチームに徹底しています。これにより、将来のセキュリティ監査が大幅にスムーズになります。

セキュリティ文化の構築

Semgrepは単なるバグハンターではありません。教育ツールでもあります。開発者がPR内でセキュリティ警告を目にすることで、リアルタイムで脆弱性について学ぶことができます。これにより、継続的なフィードバックループが生まれ、チームのコーディングスキルが向上します。この24時間365日の自動ガードを導入して以来、ステージング環境に到達する重大な脆弱性が大幅に減少し、ようやく私は夜ぐっすり眠れるようになりました。

Share: