Defender XDRでDomain Admin SID・証明書ログオンをKQL検出する方法

Microsoft Defender XDRでDomain Admins、Schema Admins、Enterprise Adminsなどの特権SIDを含むログオンを探すには、DeviceLogonEventsのAdditionalFieldsをparse_json()で展開します。そのうえで、TokenHasDomainAdminSidなどを真偽値へ、NumberOfSidsInDomainAdminTokenを数値へ変換して絞り込みます。

ただし、証明書ログオンの検出では注意が必要です。TokenHasCertificatePublisherSidは「Certificate PublishersグループのSIDを含む」ことを示す値であり、証明書でログオンしたこと自体の証明ではありません。組織発行の証明書を使った認証コンテキストを探す場合は、公式発表に含まれるTokenHasThisOrgCertificateSidも確認します。(TECHCOMMUNITY.MICROSOFT.COM)

以下では、フィールドの到着確認から、特権ログオン、証明書ベース認証、不審なSID数の検出、カスタム検知への組み込みまでを、実行可能なKQLで解説します。

目次

新しい特権トークンテレメトリで分かること

Microsoftは2026年7月15日、Microsoft Defender XDRのAdvanced Huntingで、ログオン時の特権トークンコンテキストを調査できる新しいテレメトリを発表しました。値はDeviceLogonEventsの独立した列ではなく、AdditionalFields内のJSONプロパティとして提供されます。(TECHCOMMUNITY.MICROSOFT.COM)

主なフィールドと用途は次のとおりです。

フィールド示す内容主なハンティング用途
TokenHasDomainAdminSidトークンにDomain AdminsグループのSIDが含まれるドメイン管理者権限を持つログオンの抽出
TokenHasSchemaAdminSidトークンにSchema AdminsグループのSIDが含まれるスキーマ変更権限を伴う高リスクなログオンの確認
TokenHasEnterpriseAdminSidトークンにEnterprise AdminsグループのSIDが含まれるフォレスト全体へ影響する権限の利用確認
TokenHasCertificatePublisherSidトークンにCertificate PublishersグループのSIDが含まれる証明書発行基盤に関係するアカウントやサービスの調査
TokenHasThisOrgCertificateSid組織発行の証明書ベース認証に関連するSIDが含まれる証明書ログオンや証明書認証コンテキストの調査
NumberOfSidsInDomainAdminTokenDomain Admins SIDを含むトークン内のSID総数通常と異なるグループ構成やトークン構成の検出

NumberOfSidsInDomainAdminTokenは、Domain Admins SIDを含む場合のトークン内SID総数を表します。固定の危険値があるわけではなく、通常時の値からの変化を見るために使うのが適切です。(TECHCOMMUNITY.MICROSOFT.COM)

「SIDが含まれる」と「直接メンバーである」は同じではない

これらの値が示すのは、ログオン時に生成されたアクセストークンの内容です。Active Directory上で現在どのグループへ直接所属しているかを、そのまま表示する列ではありません。

たとえば、あるユーザーが別のグループを経由してDomain Adminsの権限を得ていれば、トークンにDomain Admins SIDが含まれる可能性があります。また、グループ構成を変更した直後でも、既存のログオンセッションには変更前のトークンが残ることがあります。

したがって、TokenHasDomainAdminSid == trueは「そのログオンセッションにDomain Adminsの権限コンテキストが存在した」と解釈するのが安全です。

管理者設定と適用範囲

今回のテレメトリについて、Microsoftの公式発表では専用ポリシーや新しい有効化スイッチを設定する手順は示されていません。既存のDeviceLogonEvents.AdditionalFieldsへ情報が追加される形であるため、実務上はAdvanced Huntingで値が到着しているかを確認します。

ただし、利用には次の前提があります。

確認項目必要な状態
データソース対象デバイスがMicrosoft Defender for Endpointへオンボードされている
テーブルDeviceLogonEventsに通常のログオンイベントが記録されている
ハンティング権限Advanced Huntingのデータを参照できる
カスタム検知権限セキュリティ設定の管理権限や、対象デバイススコープへの権限がある
ロールアウト確認実データのAdditionalFieldsに新フィールドが現れている
OS対応ログオンテレメトリを収集できる対応OSである

