脆弱性情報をソフトウェア単位で出力する
脆弱性情報は Advanced Hunting 機能を使用して出力します。詳細は以前の記事をご参照ください。
DeviceTvmSoftwareVulnerabilities では以下のフィールドの値が出力されます。
左右にスクロールしてご覧ください。
以前の記事の通り、以下クエリのように VulnerabilitySeverityLevel カラム別に件数を集計することができます。しかし、CVE ID 別に集計されるため、組織レベルによっては非常に多くの脆弱性情報が出力されます。
let rank = pack_array("Low","Medium","High","Critical");
DeviceTvmSoftwareVulnerabilities
| summarize count() by VulnerabilitySeverityLevel
| extend rankRowNum = array_index_of(rank, VulnerabilitySeverityLevel)
| sort by rankRowNum desc | project-away rankRowNum
組織内のデバイスを管理するにあたって、個々のデバイスのそれぞれに対して脆弱性の危険度を判断し、脆弱性に対応することは非常に労力がかかります。
組織のセキュリティを管理する立場からすると、危険な脆弱性を含むソフトウェアを特定したうえで、当該ソフトウェアのバージョンアップを組織内に促すようにしたいです。そこで出力される脆弱性情報をソフトウェアとそのバージョンで整理し、件数を少なくすることで意思決定を早める一助とします。
以下のクエリではソフトウェアバージョンごとに脆弱性情報を集計し、当該ソフトウェアに含まれる脆弱性の CVSS スコア最大値、CVSS メトリクスの最も影響の大きいもの、Severity の最大値を出力し、当該ソフトウェアがインストールされているアセットの台数も出力しています。ソフトウェアごとに出力した危険度を確認し、組織に多数インストールされているソフトウェアの脆弱性に関しては注意喚起、少数しかインストールされていないソフトウェアには個別で対応依頼を促すなどの方針を決めることができます。
let rank = pack_array("Low","Medium","High","Critical");
DeviceTvmSoftwareVulnerabilities
| join kind=leftouter DeviceTvmSoftwareVulnerabilitiesKB on CveId
| extend VulnerabilitySeverityLevelInt = array_index_of(rank, VulnerabilitySeverityLevel)
| summarize MaxCvssScore = max(CvssScore), MaxSeverityInt = max(VulnerabilitySeverityLevelInt), make_set(CvssVector) by SoftwareVendor, SoftwareName, SoftwareVersion, DeviceId
| summarize arg_max(MaxSeverityInt, *), DeviceCount = count() by SoftwareVendor, SoftwareName, SoftwareVersion
| extend MaxSeverityLevel = tostring(rank[MaxSeverityInt])
| extend Cvss_AV = case(
tostring(set_CvssVector) contains "AV:N", "N",
tostring(set_CvssVector) contains "AV:A", "A",
tostring(set_CvssVector) contains "AV:L", "L",
tostring(set_CvssVector) contains "AV:P", "P",
"")
| extend Cvss_AC = case(
tostring(set_CvssVector) contains "AC:L", "L",
tostring(set_CvssVector) contains "AC:M", "M",
tostring(set_CvssVector) contains "AC:H", "H",
"")
| extend Cvss_Au = case(
tostring(set_CvssVector) contains "Au:N", "N",
tostring(set_CvssVector) contains "Au:S", "S",
tostring(set_CvssVector) contains "Au:M", "M",
"")
| extend Cvss_PR = case(
tostring(set_CvssVector) contains "PR:N", "N",
tostring(set_CvssVector) contains "PR:L", "L",
tostring(set_CvssVector) contains "PR:H", "H",
"")
| extend Cvss_UI = case(
tostring(set_CvssVector) contains "UI:N", "N",
tostring(set_CvssVector) contains "UI:R", "R",
"")
| extend Cvss_S = case(
tostring(set_CvssVector) contains "S:U", "U",
tostring(set_CvssVector) contains "S:C", "C",
"")
| extend Cvss_C = case(
tostring(set_CvssVector) contains "C:H", "H",
tostring(set_CvssVector) contains "C:C", "C",
tostring(set_CvssVector) contains "C:L", "L",
tostring(set_CvssVector) contains "C:P", "P",
tostring(set_CvssVector) contains "C:N", "N",
"")
| extend Cvss_I = case(
tostring(set_CvssVector) contains "I:H", "H",
tostring(set_CvssVector) contains "I:C", "C",
tostring(set_CvssVector) contains "I:L", "L",
tostring(set_CvssVector) contains "I:P", "P",
tostring(set_CvssVector) contains "I:N", "N",
"")
| extend Cvss_A = case(
tostring(set_CvssVector) contains "A:H", "H",
tostring(set_CvssVector) contains "A:C", "C",
tostring(set_CvssVector) contains "A:L", "L",
tostring(set_CvssVector) contains "A:P", "P",
tostring(set_CvssVector) contains "A:N", "N",
"")
| extend Cvss_E = case(
tostring(set_CvssVector) contains "E:H", "H",
tostring(set_CvssVector) contains "E:F", "F",
tostring(set_CvssVector) contains "E:P", "P",
tostring(set_CvssVector) contains "E:POC", "POC",
tostring(set_CvssVector) contains "E:U", "U",
tostring(set_CvssVector) contains "E:X", "X",
tostring(set_CvssVector) contains "E:ND", "ND",
"")
| extend Cvss_RL = case(
tostring(set_CvssVector) contains "RL:U", "U",
tostring(set_CvssVector) contains "RL:W", "W",
tostring(set_CvssVector) contains "RL:WF", "WF",
tostring(set_CvssVector) contains "RL:T", "T",
tostring(set_CvssVector) contains "RL:TF", "TF",
tostring(set_CvssVector) contains "RL:O", "O",
tostring(set_CvssVector) contains "RL:OF", "OF",
tostring(set_CvssVector) contains "RL:X", "X",
tostring(set_CvssVector) contains "RL:ND", "ND",
"")
| extend Cvss_RC = case(
tostring(set_CvssVector) contains "RC:C", "C",
tostring(set_CvssVector) contains "RC:R", "R",
tostring(set_CvssVector) contains "RC:UR", "UR",
tostring(set_CvssVector) contains "RC:U", "U",
tostring(set_CvssVector) contains "RC:UC", "UC",
tostring(set_CvssVector) contains "RC:X", "X",
tostring(set_CvssVector) contains "RC:ND", "ND",
"")
| project-away set_CvssVector, MaxSeverityInt
また、ソフトウェアごとに集計した情報も以下のように集計可能です。
let rank = pack_array("Low","Medium","High","Critical");
DeviceTvmSoftwareVulnerabilities
| join kind=leftouter DeviceTvmSoftwareVulnerabilitiesKB on CveId
| extend VulnerabilitySeverityLevelInt = array_index_of(rank, VulnerabilitySeverityLevel)
| summarize MaxCvssScore = max(CvssScore), MaxSeverityInt = max(VulnerabilitySeverityLevelInt), make_set(CvssVector) by SoftwareVendor, SoftwareName, SoftwareVersion, DeviceId
| summarize arg_max(MaxSeverityInt, *), DeviceCount = count() by SoftwareVendor, SoftwareName, SoftwareVersion
| extend MaxSeverityLevel = tostring(rank[MaxSeverityInt])
| extend Cvss_AV = case(
tostring(set_CvssVector) contains "AV:N", "N",
tostring(set_CvssVector) contains "AV:A", "A",
tostring(set_CvssVector) contains "AV:L", "L",
tostring(set_CvssVector) contains "AV:P", "P",
"")
| extend Cvss_AC = case(
tostring(set_CvssVector) contains "AC:L", "L",
tostring(set_CvssVector) contains "AC:M", "M",
tostring(set_CvssVector) contains "AC:H", "H",
"")
| extend Cvss_Au = case(
tostring(set_CvssVector) contains "Au:N", "N",
tostring(set_CvssVector) contains "Au:S", "S",
tostring(set_CvssVector) contains "Au:M", "M",
"")
| extend Cvss_PR = case(
tostring(set_CvssVector) contains "PR:N", "N",
tostring(set_CvssVector) contains "PR:L", "L",
tostring(set_CvssVector) contains "PR:H", "H",
"")
| extend Cvss_UI = case(
tostring(set_CvssVector) contains "UI:N", "N",
tostring(set_CvssVector) contains "UI:R", "R",
"")
| extend Cvss_S = case(
tostring(set_CvssVector) contains "S:U", "U",
tostring(set_CvssVector) contains "S:C", "C",
"")
| extend Cvss_C = case(
tostring(set_CvssVector) contains "C:H", "H",
tostring(set_CvssVector) contains "C:C", "C",
tostring(set_CvssVector) contains "C:L", "L",
tostring(set_CvssVector) contains "C:P", "P",
tostring(set_CvssVector) contains "C:N", "N",
"")
| extend Cvss_I = case(
tostring(set_CvssVector) contains "I:H", "H",
tostring(set_CvssVector) contains "I:C", "C",
tostring(set_CvssVector) contains "I:L", "L",
tostring(set_CvssVector) contains "I:P", "P",
tostring(set_CvssVector) contains "I:N", "N",
"")
| extend Cvss_A = case(
tostring(set_CvssVector) contains "A:H", "H",
tostring(set_CvssVector) contains "A:C", "C",
tostring(set_CvssVector) contains "A:L", "L",
tostring(set_CvssVector) contains "A:P", "P",
tostring(set_CvssVector) contains "A:N", "N",
"")
| extend Cvss_E = case(
tostring(set_CvssVector) contains "E:H", "H",
tostring(set_CvssVector) contains "E:F", "F",
tostring(set_CvssVector) contains "E:P", "P",
tostring(set_CvssVector) contains "E:POC", "POC",
tostring(set_CvssVector) contains "E:U", "U",
tostring(set_CvssVector) contains "E:X", "X",
tostring(set_CvssVector) contains "E:ND", "ND",
"")
| extend Cvss_RL = case(
tostring(set_CvssVector) contains "RL:U", "U",
tostring(set_CvssVector) contains "RL:W", "W",
tostring(set_CvssVector) contains "RL:WF", "WF",
tostring(set_CvssVector) contains "RL:T", "T",
tostring(set_CvssVector) contains "RL:TF", "TF",
tostring(set_CvssVector) contains "RL:O", "O",
tostring(set_CvssVector) contains "RL:OF", "OF",
tostring(set_CvssVector) contains "RL:X", "X",
tostring(set_CvssVector) contains "RL:ND", "ND",
"")
| extend Cvss_RC = case(
tostring(set_CvssVector) contains "RC:C", "C",
tostring(set_CvssVector) contains "RC:R", "R",
tostring(set_CvssVector) contains "RC:UR", "UR",
tostring(set_CvssVector) contains "RC:U", "U",
tostring(set_CvssVector) contains "RC:UC", "UC",
tostring(set_CvssVector) contains "RC:X", "X",
tostring(set_CvssVector) contains "RC:ND", "ND",
"")
| project-away set_CvssVector, MaxSeverityInt, DeviceId
| summarize sum(DeviceCount) by MaxSeverityLevel
| extend rankRowNum = array_index_of(rank, MaxSeverityLevel)
| sort by rankRowNum desc | project-away rankRowNum
SBOM の確認方法
ソフトウェアごとに整理すると以下のように openssl といった OSS が出力されることがあります。
これらをバージョンアップまたはアンインストールしようとした場合を考えます。これらのソフトウェアは直接インストールされているわけではなく、異なるソフトウェアに同梱されているいわゆる「SBOM」に該当するものであるため、どのソフトウェアに同梱されているかがわからなければバージョンアップやアンインストールを行うことができません。
そこで、これらのソフトウェアがインストールされている場所を確認するために DeviceTvmSoftwareEvidenceBeta テーブルを leftouter join します。
※このテーブルはその名が示す通りベータ版ではあるため、今後テーブル名、カラム名の変更や出力内容が変わる可能性があります。必要に応じて変更したうえでご利用ください。
DeviceTvmSoftwareEvidenceBeta テーブルには DiskPaths というフィールドがあり、こちらにインストール先の情報が記載されていることがあります。
※必ずしも記載されている訳ではないようです。仕様が確認できた場合は追記します。
インストール先の情報からはどのソフトウェアに同梱されているかわかる情報も記載されているため、これらを確認することでアップデートまたはアンインストールの検討を行うことができます。
その他、RegistryPaths といったカラムにも情報が記載されているケースがあります。こちらもソフトウェアの特定に利する内容です。詳細は以下をご確認ください。
※参考:マイクロソフト社「Microsoft Learn Challenge:DeviceTvmSoftwareEvidenceBeta」
まとめ
ここまでお読みいただきありがとうございました。膨大な脆弱性情報をソフトウェアごとに整理する方法や、ソフトウェアのインストール先を調査する方法についてご紹介しました。これらを活用いただくことで脆弱性に対する対応方針の一助になれば幸いです。
また、以前の記事 では脆弱性に対して Known Vulnerabilities Catalog の情報を付加して優先度判断材料を増やすクエリについても紹介しています。ぜひご参照ください。
当社では Microsoft Defender for Endpoint のひとつの機能である脆弱性可視化機能を有効活用するための仕組みの検討を進めており、お悩みのお客様と共にベストプラクティスを検討できればと思っておりますので、ご興味がございましたらお気軽にお問い合わせください。