
はじめに
こんにちは、情報セキュリティ部の兵藤です。日々ZOZOの安全を守るためSOC業務に取り組んでいます。
ZOZOではGitHub Advanced Securityを導入、運用しております。本記事では、GitHub Advanced Securityに関する取り組みについて紹介します。
また、情報セキュリティ部ではその他にもZOZOを守るための取り組みを行っています。詳細については以下の「OpenCTIをSentinelに食わせてみた」をご覧ください。
目次
GitHub Advanced Securityとは
GitHub Advanced Security(以下、GHAS)は、コードのセキュリティを向上させる複数の機能が含まれており、主な機能は以下の3つがあります。
- Code scanning: コードの脆弱性を検出するための静的解析の機能
- Secret scanning: コード内に含まれるシークレット(APIキーやパスワードなど)を検出する機能
- Dependabot: ライブラリとフレームワークの既知の脆弱性を含む依存関係を検出する機能
パブリックリポジトリにおいては、GHASの機能は無料で利用可能ですが、プライベートリポジトリにおいてはCode scanningやSecret scanningの機能は有料となります。Enterpriseプランではアクティブコミッター数に応じて課金されます。詳しくは公式ドキュメントをご覧ください。
GitHub Advanced Securityの導入の目的
ZOZOではZOZOTOWNをはじめとし、WEAR, FANNS, ZOZOMETRYなど自社開発のサービスを多数運用しています。これらのサービスにおけるコードのセキュリティ向上のために、GHASを導入しました。具体的な目的は以下の通りです。
- コードの脆弱性の早期発見: Code scanningを利用してコードの脆弱性を開発の段階で早期に発見し、修正することで、セキュリティリスクを低減する。
- シークレットの漏洩防止: Secret scanningを利用してコード内に含まれるシークレットを検出し、漏洩を防止する。
- リポジトリのセキュリティ見える化: Organizationのセキュリティダッシュボードを利用して、リポジトリのセキュリティ状況を可視化する。
これらを目的にGHASを導入、運用する過程でどのようにGHASを調整、活用していったかを紹介していきます。
導入の際に実施したこと
CodeQLのAdvanced Setup設定
ZOZOではGHASの機能をOrganization全体で有効化しています。Settingsの「Security configurations」から設定ができます。Code scanningの設定のうち「CodeQL analysis」については基本的にDefaultの設定(以降、AutoBuild)を配ることになります。
このCode scanningは実態としてはGitHub Actionsでcodeql-actionを動かしており、別のActionsに組み込むことも可能です。
初期のAutoBuildの設定では一部AndroidやiOS、Cなどのビルドを行うようなコードを扱うリポジトリにおいて、Code scanningの有効化に失敗する場合があります。
理由は様々ですが、ビルドにおいて他のリポジトリを参照している、要求ライブラリやソフトのバージョンがシビア、Actionsのそもそものリソースがデフォルトのものでは足りない場合などが考えられます。そのためCodeQLのAdvanced Setupを設定し、個々のリポジトリにおいてCodeQLのビルドを行うように設定しました。
以下のようにCodeQL analysisからAdvanced Setupを選択できます。