DeviceLogonEventsは、デバイス上のユーザーログオンや認証イベントを格納するテーブルです。Microsoft Defender for Endpointが展開されていない環境では、このテーブルを使ったクエリが結果を返さない可能性があります。また、Windows 7とWindows Server 2008 R2では、このテーブルの収集がサポートされていません。(Microsoft Learn)

DeviceLogonEventsとEntraサインインログを混同しない

今回のフィールドは、デバイス上で生成されたWindowsログオンのトークンコンテキストを調べるためのものです。

次のような情報を直接置き換えるものではありません。

  • Microsoft Entra IDのクラウドサインイン
  • 条件付きアクセスの評価結果
  • 多要素認証の実行結果
  • IdentityLogonEventsに記録されるIDベースの認証
  • Active Directoryのグループ変更履歴

クラウド側のサインインとデバイス側の特権トークンを関連付けたい場合は、DeviceLogonEventsだけで完結させず、対象時刻、アカウント、端末、送信元IPなどを使って別の認証テレメトリと突き合わせます。

新しいフィールドが届いているか確認するKQL

最初からカスタム検知を作るのではなく、まず過去7日間のAdditionalFieldsに新しいキーが含まれているかを確認します。

DeviceLogonEvents
| where Timestamp > ago(7d)
| where isnotempty(AdditionalFields)
| where AdditionalFields has_any (
    "TokenHasDomainAdminSid",
    "TokenHasSchemaAdminSid",
    "TokenHasEnterpriseAdminSid",
    "TokenHasCertificatePublisherSid",
    "TokenHasThisOrgCertificateSid",
    "NumberOfSidsInDomainAdminToken"
)
| project
    Timestamp,
    DeviceName,
    ActionType,
    AccountDomain,
    AccountName,
    AccountSid,
    LogonType,
    RemoteIP,
    AdditionalFields
| order by Timestamp desc
| take 100

この段階ではActionType == "LogonSuccess"を指定していません。最初の確認で条件を絞りすぎると、テレメトリが届いているにもかかわらず見落とす可能性があるためです。

結果が出ない場合は、次の順序で確認します。

  1. 期間をago(30d)まで広げる
  2. DeviceLogonEvents | take 10でテーブル自体にデータがあるか確認する
  3. 対象端末がDefender for Endpointへオンボードされているか確認する
  4. デバイススコープやAdvanced Huntingの権限を確認する
  5. 特権アカウントによるテストログオン後に再検索する

ActionTypeの実際の値は、Microsoft Defenderポータル内のスキーマリファレンスでも確認できます。環境で結果が出ない場合は、LogonSuccessなどの条件を一度外して値を確認してください。(Microsoft Learn)

利用可能なキーを一覧化する

AdditionalFieldsに実際に含まれているトークン関連キーを一覧化したい場合は、次のKQLを使えます。

DeviceLogonEvents
| where Timestamp > ago(7d)
| where isnotempty(AdditionalFields)
| extend AF = parse_json(AdditionalFields)
| mv-expand Field = bag_keys(AF)
| extend Field = tostring(Field)
| where Field startswith "TokenHas"
    or Field == "NumberOfSidsInDomainAdminToken"
| summarize
    Events = count(),
    FirstSeen = min(Timestamp),
    LastSeen = max(Timestamp)
    by Field
| order by Field asc

このクエリは、フィールドごとのイベント数と最初・最後に確認できた時刻を表示します。段階的なロールアウトや、端末ごとのデータ差を確認するときに有効です。

nullとfalseを区別する

初期確認では、欠損値をすぐにfalseへ変換しないことが重要です。

  • false:フィールドがあり、条件に該当しなかった
  • null:フィールド自体が存在しない、または値を取得できなかった
  • true:トークンに対象SIDが含まれていた

最初からcoalesce(TokenHasDomainAdminSid, false)とすると、「フィールドが未到着」と「Domain Admins SIDが含まれていない」を区別できなくなります。カスタム検知へ移す前に、対象デバイス群で値が安定して記録されることを確認してください。

AdditionalFieldsをparse_jsonで展開する基本KQL

