Proofpointは今年、Entra IDに対して盗まれた資格情報を検証した2つのキャンペーンを記録しましたが、成功したサインインは一度もありませんでした。この手法は一文で説明できるほどシンプルです。登録されたアプリケーションに対応しないクライアントIDでトークンエンドポイントにパスワードを送信し、返されるエラーを読み取るだけです。これらのエラーコードは重要ですが、より大きな漏洩は各コードが認証シーケンスのどこに位置するかです。
3つのコード、3つの異なる意味
リクエストはOAuth 2.0 Resource Owner Password Credentials(ROPC)エンドポイントに送られます:ユーザー名、パスワード、および client_id を含むPOSTです。この手法はROPCに明確に限定されており、制限することが対策の一つです。
重要な3つの反応:
- AADSTS50034 (
UserAccountNotFound): アカウントがディレクトリにありません。 - AADSTS50126 (
InvalidUserNameOrPassword): 失敗全般をカバーする汎用エラーとして文書化されています。 - AADSTS700016:ディレクトリでアプリケーション識別子が見つかりませんでした。
AADSTS50126は、誤ったユーザー名と誤ったパスワードを区別すべきではありませんが、観察された動作ではそれらを区別しています。存在しないユーザー名に対する失敗した試みはサインインログに全く記録されません。Entraはユーザー名が有効であると確認した後にのみログ行を書き込みます。Entraは存在しないアカウントに対して50034を返し、パスワードが間違っている存在するアカウントに対して50126を返します。これはMicrosoftのドキュメントで曖昧にされている観察された動作の違いです。スコープも重要です。これらの観察はトークンエンドポイントの管理されたテナントに適用されます。フェデレーテッドテナントはPass-through Authentication(PTA)またはADFSを通じてオンプレミスでパスワードを検証するため、エラーパスは異なります。
ログは処理の違いを証明します:
Request | Log row written? | What the attacker learns |
|---|---|---|
|
Account not in directory (50034)
|
No |
This username doesn't exist |
|
Account exists, wrong password (50126) |
Yes, failed sign-in |
This username exists |
|
Account exists, correct password (700016) |
Yes, failed sign-in
|
This credential pair works |
AADSTS700016はアプリ登録が壊れていることを示します。見慣れないクライアントIDによる認証試行が続いた後に表示され、攻撃者が有効なパスワードを持っていることを確認します。
2つのキャンペーン
UNK_pyreq2323は2026年1月14日から3月初旬までAWSインフラを使い果たしました。生成されたのは70万以上の偽装クライアントIDで、正当なファーストパーティのExchange Onlineアプリケーション識別子の最後の6桁をランダム化して作成されました(例:00000002-0000-0ff1-ce00-000000XXXXXX)。約4,000テナントの100万以上のアカウントを標的とし、偽装IDは12アカウント以下で再利用された後に破棄されました。MicrosoftのファーストパーティアプリケーションIDはすべてのテナントで事前承認されているため、そのプレフィックスを保持するIDはノイズではなく正当なものに見えます。
UNK_OutFlareAZは2025年12月に出現し、2026年2月と3月に再び現れ、Cloudflareのインフラを通じて200万人以上のユーザーに対して370万件の偽装されたアプリケーションIDを使用しました。
両者は異なる生成方法を使用しており、その違いが検出に影響します。pyreq2323は実際のプレフィックスを保持し各IDを最大12回再利用しましたが、OutFlareAZはリクエストごとに新しいランダムなUUIDv4を生成し、IDごとにユーザーが1人ずつ割り当てられるため、再利用に基づくヒューリスティックはこれを見逃します。
(Proofpointは両キャンペーンをそのUNK_指定の下で追跡しており、これは名前のないクラスターを示します。)
検出
防御側は失敗やロックアウトを見ましたが、どの失敗が資格情報の確認だったかはわかりませんでした。信号はそこにあり、その朝パスワードを誤入力したすべてのユーザーと同じ山にありましたが、どのエラーコードを分離すべきかを知らなければ読み取れませんでした。本当に見えない半分は列挙です。存在しないユーザー名はログ行をまったく生成しないため、他のすべてに先立つ偵察はどのテナントにも痕跡を残しません。
これらのリクエストはすべてインタラクティブサインインログに記録されます。これはMicrosoft自身の定義によるものです:IsInteractiveはユーザーが認証要素を提供するサインインをカバーし、定義ではパスワードが明示的に示されており、ROPCはパスワードを送信します。
署名は空白のアプリケーション名です:
Client ID in request |
|
|
|---|---|---|
|
Real registered app |
populated |
populated |
|
Well-formed UUID, unregistered
|
populated
|
blank |
|
Malformed client ID |
blank |
blank |
2行目が探しているものです。isnotempty(AppId) と isempty(AppDisplayName) を組み合わせて、正しく形成されているが未登録のケースを特定し、不正なデータを含めないようにします。
重要なクエリ は確認済みの資格情報漏えいを返します:
SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType == "700016"
| where isnotempty(AppId) and isempty(AppDisplayName)
| project TimeGenerated, UserPrincipalName, AppId, IPAddress,
UserAgent, AuthenticationProtocol, ResultDescription
| order by TimeGenerated asc
各行はEntraで検証された資格情報のペアですので、出力をパスワードリセットおよびセッション取り消しリストとして扱ってください。これはアプリ登録チケットではなく、ここで最も重大なミスは700016件のヒットをアプリケーション登録の所有者にルーティングすることです。
キャンペーン検出の場合、クライアントIDではなくソースを基準にしてください:
SigninLogs
| where TimeGenerated > ago(7d)
| where isnotempty(AppId) and isempty(AppDisplayName)
| summarize
DistinctAppIds = dcount(AppId),
TargetedUsers = dcount(UserPrincipalName),
ConfirmedCreds = countif(ResultType == "700016"),
WrongPassword = countif(ResultType == "50126"),
Lockouts = countif(ResultType == "50053"),
UserAgents = make_set(UserAgent, 10)
by IPAddress, bin(TimeGenerated, 1h)
| where DistinctAppIds > 10
| order by DistinctAppIds desc
IPAddressを軸にすることはIPAddress 意図的です。12アカウント再利用上限に基づくヒューリスティックはpyreq2323を検出しますが、OutFlareAZはまったく検出しません。なぜなら後者はIDを再利用しないからです。両キャンペーンはソースから見ると同じように見えます:大量のクライアントIDを提示しますが、何も解決しません。同じ行に ConfirmedCreds と Lockouts を保持すると、本物の侵害の隣にノイズが表示されます。
修正内容の対象と非対象
Conditional Accessを通じてROPCを制限することは正しい判断であり、Proofpointも推奨しています。しかし、それが何をもたらすのかを明確にしたいと思います。
列挙には影響しません。 Entraがユーザー名を解決する際にAADSTS50034が返されます。これは資格情報の検証前であり、ポリシー評価の2ステップ前です。Conditional Accessポリシーは、ポリシーが参照される前に発行された応答を傍受できず、テナントの設定に関係なくログ行も書き込まれません。ディレクトリに対するユーザー名の列挙は、厳格なレガシー認証ブロックを含むすべてのテナント側の制御を通過します。
レガシー認証をブロックするポリシーはAADSTS53003を返します、BlockedByConditionalAccess。同じ順序のロジックにより、53003はパスワードが検証された後にのみ到達可能であるため、攻撃者のエラーメッセージは53003ではなく50126になります。異なるコード、同じ情報です。
参考文献
共有する
もっと詳しく
著者について
Darryl Baker
シニアスタッフ セキュリティリサーチャー
Darryl G. Baker は Netwrix のシニアスタッフセキュリティリサーチャーであり、Identity および Active Directory のセキュリティ分野で広く認められた権威者です。10年以上にわたるアイデンティティシステムの経験をもとに、彼は Active Directory、Entra ID、Azure 環境に焦点を当てたエンタープライズ向けのセキュリティ評価、アイデンティティセキュリティ研修、そして脅威のエミュレーション(threat emulations)を主導してきました。Darryl は BlueTeamCon、BSidesCT、The Experts Conference、Wild Wild West Hackin’ Fest にて高い評価を受けた研修やデモを提供しています。彼は、現在のレッドチーム/ブルーチームのツールを活用し、防御側が攻撃経路分析から脅威ハンティング(threat hunting)までを習得できるよう支援する、多数のハンズオン攻撃エミュレーションラボのアーキテクトです。彼のセッションでは、Darryl が深い技術的洞察と実世界のケーススタディを融合させ、ブルーチームのプロフェッショナルが Identity セキュリティ体制を強化し、進化し続ける攻撃者の手口に対して防御できるようエンパワーします。