YAMLが出力されるので、リポジトリごとのBuildに必要な設定を普通のActionsのように編集します。例えば、以下のように設定することで、Advanced Setupを実施できます。
jobs: analyze: name: Analyze (${{ matrix.language }}) runs-on: my-larger-runner # 実行したい環境を指定 timeout-minutes: 360 strategy: fail-fast: false matrix: include: - language: java-kotlin build-mode: manual # manualに変更して、カスタムしたビルドを行う - language: ruby build-mode: none - if: matrix.build-mode == 'manual' name: Build with Gradle # manualでのBuild方法を記載 run: | ./gradlew assembleDebug
これらの設定は個々のリポジトリごとに設定する必要があったため、プロダクトのエンジニアの協力を得て、CodeQLが有効化されていないリポジトリに対して、Advanced Setupの設定をしました。
ある程度ビルドが失敗する原因、グループが絞れている場合はこちらのドキュメントのようにスクリプトを利用して一括設定も可能です。
コンテナスキャンの設定
GHASの機能にはコンテナイメージに存在するライブラリやOSの脆弱性を検出する機能は特段ありません。
そのため、TrivyやAWSのECRのコンテナスキャンなどのツールを利用してコンテナイメージの脆弱性を検出するなど代替案を検討する必要がありました。
基本的にこれらのコンテナスキャンの設定は各プロダクトで管理しています。このコンテナスキャンの結果はCodeQLで検出した結果と同じようにGitHubのSecurityタブで確認できるため、社内にコンテナスキャンを行うActionsを展開しました。以下はTrivyを利用したコンテナスキャンの結果をGHASのSecurityタブにアップロードするActionsの一例です。
jobs: scan-container: runs-on: ubuntu-latest timeout-minutes: 300 steps: # Checkout code - name: "Checkout repository" uses: actions/checkout@v4 #名称決定 - name: "Prepare" id: prep run: | REPO_NAME=$(echo "${{ github.repository }}" | tr "[:upper:]" "[:lower:]") echo "repo_name=${REPO_NAME}" >> $GITHUB_OUTPUT # コンテナビルド - name: "Build Container" run: docker build -t ghcr.io/${{ steps.prep.outputs.repo_name }}:scan-tmp ./ # 脆弱性スキャン - name: "Scan Container" uses: aquasecurity/trivy-action@master with: image-ref: "ghcr.io/${{ steps.prep.outputs.repo_name }}:scan-tmp" format: "sarif" limit-severities-for-sarif: true exit-code: "0" vuln-type: "os,library" severity: "CRITICAL" trivyignores: ".github/workflows/trivy/.trivyignore" output: "trivy-results.sarif" # GHASへのアップロード - name: "Upload Trivy scan results to GitHub Security tab" uses: github/codeql-action/upload-sarif@v3 with: sarif_file: "trivy-results.sarif"
GHASはsarif形式のファイルアップロードをサポートしておりこの形式で出力すれば、コンテナスキャンの結果もGHASのSecurityタブで確認できます。この例ではlimit-severities-for-sarifをtrueにすることで、severityがCriticalの脆弱性のみを検出し、GHASにアップロードしています。
CodeQLの検知検証
CodeQLには様々な検知ルール(query)が用意されており、コードの脆弱性をそのqueryに基づいて検出します。Organization全体でCode scanningを有効化すると、defaultのqueryスイートが適用されます。security-extendedのqueryスイートについては各リポジトリで適用できます。
ZOZOではこの検出機能を検証するために脆弱なRuby, JavaScriptのアプリを用意しました。その際の所感をここでは紹介します。
CodeQLの得意な検知
SQLインジェクションやOSコマンドインジェクションはもちろん、プロトタイプ汚染まで検知してくれます。基本的にユーザーの入力が行われるところで発火するものは漏れなく検知してくれる印象です。
その中でもSSRFなど、一般的な診断ツールで検知が難しいものを漏れなく拾ってくれる点は非常に良いと思いました。
このSSRFの脆弱性は環境ごとに影響度合いが変わるためトリアージは難しいです。例えばAWSの場合はIMDSv2のtokenが奪取された状況、Azureの場合はManaged IdentityのIDENTITY_HEADERが奪取された状況では被害が拡大し、無視できないものです。
実際に検知されたAlertについての対応は後ほど記載いたします。
CodeQLの不得意な検知
一度DBなどに格納された値など、Storedされた値を出力する際に発火するパストラバーサルやXSSなどの検知は難しい印象を受けました。
Storedされた情報を元に検知するqueryは言語によってバラつきがあるようで、パストラバーサルに関してはまだRubyのqueryでは実装されていないようでした。Javaでは実装されていそうなので、今後Rubyでも検知できそうです。
その他にもアクセス制御不備の脆弱性など、そもそもSASTでの検知が難しいであろう部分もありました。
運用の際に実施したこと
GHASのAlertの通知設定
GHASではCode scanningやSecret scanningで検出された脆弱性やシークレットの漏洩について、Alertが発生します。これらのAlertはGitHubのWeb UI上で確認できますが、Alertの発生をSlackや外部SIEMサービスなどの外部サービスに送信できます。
SOCではGHASから発生するAlertについて監視し、重大なAlertに対して早急に対応する体制を構築しました。具体的にはSOCで利用しているSentinelにGitHubのWebhookを介してAlertを送信します。ドキュメントに従ってapplication/json形式で設定します。ペイロードURLに関しては、Sentinelのコネクタで作成されるAzure FunctionsのURLを設定します。以下のコネクターです。
作成されたFunctionの「関数のURLの取得」から「ファンクション キー」のURLを設定すればログがSentinelに格納されます。
githubscanaudit_CLのテーブルにdependabot_alert、secret_scanning_alert、repository_vulnerability_alertのeventがログとしてJSON形式で格納されます。これらのログを元にKQLの分析ルールを作成し、重大なAlertを抽出、通知します。
以下はCode scanningのAlertを抽出するKQLの一例です。
githubscanaudit_CL
| where event_s == "code_scanning_alert" and action_s == "created"
| extend alert = parse_json(alert_s)
| extend
scan_url = alert.html_url,
path = alert.most_recent_instance.location.path,
severity = alert.rule.security_severity_level,
description = alert.rule.description,
message = alert.most_recent_instance.message.text
| where severity == 'critical'
| project TimeGenerated, scan_url, path, severity, description, message, alert
Code scanningのCriticalなAlertを抽出し、scan_urlやpath、severity、description、messageを表示しています。scan_urlはGitHubのWeb UI上でAlertの詳細を確認するためのURLで、こちらでコード上の脆弱な箇所を確認できます。
Secret scanningでのAlertの抽出も似たようなKQLで抽出できます。以下はSecret scanningのAlertを抽出するKQLの一例です。
githubscanaudit_CL
| where event_s == "secret_scanning_alert" and action_s == "created"
| extend alert = parse_json(alert_s)
| extend
scan_url = alert.html_url,
secret_type = alert.secret_type_display_name,
validity = alert.validity
| where validity == "active" or validity == "unknown"
| project TimeGenerated, secret_type, scan_url, validity, alert
Secret scanningのAlertではシークレットの有効性を示すvalidityがあり、activeやunknownのものを抽出しています。トリアージを行う際にこの値は指標となるので、有効化しておくと良いでしょう。こちらのドキュメントが参考になります。
Sentinelでこれらの分析ルールを作成すれば、他の監視ルールと同様にAlertをSlackやメールなどで通知し、SOC業務の中で監視できます。
Push Protectionの設定
GHASのSecret scanningの機能にはPush Protectionという機能があります。これは、GitHubにPushされる前段階でシークレットを検出し、Pushをブロックする機能です。GHASの機能の中で一番心踊る機能ですね。
エンジニアが開発中において誤ってシークレットをPushする前にブロックできるので、予防的対策として非常に有効な機能です。ZOZOではこのPush Protectionを有効化するにあたって、いくつか手順を踏みました。
ZOZOでは開発体験を損なわないためにPush Protectionによるブロック、そして誤検知の頻度に着目しました。2か月ほど様子見を行い、検知されるシークレットはAlertで対応するといった運用をしていました。
2か月の運用で、おおよそ1か月で20Alertほど上がることが確認できました。ただ、このAlertは1つのリポジトリに対して複数のシークレットが検出されるので、シークレットのPush頻度で言えば2週間に1度あるかないかの頻度でした。この辺りはGitHubのSecurityタブの「Overview」などから視覚的に確認できます。
また、誤検知についてはあまりなく、FirebaseのAPI keyなどコード上に記載できる特殊なシークレットを除いては、とても正確な検知でした。
そのため、Push Protectionを有効化しても開発体験を損なうことはないと判断し、Push Protectionの有効化することにしました。
また、Push Protectionにはバイパス機能があり、ブロックされたシークレットをエンジニア自身が確認でき、以下のように自身でPushを許可できます。
このバイパス機能によってシームレスな開発が可能です。
Push Protectionの全社展開後のブロック数やバイパス数は以下のようにSecurityタブの「Secret scanning insights」から確認できます。
こういった視覚情報でリポジトリのシークレットの状況を確認できるのは非常に便利です。
Code scanningのAlertの対応
Code scanningで検出された脆弱性のAlertは、Securityタブの「Code scanning」から確認できますが、開発中のPRでもAlertを上げてくれます。また、以下の「Copilot Autofix」を有効化していれば、GitHub Copilotのライセンスを利用しなくても1、Copilotが脆弱性の修正案を提示してくれる機能もあります。
これらの機能を活用して、エンジニア自身に開発中のセキュアコーディングを実施してもらうことができます。SOCでのAlert対応はこの機能で修正されなかったもののうち、重大なものに対して対応する形になります。
ZOZOにおいてセキュアコーディングは実践すべき重要な取り組みです。これにより、SentinelへのAlertの発生を抑制し、SOCチームはログ解析やインシデント対応といった業務2に注力できます。Copilotの活用は、セキュアコーディングを強力に支援し、開発チームやSOCチームの生産性向上にも寄与します。
実際に以下のような脆弱なFlaskアプリを作成してみました。
この脆弱なコードを含むPRを作成すると、Code scanningのActionsが実行されます。
Actionsが完了すると以下のように脆弱性が検知されます。
「Copilot Autofix」を有効化していれば、PR上で修正案も丁寧に提案してくれて便利です。
Flaskのデバッグモードも検知してくれました。
このように開発段階で脆弱性を検知し、修正案を提示してくれる機能は非常に優秀で、助かっています。
以下は一時期のCode scanningのAlertの数です。Securityタブの「CodeQL pull request alerts」から確認できます。
38%ほどのAlertに対してCopilotが修正案を提示してくれており、そのうち12.5%はCopilotの修正案を適用してAlertを解消してます。修正判断をしたAlertにおけるCopilotの提案をそのまま受けて修正した平均時間が12.98時間ほどです。また、Copilotから修正案が提示されなかったAlertにおける修正した時間が175.98時間(約1週間)ほどです。これらから、単純に163時間(6日以上)をCopilotの修正案で削減できていることがわかります。
「Copilot Autofix」の機能はZOZOでのセキュアコーディングにおいて非常に強力であることがわかります。
Secret scanningのAlertの対応
Secret scanningで検出されたシークレットのAlertは、Push Protectionを有効化している場合は基本的にPush Protectionによってブロックされます。ですが、Push Protection有効化前にPushされたものはブロックされません。
例えば、誤ってPushしてしまったシークレットに対して記載されているファイルを削除したとしても、コミットログなどにその情報は残ったままです。そのため、git showなどのコマンドでシークレットが確認できてしまいます。
そのため、以前にPushしてしまったいにしえの伝統のリポジトリなどはこういったシークレットが残っている場合があります。これらのシークレットに対してもGHASのSecret scanningは検出してくれます。
少数検知の場合は都度プロダクトチームに修正依頼をする形でも良いのですが、数が多い場合は何か共通の管理シートのようなものが必要になってきます。ZOZOではGHASのSecurityタブを確認できる対象者を絞っているので、プロダクトのエンジニア全体に一気に共有できる別の方法を模索しました。
以下のようにSecurityタブの「Overview」にてFilterを用いて対象を絞りつつ、「Export CSV」で対象のAlertをCSV形式でエクスポートできます。
これで対象となるAlertを出力し、全体を管理するためのシートを全社に展開することで、プロダクトチームに対してシークレットの修正対象を共有できました。このCSV自体にシークレットは載らず、リポジトリ個別のAlert URLに繋がる情報が含まれるので、そのURL経由で各プロダクトに対して、Alertの詳細を確認してもらうことができます。
以降の修正の流れはシークレットの提供元でシークレット無効化作業や、公式ドキュメントに沿って修正する形になります。地道な作業ですが、プロダクトのエンジニアの協力を得て、シークレットの修正をしました。
徐々にシークレットの修正が進んでいき、GHASのSecret scanningのAlertも減少していきました。
こういった情報も上記のようにOverviewから確認できます。
まとめ
GHASの導入によって、ZOZOにおけるセキュアコーディング環境がより整備され、開発者がセキュリティを意識した開発をしやすくなりました。特にCode scanningやSecret scanningの機能は、開発段階での脆弱性検出やシークレット漏洩防止に大きく貢献しています。また、Copilot Autofixの機能を活用することで、脆弱性の修正も効率的に行えるようになりました。
おわりに
ZOZOでは、一緒に安全なサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクから是非ご応募ください!