AdditionalFieldsは文字列型で格納されているため、各プロパティをKQLの条件式で使うにはparse_json()でdynamic型へ変換します。複数のJSONプロパティを取得する場合、Microsoftもparse_json()の利用を案内しています。(Microsoft Learn)

DeviceLogonEvents
| where Timestamp > ago(7d)
| where AdditionalFields has_any (
    "TokenHasDomainAdminSid",
    "TokenHasSchemaAdminSid",
    "TokenHasEnterpriseAdminSid",
    "TokenHasCertificatePublisherSid",
    "TokenHasThisOrgCertificateSid",
    "NumberOfSidsInDomainAdminToken"
)
| extend AF = parse_json(AdditionalFields)
| extend
    TokenHasDomainAdminSid =
        tobool(AF.TokenHasDomainAdminSid),
    TokenHasSchemaAdminSid =
        tobool(AF.TokenHasSchemaAdminSid),
    TokenHasEnterpriseAdminSid =
        tobool(AF.TokenHasEnterpriseAdminSid),
    TokenHasCertificatePublisherSid =
        tobool(AF.TokenHasCertificatePublisherSid),
    TokenHasThisOrgCertificateSid =
        tobool(AF.TokenHasThisOrgCertificateSid),
    NumberOfSidsInDomainAdminToken =
        toint(AF.NumberOfSidsInDomainAdminToken)
| project
    Timestamp,
    ReportId,
    DeviceId,
    DeviceName,
    ActionType,
    AccountDomain,
    AccountName,
    AccountSid,
    LogonType,
    LogonId,
    RemoteDeviceName,
    RemoteIP,
    RemoteIPType,
    IsLocalAdmin,
    TokenHasDomainAdminSid,
    TokenHasSchemaAdminSid,
    TokenHasEnterpriseAdminSid,
    TokenHasCertificatePublisherSid,
    TokenHasThisOrgCertificateSid,
    NumberOfSidsInDomainAdminToken
| order by Timestamp desc

parse_jsonの前に絞り込む

大量のログすべてにparse_json()を実行すると、クエリ負荷が高くなります。次の順番で処理するのが基本です。

  1. Timestampで期間を限定する
  2. AdditionalFields hasまたはhas_anyで候補を絞る
  3. parse_json()でJSONを展開する
  4. 真偽値や数値へ変換する
  5. 必要な列だけをprojectする

MicrosoftのAdvanced Huntingベストプラクティスでも、解析関数を使う前に時間や条件でレコードを絞り込み、部分文字列検索のcontainsより、単語検索のhasを優先することが推奨されています。(Microsoft Learn)

Domain Adminsなどの特権ログオンを検出するKQL

次のクエリは、Domain Admins、Schema Admins、Enterprise AdminsのいずれかのSIDを含む成功ログオンを抽出します。

let lookback = 7d;
DeviceLogonEvents
| where Timestamp > ago(lookback)
| where ActionType == "LogonSuccess"
| where AdditionalFields has_any (
    "TokenHasDomainAdminSid",
    "TokenHasSchemaAdminSid",
    "TokenHasEnterpriseAdminSid"
)
| extend AF = parse_json(AdditionalFields)
| extend
    TokenHasDomainAdminSid =
        tobool(AF.TokenHasDomainAdminSid),
    TokenHasSchemaAdminSid =
        tobool(AF.TokenHasSchemaAdminSid),
    TokenHasEnterpriseAdminSid =
        tobool(AF.TokenHasEnterpriseAdminSid),
    NumberOfSidsInDomainAdminToken =
        toint(AF.NumberOfSidsInDomainAdminToken)
| where TokenHasDomainAdminSid == true
    or TokenHasSchemaAdminSid == true
    or TokenHasEnterpriseAdminSid == true
| extend PrivilegeContext = strcat(
    iff(TokenHasDomainAdminSid == true, "Domain Admins;", ""),
    iff(TokenHasSchemaAdminSid == true, "Schema Admins;", ""),
    iff(TokenHasEnterpriseAdminSid == true, "Enterprise Admins;", "")
)
| project
    Timestamp,
    ReportId,
    DeviceId,
    DeviceName,
    AccountDomain,
    AccountName,
    AccountSid,
    LogonType,
    LogonId,
    RemoteDeviceName,
    RemoteIP,
    RemoteIPType,
    IsLocalAdmin,
    PrivilegeContext,
    NumberOfSidsInDomainAdminToken
