How to get Antivirus-related Data from Microsoft Defender for Endpoint using Intune and Graph API. Hello everyone! In this episode, I would like to tell you how I tried to get automatically antivirus-related data (current status, engine and signature version, last full scan date) from Microsoft Defender for Endpoint using Microsoft Intune and the Graph API.
Why is this necessary?
You might assume that if the Defender for Endpoint agent is installed on the host, everything should be fine automatically. But in fact, the antivirus engine and signature versions may be outdated, real-time protection may be disabled. And so all this needs to be monitored.
Grapf API
This will be the third episode about Microsoft Enterprise Security APIs. The first was about Defender and Defender API, the second was about Intune and the Intune API. And today I’m going to talk about the Grapf API, which should probably replace all the other APIs and should be more logical and easier. Although in my opinion it is even strangier and poorly documented. I didn’t like it.
App Registration
To get started with the Graph API, you need to register an Azure app. I described this in detail in the Defender for Endpoint episode. Here I am only listing the Microsoft Graph permissions I used:
- Device.Read.All
- DeviceManagementConfiguration.Read.All
- DeviceManagementManagedDevices.Read.All
Access Token
To get a token, execute the following code:
import json
import requests
def get_token():
body = {
"resource": "https://graph.microsoft.com",
"client_id": azure_appId,
"client_secret": azure_appSecret,
"grant_type": "client_credentials"
}
response = requests.post("https://login.microsoft.com/" + azure_tenantId + "/oauth2/token", data=body)
return response.json()["access_token"]
And then the question is how to use this token.
What didn’t work
Now I will list what I tried and what did not work:
- Get windowsProtectionState. Because it’s not clear where to get deviceManagementScriptId and deviceManagementScriptDeviceStateId, and why is it needed.
- Get deviceManagementReports. Doesn’t work.
- deviceManagement/managedDevices$expand=windowsProtectionState. Doesn’t work.
What kind of worked
This kind of worked. You can get all device IDs with https://graph.microsoft.com/beta/deviceManagement/managedDevices, and then get windowsProtectionState results with separate queries https://graph.microsoft.com/beta/deviceManagement/managedDevices/{DeviceID}/windowsProtectionState. This works, but for a large number of hosts, making a large number of requests is crazy.
What worked
Finally export of DefenderAgents report worked. First of all, you need to configure the report parameters and order it. You will receive a unique report ID.
import json
import requests
import zipfile
import csv
auth_token = get_token()
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': "Bearer " + auth_token
}
body = {
"reportName": "DefenderAgents",
"select": [
"DeviceId",
"DeviceName",
"DeviceState",
"PendingFullScan",
"PendingReboot",
"PendingManualSteps",
"PendingOfflineScan",
"CriticalFailure",
"MalwareProtectionEnabled",
"RealTimeProtectionEnabled",
"NetworkInspectionSystemEnabled",
"SignatureUpdateOverdue",
"QuickScanOverdue",
"FullScanOverdue",
"RebootRequired",
"FullScanRequired",
"EngineVersion",
"SignatureVersion",
"AntiMalwareVersion",
"LastQuickScanDateTime",
"LastFullScanDateTime",
"LastQuickScanSignatureVersion",
"LastFullScanSignatureVersion",
"LastReportedDateTime",
"UPN",
"UserEmail",
"UserName"
]
}
response = requests.post("https://graph.microsoft.com/beta/deviceManagement/reports/exportJobs", data=json.dumps(body), headers=headers)
report_id = response.json()['id']
print(json.dumps(response.json(), indent=4))
Then you wait and check the status of the report using the ID. Until it is “completed”.
status = ""
while status != "completed":
# report_id = "DefenderAgents_0dc67ffe-6fa4-4fcf-bd1f-2e4c94ca2612"
url = "https://graph.microsoft.com/beta/deviceManagement/reports/exportJobs('" + report_id + "')"
response = requests.get(url, headers=headers)
status = response.json()['status']
print("Waiting...")
Then you download the report as a zipped csv and save it.
r = requests.get(response.json()['url'])
f = open('data/defender_agents.zip', 'wb')
f.write(r.content)
It remains only to turn the contents of the file into a convenient dict. You can do it like this:
myzip = zipfile.ZipFile('data/defender_agents.zip')
myfile = myzip.open(myzip.namelist()[0])
lines = str(myfile.read()).split("\\r\\n")
rows = list()
for row in csv.reader(lines):
rows.append(row)
result_dict = dict()
for row_mumber in range(1, len(rows)):
if len(rows[row_mumber]) == len(rows[0]):
line_dict = dict()
for param_number in range(0, len(rows[0])):
line_dict[rows[0][param_number]] = rows[row_mumber][param_number]
result_dict[line_dict['DeviceName'].lower()] = line_dict
print(result_dict)
Hi! My name is Alexander and I am a Vulnerability Management specialist. You can read more about me here. Currently, the best way to follow me is my Telegram channel @avleonovcom. I update it more often than this site. If you haven’t used Telegram yet, give it a try. It’s great. You can discuss my posts or ask questions at @avleonovchat.
А всех русскоязычных я приглашаю в ещё один телеграмм канал @avleonovrus, первым делом теперь пишу туда.
Pingback: И тут было бы справедливо спросить: "А не ты ли, Александр, совсем недавно топил за эти самые облачные ИБ сервисы Microsoft, а теперь получаетс
Pingback: Vulnerability Management news and publications #2 | Alexander V. Leonov
Pingback: Microsoft Defender for Endpoint: The Latest Versions of Antivirus Engine & Signatures | Alexander V. Leonov