実在するOneDriveアカウントを介してC2を運用し、盗まれた認証情報をすべて遠隔で入れ替え可能なインプラントをリバースエンジニアリングしました。トークンの無効化だけでは、封じ込めにはなりません。
私がこれまで書いたり、読んだり、引き継いだりしてきたID侵害対応の手順書には、必ず冒頭近くに同じステップが記されています。トークンを無効化すること。パスワードをリセットし、セッションを終了させ、リフレッシュトークンを失効させてから、調査に移る——というものです。これは正しい直感です。窃取したセッションクッキーそのものが標的となるAiTM(Adversary-in-the-Middle)フィッシングに対しては、トークンの無効化こそがインシデントを終結させる一手になります。
ところが、あるバックドアを数日かけて解析したところ、このステップがまったく意味をなさないケースに出くわしました。その理由はたった一つの関数に集約されており、しかも私たちの大半が真っ先に目を向けるような場所ではありませんでした。
今回のサンプルは「GraphWorm」と呼ばれるもので、中国系APTグループ「Webworm」に紐づくカスタムインプラントです。そこで見つけたある挙動によって、私は自身のインシデント対応手順を一行書き換えることになりました。読者の皆さんの手順書にも、同様の追記が必要だと思います。
C2チャネルの正体は、誰かのOneDrive
GraphWormにはC2ドメインが存在しません。レンタルVPSへのビーコンも、ブロック対象となるハードコードされたアドレスもありません。OAuthアプリケーションとしてMicrosoft Graphに認証し、OneDriveアカウントを「デッドドロップ」(受け渡し場所)として利用するのです。
その仕組みはナプキンにスケッチできるほどシンプルです。攻撃者は暗号化されたタスクファイルをジョブ用フォルダに書き込みます。インプラントはそのフォルダをポーリングし、タスクを実行した後、暗号化された結果を結果用フォルダにアップロードします。ハートビートファイルは一定間隔ごとに新しいタイムスタンプを刻み、フィンガープリントファイルには被害者のシステム詳細が記録されます。対応するコマンドセットには、シェル実行、ファイルのアップロード/ダウンロード、スリープ、キル、そして展開時の鍵をセッションごとの鍵にアップグレードする鍵交換のペアが含まれます。
これらすべての通信は、TLSで保護されたgraph.microsoft.comを経由します。しかも通信元のホストは、一日中Microsoft 365とやり取りしているマシンです。ファイアウォールがフラグを立てるような不審な宛先もなければ、レピュテーションエンジンが減点するような新規登録ドメインもなく、不自然なポートもありません。ネットワークレイヤーには何一つ手がかりがないのです。これはまさに設計上の狙いであり、以前にも書いた通りの傾向、つまり攻撃者が自前のインフラを構築するのをやめ、他人のインフラの上で運用するようになったという流れと同じです。
認証情報はすべて平文でバイナリ内に格納されていました。クライアントID、クライアントシークレット、テナントID、そして1,300文字を超えるリフレッシュトークンです。さらに攻撃者は、PDBのフルパスがそのまま残ったデバッグビルドを出荷していました。
対応を計画する上で、もう一つ重要な点があります。このインプラントは、ホスト名やアドレスで被害者を識別していません。WMI経由で取得したネットワークアダプタのMACアドレスと、CPUおよびディスクのシリアル番号をハッシュ化して識別子を生成しているのです。マシン名を変更しても、別のサブネットに移動させても、新しい送信元アドレスの背後に置いても、攻撃者側は同一のマシンだと認識し続けます。被害者側のネットワーク上のIDを変更することに頼った封じ込め計画は、そもそもこのインプラントには通用しない問題を解決しようとしていることになります。
ここまでは興味深い話ではありますが、目新しいものではありません。クラウドをホストにしたC2は、今に始まったことではないからです。私が予想していなかったのは、タスク処理ハンドラの部分でした。
対応手順を無力化する関数
このインプラントが受け付けるコマンドの一つに、「upgrade」というものがあります。その流れは短く、たどってみる価値があります。
このハンドラは、受信したタスクから設定情報のブロブを解析します。次に、ビーコンオブジェクトが保持する5つの認証情報文字列をすべて破棄し、新しい5つの値をコピーして書き込み、OAuthスコープの構造を再構築し、新しいOneDriveアカウントへの接続をテストし、置き換えた設定をディスクに書き込み、稼働中のAPIインスタンスを入れ替えます。
たった一つのタスクで、身元がまるごと入れ替わるのです。
この結果がもたらす意味を、少し考えてみてください。調査を進め、該当アプリケーションを特定し、プラットフォームに申請してトークンが無効化されたとします。すると、インプラントは次のポーリングに失敗します。もし攻撃者がそれを監視していれば——そして、ハートビートファイルを一定間隔ごとに書き込ませている以上、監視しているはずですが——攻撃者は数か月前に登録しておいた別のOneDriveアカウントを指定した「upgrade」タスクをキューに入れます。インプラントはそれを受け取り、認証情報をローテーションして活動を再開します。エンドポイント上では何も変わっていません。新しいバイナリもなく、新しい永続化の仕組みもなく、新しいプロセスもありません。ディスク上のファイルは同じままで、その背後にある「身元」だけが変わるのです。
トークンの無効化は、一つの認証情報を取り除いただけで、アクセスそのものを取り除いたわけではなかったのです。
この点は、このマルウェアファミリーに関する公開レポートには記載されていません。もっとも、これは批判すべきことではありません。ベンダーのレポートは観測したキャンペーンの範囲に限定されるものであり、今回の発見は、誰も優先して調べる理由がなかった関数をデコンパイラで開いて初めて見えてくる類のものだからです。私自身、これを文章にする前に2度確認しました。一度は抽出した文字列から、もう一度はデコンパイルした関数そのものから。文字列から復元した認証情報フィールドのオフセットは、コンストラクタが実際に読み取るオフセットと一致している必要があり、実際に一致していました。これこそが、単なる推測と確かな発見との違いです。
この発見を受けて変えたこと
この件を踏まえ、明日からでもID対応手順に組み込みたいと考えていることが3つあります。
- トークンの無効化を「排除」ではなく「遅延」と捉えること。C2がアプリケーションIDを介して動いている場合は特にそうです。持続性を持つ対象は、発行されるトークンではなく、アプリケーションの「登録」そのものです。トークンは枝葉に過ぎず、根幹にあるのは登録情報です。無効化だけで終わる封じ込めは、扉を閉じたのではなく、タイマーを起動しただけに過ぎません。その登録情報が自組織のテナントの管理外にある場合、停止させるには報告書を提出し、相手の対応キューを待つほかありません。だからこそ、最後のステップとしてではなく、早い段階で申請すべきです。
- 攻撃者は予備を持っていると想定すること。認証情報のローテーションは攻撃者にとってほぼコストがかかりませんが、防御側にとっては対応サイクルまるごとのコストになります。この非対称性を踏まえて対応の順序を組むべきです。認証情報を無効化するのと同時に、エンドポイントがそのチャネルに到達できないようにする必要があり、後回しにしてはいけません。ホストの隔離や、自組織のテナント内で該当アプリケーションの認証を個別にブロックすることは、いずれも他者の協力を必要としない対策です。
- IDレイヤーで狩ること。ネットワークレイヤーはこのケースでは無力だからです。このマルウェアファミリーに実際に反応する検知は、ネットワークシグネチャではまったくありません。クラウドのテレメトリに対するクエリです。攻撃者のアプリケーションIDがサインインイベントに現れていないか、見覚えのないテナントに対する認証が発生していないか、ブラウザ以外のHTTPライブラリのユーザーエージェントがOneDriveにアクセスしていないか、ビーコンやフィンガープリントのファイル名がファイルテレメトリに現れていないか、といった点です。アプリケーションIDは固定文字列であり、正当な用途で使われることは一切ありません。サインインのテレメトリに対して一度クエリを投げるだけで、そのIDが自組織のテナントにトークンを要求したことがあるかどうかが分かります。
これらはいずれも新しいツールを必要としません。必要なのは、アプリケーションの登録情報そのものを「排除すべき対象」として扱う発想です。
私の印象に強く残ったのは、もっと大きな構図です。私たちはこの10年、インフラを狩る術を磨いてきましたが、攻撃者はインフラそのものを持たないことでそれに応えてきました。チャネルがクラウドテナント内のフォルダであり、そのフォルダの背後にある身元がリモートコマンド一つで入れ替え可能である以上、私たちが追うよう訓練されてきた痕跡こそが、攻撃者にとって最も安価に入れ替えられるものなのです。一方で、攻撃者が安く入れ替えられないものが二つあります。エンドポイント上に存在するコードそのものと、そのコードが自組織のテレメトリ内で示す振る舞いです。
そのため、私は今、その前提そのものを疑うようにしています。あるIDインシデントを「封じ込めた」と宣言する前に、具体的に何を排除したのか、そして攻撃者が被害者のマシンに一切触れることなく自分自身に代わりの手段を用意できるかどうかを問うようにしているのです。今回のサンプルでは、答えは「イエス」でした。そしてそれを証明するのに必要だったのは、たった一つの関数でした。
今回の解析の全容と検知コンテンツはGitHub上で公開しています。
翻訳元: https://www.csoonline.com/article/4223975/revoking-the-token-didnt-kill-the-backdoor.html