| order by Timestamp desc

このクエリの結果が出たからといって、直ちに侵害と判断することはできません。特権管理端末、ドメインコントローラー、正規の管理作業でも検出されます。

重要なのは、特権SIDの有無に、端末、ログオン種別、送信元、時間帯を組み合わせることです。

優先的に調査すべき条件

条件調査優先度判断のポイント
Schema AdminsまたはEnterprise Admins SIDが通常のクライアントPCで検出された非常に高いフォレスト全体へ影響する権限が一般端末で使われていないか
Domain Admins SIDが承認済み管理端末以外で検出された高いTier 0用端末やPAWの運用ルールに反していないか
特権SIDとPublicのRemoteIPTypeが同時に記録された高いVPN、ゲートウェイ、NATなど正規経路か
初めて確認するアカウントと端末の組み合わせ高い新規運用、端末交換、横展開のいずれか
深夜や休日にInteractiveまたはRemote Interactiveが発生した中~高変更作業や障害対応の申請があるか
承認済みサーバーでServiceまたはBatchログオンが反復している中サービスアカウントやスケジュールタスクとして正常か

承認済みの管理端末を除外する場合は、実環境のFQDNへ置き換えて次の条件を追加します。

| where DeviceName !in~ (
    "dc01.contoso.local",
    "dc02.contoso.local",
    "paw01.contoso.local"
)

ただし、ドメインコントローラーを常に除外すると、侵害された管理者アカウントによる不正ログオンも見落とします。カスタム検知では除外しても、調査ブックでは全件の推移を残す運用が安全です。

IsLocalAdminとは別の情報として扱う

IsLocalAdminは、そのユーザーが対象デバイス上のローカル管理者かどうかを示します。一方、TokenHasDomainAdminSidはログオントークン内のDomain Admins SIDを示します。両者は目的が異なるため、次のように独立して評価します。DeviceLogonEventsにはIsLocalAdmin、AccountSid、LogonType、RemoteIPなどの調査用列も用意されています。(Microsoft Learn)

  • IsLocalAdmin == trueでもDomain Adminsとは限らない
  • Domain Admins SIDを持つログオンでも、検知時点の列の状態やイベント種別によってIsLocalAdminの見え方が異なる可能性がある
  • 特権グループの検出条件をIsLocalAdminだけで代用しない

証明書ログオンを検出するKQL

証明書関連の2つのフィールドは、意味を分けて扱います。

フィールド正しい解釈
TokenHasThisOrgCertificateSid組織発行の証明書ベース認証に関連するトークンコンテキスト
TokenHasCertificatePublisherSidCertificate PublishersグループのSIDを含むトークン

証明書認証を探す基本クエリは次のとおりです。

DeviceLogonEvents
| where Timestamp > ago(7d)
| where ActionType == "LogonSuccess"
| where AdditionalFields has "TokenHasThisOrgCertificateSid"
| extend AF = parse_json(AdditionalFields)
| extend
    TokenHasThisOrgCertificateSid =
        tobool(AF.TokenHasThisOrgCertificateSid),
    TokenHasCertificatePublisherSid =
        tobool(AF.TokenHasCertificatePublisherSid)
| where TokenHasThisOrgCertificateSid == true
| extend CertificateContext = iff(
    coalesce(TokenHasCertificatePublisherSid, false),
    "Certificate authentication + Certificate Publishers SID",
    "Certificate-based authentication"
)
| project
    Timestamp,
    ReportId,
    DeviceId,
    DeviceName,
    AccountDomain,
    AccountName,
    AccountSid,
    LogonType,
    LogonId,
    RemoteDeviceName,
    RemoteIP,
    RemoteIPType,
    CertificateContext
| order by Timestamp desc

TokenHasCertificatePublisherSidだけを調査する場合は、フィルターを次のように変更します。

| where TokenHasCertificatePublisherSid == true

ただし、その結果を「証明書でログオンしたユーザー」と断定してはいけません。Certificate Publishersグループへの所属を伴う管理アカウント、証明書サービス、認証基盤のサービスアカウントなどが該当する可能性があります。

