はじめに
アクティブなGitHubセッションはすべて、鍵のかかっていない扉のようなものです。通常は、その扉が自分の端末にあり、自分だけが利用するものであれば問題ありません。しかし、セッションは知らないうちに蓄積されていきます。たとえば、昨年手放したノートPC、設定したまま放置しているCIパイプライン、「テスト用だから」とスクリプトに貼り付けた個人用アクセストークン(Personal Access Token: PAT)などです。これらはすべて、攻撃者に侵入の機会を与え続ける状態になり得ます。そして、その状態が長く続くほど、不正な利用者に悪用される可能性は高まります。
GitHubセッションや個人用アクセストークン(PAT)はベアラークレデンシャルです。APIは、トークンを保持している者を本人として扱います。APIリクエスト時には、多要素認証(MFA)や再認証、追加の認証チャレンジは行われません。認証・認可の判断に使用されるのは、トークンと、それに付与されたスコープだけです。この設計により、トークンは自動化に適している一方で、漏えいすると大きなリスクとなります。repoスコープを持つPATが1つ漏えいするだけで、取り消されるまでの間、プライベートリポジトリをクローンし、コミットをプッシュし、プルリクエストを作成できます。
セッションやトークンを取り消すことは、デジタルの世界における鍵の交換に相当します。これは、開発者の日常的なセキュリティ運用の中でも、見落とされがちな対策の1つです。トークンが漏えいした場合や、ノートPCを紛失した場合、あるいは従業員がチームを離れた場合でも、トークンやセッションを取り消すことで、侵害につながり得た事態を重大なインシデントへ発展させずに済みます。取り消しは迅速かつ追加費用なく実施でき、実行したその瞬間から攻撃対象領域を縮小できます。
しかし、この手順は見落とされがちです。私たちはアクセス権を付与することには細心の注意を払う一方で、不要になったアクセス権を取り消すことには十分な注意を払っていません。本記事では、こうした開いた扉を閉じる方法をはじめ、取り消しが重要な理由、実施すべきタイミング、そしてそれを自動化する方法について解説します。
OAuthとユーザーアクセストークン
あなたの代わりにアカウントへアクセスしているものの多くは、人ではなくアプリです。GitHubにはOAuth AppsとGitHub Appsという2種類のアプリがあり、一見するとよく似ていますが、認証の仕組みはまったく異なります。どちらのアプリなのかを把握することで、どのようにアクセス権を取得したのか、どのリソースにアクセスできるのか、そしてどのようにアクセスを取り消せばよいのかを理解できます。
OAuth Appsは、従来からあるシンプルな認証モデルです。ユーザーが「Authorize」をクリックしてスコープを承認すると、その時点からアプリは、そのユーザーとして動作するトークン(gho_)を保持します。GitHubから見れば、そのアプリはユーザー本人として扱われます。そのため、付与されたスコープの範囲内で、ユーザーがアクセスできるものには同じようにアクセスできます。また、そのトークンは認可が取り消されるまで有効です。OAuth Appsにはインストールの概念がなく、リポジトリ単位でアクセス権を選択することもできません。認可はユーザーアカウント全体に対して一括で付与されます。開発は容易ですが、1回のクリックでアプリへ広範なアクセスを与えることになり、そのトークンは自動的には失効しません。
GitHub Appsは、より新しく、より厳格に設計された認証モデルです。実行する処理に応じて、次の3つの異なる認証方式を使い分けます。
- アプリ自身として ユーザーを介さず、アプリは自身の秘密鍵でJSON Web Token(JWT)に署名し、自身のアイデンティティを証明します。このアプリとしてのアイデンティティは、管理用APIの呼び出しに使用されるほか、後続の認証に使用するトークンを生成します。
- インストールとして アプリがインストールされているアカウントに対して動作する場合は、JWTを使用してインストールアクセストークン(
ghs_)を取得します。このトークンは、インストール時に付与されたリポジトリと権限の範囲に限定され、1時間で失効します。 - ユーザーに代わって アプリが特定のユーザーとして動作する必要がある場合は、OAuth Appsと同じOAuthフローを実行し、ユーザー・サーバートークン(
ghu_)を取得します。ただし、このトークンは広範なスコープではなく、アプリに設定されたきめ細かな権限に制限され、デフォルトで短期間で失効します。
攻撃者はどのように環境へのアクセス権を取得したのでしょうか?
攻撃者が環境へのアクセス権を取得する方法はさまざまです。AiTM(Adversary-in-the-Middle)フィッシング、GitHubの認証メカニズムの悪用、認証情報の漏えいや窃取などがその例です。アクセスを取得した手法に応じて、攻撃者が取得したアクセス権を適切に取り消す方法も判断できます。また、認証メカニズムの中には、ユーザーをフィッシングして認証情報を窃取するために悪用されるものも存在します。
もちろん、多要素認証(MFA)を突破する代表的な手法として、AiTM(Adversary-in-the-Middle)フィッシングは広く知られています。この攻撃では、リバースプロキシ型のフィッシングページが利用者と正規のログイン画面の間に気付かれないよう配置され、入力されたすべての情報をリアルタイムで中継します。利用者は正規の手順どおりにサインインし、多要素認証(MFA)も正常に完了しますが、リバースプロキシは認証済みセッションを取得し、攻撃者はそのセッションを利用して環境へアクセスするとともに、パスワードも窃取します。この手法が広く利用されている理由は、企業がIdP(Identity Provider)を介して各種リソースへアクセスしている場合、攻撃者はIdPを装うことで、標的ユーザーが利用できるアプリケーション群全体へアクセスできる可能性があるためです。
AiTMにも弱点があります。たとえば、FIDOセキュリティキーのようなハードウェア認証デバイスは、この攻撃を防ぐことができます。これは、認証時に対象ドメインに結び付けて署名を行うためです。また、IDSやIPSに加え、一部のEDR製品でもAiTM攻撃を検知できるようになってきています。一方、GitHubには認証をGitHub側で処理し、要求元へトークンを発行する認証メカニズムも用意されています。GitHub CLIやGitHub Desktopなどの正規ツールではこれらの仕組みが利用されており、これらのツールを使用したことがあれば、この認証メカニズムを目にしたことがあるでしょう。
- OAuth同意フィッシング 攻撃者は正規のアプリを装ったOAuth Appを登録し、ユーザーをGitHubの正規の「Authorize」画面へ誘導します。ユーザーが一度クリックするだけで、ユーザーとして動作するトークンが発行され、Organization(組織)でアプリが認可されます。GitHubが認証プロセス全体(MFAを含む)を処理するため、結果として攻撃者はユーザーとして動作するトークンを取得できます。この手法では、一度認証が成功すると、被害者が後からパスワードを変更しても、そのトークンは引き続き有効です。
- デバイスコードフィッシング 前述のOAuth同意フィッシングに類似した手法で、デバイス認可フローを悪用する攻撃です。攻撃者はデバイス認可フローを開始して有効期間の短いユーザーコードを取得し、そのコードをGitHubの正規のデバイスログインページへ入力するよう被害者を誘導します。この際、IT部門や普段どおりのログイン画面を装うケースがよく見られます。その後の流れはOAuth同意フィッシングと同様で、被害者はGitHub上でOrganization(組織)でアプリを認可してしまいます。アクセス先は正規のGitHubページであるため、偽のURLから攻撃を見分けることはできません。
ここまで見てきた攻撃手法には共通点があります。そのほとんどは、パスワードを知らなくても成立し、多要素認証(MFA)を回避することを目的として設計されています。これはトークンの性質によるものです。一度認証が正常に完了すると、その後はトークンによって、それを利用するユーザーやアプリケーションのアクセスが認可されます。だからこそ、このような攻撃が疑われる場合は、該当する認証情報を取り消すことが重要です。
いよいよフィッシングの時間です
OAuth同意フィッシングやデバイスコードフィッシングは、新しい攻撃手法ではありません。これらは長年にわたり、特にEntra ID環境で数多く確認されてきました。GitHubもこれら両方の認証方式を提供しているため、同様の手法を利用して標的環境へのアクセス権を取得できるのは自然なことです。
デバイスコードフィッシングでは、攻撃者は有効期間の短いユーザーコードを生成して標的へ送信します。標的はGitHubのデバイスログインページ(https://github.com/login/device)にサインインし、そのコードを入力してアプリを認可します。

OAuth同意フィッシングも仕組みは同様ですが、この場合は攻撃者はURLを生成して標的へ送信するだけです。

最終的に、標的にはGitHub Appsでは「ghu_」、OAuth Appsでは「gho_」というプレフィックスを持つトークンが発行されます。
ユーザーがGitHub AppsまたはOAuth Appsを介してOAuthフローまたはデバイスコード認証を完了すると、そのアプリは要求したスコープの範囲でアクセスできるようになります。

ユーザーがGitHub AppsまたはOAuth Appsで認証を行う際、アプリはOrganization(組織)でアプリを認可する「Authorize」画面を表示します。Organization(組織)へのアクセスが不要な場合、この操作は不要です。一方、Organization(組織)へのアクセスが必要な場合は、アプリを認可する必要があります。

どちらの認可画面も見た目は同じです。また、どちらもGitHubが運営するアプリであれば、「Owner」欄にはGitHubが表示されます。

GitHubが所有または運営していないアプリには、そのことが明示されます。そのため、GitHubになりすましたアカウントやOrganization(組織)を作成してフィッシングアプリを作成しても、正規のGitHubアプリのようには見えません。

よく利用されているGitHub Apps
フィッシングを正当なものに見せかけるために、GitHubのファーストパーティGitHub Appsを利用できます。その一部では、GitHub CLIのクライアントIDとクライアントシークレットが、オープンソースとして公開されているソースコードで確認できます。例えば、GitHub CLIのクライアントIDとクライアントシークレットは、このツールのソースコード内に含まれています。

GitHub DesktopのクライアントIDは、OAuthフローのURLから確認できるとおり、de0e3c7e9973e1c4dd77です。また、ソースコードには開発用のクライアントIDとクライアントシークレットも含まれているようです。

VSCodeは、デバイスコード認証を使用してGitHubトークンを取得し、クライアントID 01ab8ac9400c4e429b23 を使用しています。このクライアントIDも、VSCodeのリポジトリ内で公開されています。

端末上のGitHub CLIユーザートークンを窃取する
標的端末へのアクセス権を持つ攻撃者は、その端末から認証情報を窃取することも可能です。GitHub CLIが端末で使用されると、トークンが生成されます。このトークンは、使用するOSに応じてシステム内に保存されます。Windowsでは、このトークンはWindows Credential Managerの汎用資格情報(Generic Credential)として保存されます。

この資格情報にはユーザーがアクセスできるため、[CredManRead]::CredReadを使用すれば、比較的容易に抽出できます。

LinuxとmacOSでは、認証情報はデフォルトでプレーンテキストのまま ~/.config/gh/hosts.yml に保存されます。

--secure-storageフラグを使用した場合でも、私たちの検証では同じ結果となりました。

この記事では、GHCLIがトークンの保存先としてmacOS KeychainおよびGNOME Keyringを使用するよう設定する方法を紹介しています。このような構成の場合、攻撃者は次の方法を使用してそれらを抽出する必要があります。
# Commands copied from https://honnibal.dev/blog/locking-down-gh
# macOS
security find-generic-password -s "github-write-pat" -a "$(whoami)" -w
# Linux
secret-tool lookup service github-write-pat user "$(whoami)"
最後に触れておくと、トークンを抽出する最も簡単な方法は、GitHub CLIを使用することです。具体的には、gh auth cli コマンドを使用します。

侵害された認証情報への対応
GitHubで認可済みのログインを一覧表示する
認証情報の侵害が発生した場合、GitHubではOrganization(組織)の管理者が、各OrganizationユーザーのSSOセッションおよび認可済みの認証情報を一覧表示できます。ただし、この一覧には、GitHub AppsやOAuth Apps(GitHubが提供するファーストパーティアプリを含む)を介した認証情報は表示されません。

これらのOrganization(組織)に対する認可は、代わりにGitHub APIの以下のエンドポイントを使用して一覧表示できます。/orgs/$ORG/credential-authorizations

1回の呼び出しですべての認可を取り消す
トークンが漏えいし、そのトークンの値が分かっている場合、GitHubはそのトークンを取り消すためのAPIエンドポイントを提供しています。このAPIはトークンの値を受け取り、トークンを直ちに失効させます。さらに、このエンドポイントは認証を必要としない設計になっています。そのため、漏えいしたトークンを無効化する際に、所有者であることを証明したり、GitHubへログインしたりする必要はありません。これは、漏えいしたトークンを発見した人であれば誰でも報告して失効させ、リスクを迅速に低減できるよう意図的に設計されています。
curl -X POST https://api.github.com/credentials/revoke -H "Accept: application/vnd.github+json" -d '{"credentials":["ghp_...","ghu_...","gho_..."]}'
この方法でトークンを取り消すと、そのトークンを再利用することはできません。そのため、そのトークンを引き続き使用している正規のサービスでは、代わりとなる新しい認証情報を発行する必要があります。

GitHub AppsおよびOAuth Appsで発行されたユーザートークンを取り消す
ユーザーが未承認のGitHub AppsまたはOAuth Appsを介してログインしている場合、管理者は/orgs/$ORG/credential-authorizations/<credential id>へDELETEリクエストを送信することで、その認可を取り消すことができます。これにより該当セッションは失効します。ただし、その後ユーザーが再度認証または認可に成功すると、新しいセッションが作成されます。

OAuthおよびGitHub Apps認証の不正利用を検出する
OAuthおよびGitHub Appによるユーザー認証では、org_credential_authorization.grantイベントが再度記録されます。この場合、application_typeフィールドの値はOauthApplicationになります。

セッションが失効すると、org_credential_authorization.grantイベントが記録されます。

GitHub Appsによるユーザー認証でも、同じイベントが記録されます。

エクサフォースがあなたのセキュリティを守ります
侵害された認証情報への迅速な対応を可能にするため、エクサフォースは、不審または侵害の可能性があるセッションを自動的に失効できる自動化エージェントを提供しています。

Automation Agentは、以下の3つの処理を実行します。
- GitHub Organization(組織)におけるGitHubユーザーの認可情報を一覧表示する
- GitHub Organization(組織)に対する認可を取り消す
- 必要に応じて、ユーザーをOrganization(組織)メンバーから一時的に除外する

まとめ
GitHubトークンの取り消しが重要なのは、トークンがベアラー認証情報として機能するためです。トークンを取得した者は、そのトークンに付与された権限の範囲内で操作を実行でき、パスワードや追加の認証要素は必要ありません。そのため、リポジトリへの誤ったコミット、ログへの記録、フィッシング攻撃、侵害された端末への保存などによってトークンが漏えいすると、有効である限り攻撃者が利用可能なアクセスキーになります。トークンの取り消しは、侵害が疑われる場合の緊急停止手段です。トークンを即座に無効化し、不正利用を防止できるため、被害を迅速に封じ込める最も効果的な方法です。
トークンの取り消しは、有効期限やローテーションに代わるものではなく、それらを補完する仕組みです。有効期限によってトークンが悪用される可能性のある期間を自動的に短縮できますが、取り消しを利用すれば、従業員の退職、連携サービスの廃止、不審な利用の検知など、必要なタイミングで即座にアクセスを無効化できます。有効期限切れを待つ必要はありません。その基本原則は、継続的なアクセス権を最小化することです。有効なトークンは攻撃に利用される可能性があるため、漏えいしたトークンや不要になったトークンを迅速に取り消すことで、攻撃対象領域を可能な限り小さく維持できます。








