Data preparation

Vulnerability Data schema

The vulnerability data is prepared by Microsoft Defender and can be viewed on the Microsoft Defender Advanced Hunting Portal

Table Name Description Fields
DeviceTvmSoftwareVulnerabilities Contains vulnerability data of the clients DeviceID, DeviceName, OSPlatform, OSVersion, OSArchitecture, Software-Vendor, SoftwareName, SoftwareVersion, CveId, VulnerabilitySeverityLevel, RecommendedSecurityUpdate, RecommendedSecurityUpdateId, CveTags, CveMitigationStatus, MachineGroup
DeviceTvmSoftwareVulnerabilitiesKB Contains data of a specific vulnerability CveId, CvssScore, CvssVector, CveSupportability, IsExploitAvailable, Vulner-abilitySeverityLevel, LastModifiedTime, PublishedDate, VulnerabilityDescrip-tion, AffectedSoftware, TenantId, Type, SourceSystem
DeviceTvmSoftwareEvidenceBeta Contains registry and disk paths of a software DeviceId, SoftwareVendor, SoftwareName, SoftwareVersion, RegistryPaths, DiskPaths, LastSeenTime, TenantId, Type, SourceSystem, MachineGroup

Table joins overview

kql table joins overview

Final result and source for each field

Field ⬇ Source Table ➡ DeviceTvmSoftwareVulnerabilities Asset DeviceTvmSoftwareVulnerabilitiesKB DeviceTvmSoftwareEvidenceBeta
Host (x) x    
VulnerabilitySeverityLevel     x  
DiskPath       x
RegistryPath       x
CveID     x  
CvssScore     x  
RecommendedSecurityUpdate x      
contact   x    
SoftwareVendor (x)     x
SoftwareName (x)     x
SoftwareVersion (x)     x
VulnerabilityDescription     x  

“x” indicates the main source for the field and “(x)” is where the field also exists but is only used for joining!

Full Query

$kqlQuery = @"
let asset = datatable(HostSrc: string, contact: string, oe: string)
[
    $assetString
];

let excludes = datatable(EntryUUID: string)
[
    $excludesString
];
DeviceTvmSoftwareVulnerabilities 
| extend HostSrc = replace_string(DeviceName, ".flo7000.ch", "")
| join kind=inner (asset) on HostSrc
| join kind=leftouter (DeviceTvmSoftwareVulnerabilitiesKB) on CveId
| join kind=leftouter (DeviceTvmSoftwareEvidenceBeta) on DeviceId, SoftwareName, SoftwareVendor
| extend EntryUUID = strcat(HostSrc, "-", CveId, "-",SoftwareVendor, "-",SoftwareName, "-",SoftwareVersion)
| join kind=leftanti (excludes) on EntryUUID
| extend FirstDiskPath = tostring(parse_json((DiskPaths))[0]) // Extract the first disk path
| extend FirstRegistryPath = tostring(parse_json((RegistryPaths))[0]) // Extract the first registry path
| sort by HostSrc, CveId, FirstRegistryPath, FirstDiskPath
// Summarize the query by taking the most recent entry for each HostSrc and CveId
| summarize arg_max(CveId, *) by DeviceName, CveId
| project HostName = HostSrc, FirstDiskPath, FirstRegistryPath, RecommendedSecurityUpdate, 
    VulnerabilityDescription, CvssScore, CveId, SoftwareVendor, SoftwareName, SoftwareVersion, VulnerabilitySeverityLevel, IsExploitAvailable, contact, OSPlatform
| order by HostName
| limit 200
"@

$response = Invoke-DefenderATPQuery -Query $kqlQuery
$result = $response.Result