証明書ベース認証で確認すべき項目

証明書関連のイベントを見つけたら、少なくとも次を確認します。

  • そのアカウントで証明書認証を許可しているか
  • ログオン先が承認済み端末や認証サーバーか
  • LogonTypeが想定した認証経路と一致するか
  • RemoteIPやRemoteDeviceNameが通常利用する接続元か
  • 証明書の発行、更新、失効などの作業時刻と一致するか
  • 同じアカウントでDomain Adminsなどの特権SIDも検出されていないか
  • 直前にアカウント、グループ、証明書テンプレートの変更がなかったか

証明書認証と特権SIDを同時に探す場合は、次の条件を追加します。

| extend
    TokenHasDomainAdminSid =
        tobool(AF.TokenHasDomainAdminSid),
    TokenHasSchemaAdminSid =
        tobool(AF.TokenHasSchemaAdminSid),
    TokenHasEnterpriseAdminSid =
        tobool(AF.TokenHasEnterpriseAdminSid)
| where TokenHasDomainAdminSid == true
    or TokenHasSchemaAdminSid == true
    or TokenHasEnterpriseAdminSid == true

これにより、「証明書認証のコンテキストがあり、かつ高権限SIDを含むログオン」を優先的に抽出できます。

NumberOfSidsInDomainAdminTokenで不審なSID構成を探す

NumberOfSidsInDomainAdminTokenは、Domain Admins SIDを含むトークンに格納されたSID総数です。これを使うと、単にDomain Adminsであることだけでなく、トークンのグループ構成が通常と異なっていないかを調べられます。(TECHCOMMUNITY.MICROSOFT.COM)

まずSID数の分布を確認する

最初から「20以上なら危険」といった固定値を決めず、過去30日間の分布を確認します。

DeviceLogonEvents
| where Timestamp > ago(30d)
| where AdditionalFields has "NumberOfSidsInDomainAdminToken"
| extend AF = parse_json(AdditionalFields)
| extend SidCount =
    toint(AF.NumberOfSidsInDomainAdminToken)
| where isnotnull(SidCount)
| summarize
    Events = count(),
    Accounts = dcount(AccountSid),
    Devices = dcount(DeviceId)
    by SidCount
| order by SidCount asc

SID数は、組織のグループ設計、入れ子構造、運用ツール、サービスアカウントなどによって変わります。そのため、他社環境のしきい値をそのまま採用するより、次の単位で基準を作る方が実用的です。

  • アカウント単位
  • アカウントとログオン種別の組み合わせ
  • アカウントとデバイスの組み合わせ
  • 管理端末と一般端末の区分
  • 通常運用時と変更作業時の区分

過去30日間の通常値から外れたログオンを探す

次のクエリは、直近1日のSID数を、それ以前の約29日間における同一アカウントの5パーセンタイルから95パーセンタイルまでの範囲と比較します。

let BaselineStart = ago(30d);
let CurrentStart = ago(1d);
let Baseline =
    DeviceLogonEvents
    | where Timestamp between (BaselineStart .. CurrentStart)
    | where AdditionalFields has "NumberOfSidsInDomainAdminToken"
    | extend AF = parse_json(AdditionalFields)
    | extend SidCount =
        toint(AF.NumberOfSidsInDomainAdminToken)
    | where isnotempty(AccountSid)
    | where isnotnull(SidCount)
    | summarize
        BaselineP05 = percentile(SidCount, 5),
        BaselineP95 = percentile(SidCount, 95),
        BaselineEvents = count()
        by AccountSid;
DeviceLogonEvents
| where Timestamp > CurrentStart
| where AdditionalFields has "NumberOfSidsInDomainAdminToken"
| extend AF = parse_json(AdditionalFields)
| extend SidCount =
    toint(AF.NumberOfSidsInDomainAdminToken)
| where isnotempty(AccountSid)
| where isnotnull(SidCount)
| join kind=leftouter Baseline on AccountSid
| where isnull(BaselineEvents)
    or SidCount < BaselineP05
    or SidCount > BaselineP95
| project
    Timestamp,
    DeviceName,
    AccountDomain,
    AccountName,
    AccountSid,
    LogonType,
    RemoteIP,
    SidCount,
    BaselineP05,
    BaselineP95,
    BaselineEvents
| order by Timestamp desc

このクエリは「攻撃を確定するクエリ」ではなく、通常と異なるログオンを調査対象へ上げるためのものです。

特に確認すべきなのは、次のような変化です。

  • 同じ管理者なのにSID数が突然増えた
  • 通常と異なる端末でのみSID数が変化した
  • Remote InteractiveやNetworkログオンでだけ異常値になった
  • 新しいアカウントで、過去の基準値が存在しない
  • グループ変更の予定がないのにSID数が変化した
  • SID数の変化と同時にSchema AdminsやEnterprise Admins SIDが現れた

SID数が多い場合だけでなく、通常より少ない場合も確認対象です。グループ構成の変更、異なる認証経路、別セッションの生成などが原因で、トークン内容が変化している可能性があるためです。

既存のKQLへ組み込む方法

既存のログオン検知クエリへ追加する場合は、クエリ全体を書き直す必要はありません。

追加する位置

既存クエリが次のような構成なら、

DeviceLogonEvents
| where Timestamp > ago(7d)
| where ActionType == "LogonSuccess"
| project Timestamp, DeviceName, AccountName, LogonType

projectより前に次のブロックを追加します。

| where AdditionalFields has_any (
    "TokenHasDomainAdminSid",
    "TokenHasSchemaAdminSid",
    "TokenHasEnterpriseAdminSid",
    "TokenHasCertificatePublisherSid",
    "TokenHasThisOrgCertificateSid",
    "NumberOfSidsInDomainAdminToken"
)
| extend AF = parse_json(AdditionalFields)
| extend
    TokenHasDomainAdminSid =
        tobool(AF.TokenHasDomainAdminSid),
    TokenHasSchemaAdminSid =
        tobool(AF.TokenHasSchemaAdminSid),
    TokenHasEnterpriseAdminSid =
        tobool(AF.TokenHasEnterpriseAdminSid),
    TokenHasCertificatePublisherSid =
        tobool(AF.TokenHasCertificatePublisherSid),
    TokenHasThisOrgCertificateSid =
        tobool(AF.TokenHasThisOrgCertificateSid),
    NumberOfSidsInDomainAdminToken =
        toint(AF.NumberOfSidsInDomainAdminToken)

その後、目的に応じたwhere条件を追加します。

特権SIDのいずれかを含む

| where TokenHasDomainAdminSid == true
    or TokenHasSchemaAdminSid == true
    or TokenHasEnterpriseAdminSid == true

証明書ベース認証を含む

| where TokenHasThisOrgCertificateSid == true

Domain AdminsでSID数が基準値より多い

| where TokenHasDomainAdminSid == true
| where NumberOfSidsInDomainAdminToken > 30

最後の30は例です。実環境の分布を確認せず、この値をそのまま本番の判定基準にしないでください。

カスタム検知へ組み込むKQL

次の例は、承認済み管理端末以外で発生した特権ログオンをカスタム検知へ移すための基本形です。

カスタム検知ではサービス側がルールのルックバックに基づいてデータを事前に絞り込むため、この例では固定のTimestamp > ago()を入れていません。Microsoftも、追加の時間条件が必要な場合を除き、カスタム検知でのTimestampによる絞り込みを避けるよう案内しています。(Microsoft Learn)

DeviceLogonEvents
| where ActionType == "LogonSuccess"
| where AdditionalFields has_any (
    "TokenHasDomainAdminSid",
    "TokenHasSchemaAdminSid",
    "TokenHasEnterpriseAdminSid"
)
| extend AF = parse_json(AdditionalFields)
| extend
    TokenHasDomainAdminSid =
        tobool(AF.TokenHasDomainAdminSid),
    TokenHasSchemaAdminSid =
        tobool(AF.TokenHasSchemaAdminSid),
    TokenHasEnterpriseAdminSid =
        tobool(AF.TokenHasEnterpriseAdminSid),
    NumberOfSidsInDomainAdminToken =
        toint(AF.NumberOfSidsInDomainAdminToken)
| where TokenHasDomainAdminSid == true
    or TokenHasSchemaAdminSid == true
    or TokenHasEnterpriseAdminSid == true
| where DeviceName !in~ (
    "dc01.contoso.local",
    "dc02.contoso.local",
    "paw01.contoso.local"
)
| extend PrivilegeContext = strcat(
    iff(TokenHasDomainAdminSid == true, "Domain Admins;", ""),
    iff(TokenHasSchemaAdminSid == true, "Schema Admins;", ""),
    iff(TokenHasEnterpriseAdminSid == true, "Enterprise Admins;", "")
)
| project
    Timestamp,
    ReportId,
    DeviceId,
    DeviceName,
    AccountSid,
    AccountDomain,
    AccountName,
    LogonType,
    RemoteDeviceName,
    RemoteIP,
    RemoteIPType,
    PrivilegeContext,
    NumberOfSidsInDomainAdminToken

contoso.localの部分は実際の端末名へ変更します。除外対象が多い場合は、端末の命名規則やデバイスグループを使った条件へ変更してください。

カスタム検知で残すべき列

Microsoft Defender for Endpointのデータを使うカスタム検知では、少なくとも次の列を残します。

  • Timestamp
  • DeviceIdまたはDeviceName
  • AccountSid
  • ReportId
  • 調査に必要なアカウント、ログオン、接続元情報

AccountSidを残すことで、アカウントエンティティへの関連付けがしやすくなります。DeviceIdまたはDeviceNameは、デバイススコープやプロセスツリーなどの処理にも利用されます。(Microsoft Learn)

なお、ReportIdは単独では一意ではありません。イベントを識別する場合は、DeviceNameとTimestampを組み合わせて扱います。(Microsoft Learn)

最初は1時間ごとの検知が扱いやすい

DeviceLogonEvents自体はContinuous、つまりNRTカスタム検知の対象テーブルです。ただし、NRTでは利用できるKQL構文に制限があり、一般提供されている列のみがサポートされます。新しいAdditionalFields内の値を使うルールは、ポータルの互換性チェックで利用可能か確認してください。(Microsoft Learn)

運用開始時は、次の順序が安全です。

  1. Advanced Huntingで7日分を手動実行する
  2. 正常な端末、アカウント、時間帯を把握する
  3. 1時間ごとのカスタム検知として登録する
  4. 誤検知を除外する
  5. NRT互換性を確認できた場合にContinuousへ移行する

カスタム検知は1回の実行につき最大150件のアラートに制限されるため、特権SIDを持つすべての正常ログオンを無条件にアラート化すると、重要なイベントが埋もれる可能性があります。作成前に通常運用を除外することが重要です。(Microsoft Learn)

調査ブックへ追加すると効果的な集計

個々のイベントだけでなく、継続的な傾向を表示すると変化を発見しやすくなります。

調査ブックやダッシュボードには、次の表示を追加すると実用的です。

表示確認できること
日別の特権ログオン件数急増した日や作業日の特定
特権グループ別の件数Domain、Schema、Enterprise Adminsの利用傾向
アカウント別上位特権アカウントの過剰利用
デバイス別上位管理権限が集中する端末や想定外端末
LogonType別Interactive、RDP、Network、Serviceなどの比率
RemoteIP別通常と異なる接続元
SID数の分布グループ構成やトークン構成の変化
証明書認証件数証明書ベース認証の増減や新規利用

日別に特権ログオンを集計する例は次のとおりです。

DeviceLogonEvents
| where Timestamp > ago(30d)
| where AdditionalFields has_any (
    "TokenHasDomainAdminSid",
    "TokenHasSchemaAdminSid",
    "TokenHasEnterpriseAdminSid"
)
| extend AF = parse_json(AdditionalFields)
| extend
    DomainAdmin = tobool(AF.TokenHasDomainAdminSid),
    SchemaAdmin = tobool(AF.TokenHasSchemaAdminSid),
    EnterpriseAdmin = tobool(AF.TokenHasEnterpriseAdminSid)
| where DomainAdmin == true
    or SchemaAdmin == true
    or EnterpriseAdmin == true
| extend PrivilegeContext = case(
    EnterpriseAdmin == true, "Enterprise Admins",
    SchemaAdmin == true, "Schema Admins",
    DomainAdmin == true, "Domain Admins",
    "Other"
)
| summarize
    Events = count(),
    Accounts = dcount(AccountSid),
    Devices = dcount(DeviceId)
    by bin(Timestamp, 1d), PrivilegeContext
| order by Timestamp desc

複数の特権SIDが同時に含まれる場合、この例では優先順位の高い1種類へ分類されます。全SIDの組み合わせを表示したい場合は、前述のstrcat()を使った方式へ変更します。

誤検知と見落としを防ぐ注意点

TokenHasCertificatePublisherSidだけで証明書ログオンと断定しない

最も間違えやすい点です。

TokenHasCertificatePublisherSidはCertificate PublishersグループSIDの存在を示します。証明書ベース認証を直接探す場合は、TokenHasThisOrgCertificateSidを使用します。

trueを侵害と断定しない

特権管理端末やドメインコントローラーでは、Domain Admins SIDを含むログオンが正規に発生します。検知は「特権コンテキストが使われた」という調査開始点です。

nullをfalseへ置き換える前に到着状況を確認する

値がない理由は、条件非該当だけではありません。ロールアウト状況、イベント種別、端末、OS、データ収集状態によってフィールドが存在しない可能性があります。

SID数に共通の固定しきい値を設定しない

SID数は組織ごとのグループ設計で変わります。全アカウントに同じ数値を適用するより、アカウント、端末、ログオン種別ごとの通常値を作ります。

parse_jsonを最初に実行しない

期間やフィールド文字列で絞り込んでからJSONを解析します。長期間の全ログへ無条件にparse_json()を実行すると、タイムアウトやCPU使用量増加の原因になります。(Microsoft Learn)

AccountNameだけでアカウントを識別しない

同じ名前のローカルアカウントや別ドメインのアカウントが存在する可能性があります。集計や許可リストでは、可能な限りAccountSidを主キーとして使います。

承認済み端末を除外しすぎない

カスタム検知ではアラートを抑えるために除外が必要ですが、調査用クエリからも消すと、正規の管理端末が侵害された場合に見落とします。

「アラートでは除外、調査ブックでは表示」という二段構成が実用的です。

運用開始までの実践手順

フィールド到着を確認する

最初の7日間は、AdditionalFieldsの生データとキー一覧を確認します。特権アカウントによる正規のテストログオンを実施し、期待するフィールドが記録されるかを確認します。

30日間の基準値を作る

アカウント、端末、LogonType、時間帯、SID数を集計します。特にDomain Adminsを日常的に使っている端末と、通常は使わない端末を区別します。

高シグナルな条件から検知を始める

最初からすべてのDomain Adminsログオンをアラート化するのではなく、次のような条件から始めます。

  • Enterprise AdminsまたはSchema Adminsのログオン
  • 承認済み管理端末以外での特権ログオン
  • Public IPを接続元とする特権ログオン
  • 初めて確認されたアカウントと端末の組み合わせ
  • 通常範囲を外れたSID数
  • 証明書認証と特権SIDの同時検出

カスタム検知と調査ブックを分ける

カスタム検知は高シグナルな条件に限定します。一方、調査ブックには正常イベントも残し、利用傾向や変化を確認できるようにします。

対応手順を決める

アラート発生時に確認する項目を事前に決めます。

  1. 作業申請や変更記録の有無
  2. アカウント所有者と利用目的
  3. ログオン先端末の役割
  4. 接続元IPとリモート端末
  5. 同一LogonIdや前後イベント
  6. グループメンバーシップの変更履歴
  7. 証明書発行やテンプレート変更の履歴
  8. 不審なプロセス、ネットワーク接続、資格情報アクセスの有無

新しい特権トークンテレメトリの価値は、単に「Domain Adminsかどうか」を表示できることだけではありません。ログオン時に実際に形成されたトークンの文脈を、端末、認証方法、SID構成と組み合わせて調査できる点にあります。

まずはAdditionalFieldsの到着確認と30日間の基準値作成から始め、通常運用との差が大きいログオンだけをカスタム検知へ昇格させると、アラート疲れを抑えながら特権アカウントの不正利用を監視できます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次