Monitoring DNS Record Changes in Microsoft Sentinel


Overview.
DNS record manipulation is a subtle yet powerful technique often used by threat actors to intercept communication, reroute authentication flows, or disrupt services. Despite its impact, DNS change monitoring is often overlooked in security monitoring strategies.
This article demonstrates how to implement DNS record change detection in Microsoft Sentinel using Azure Logic Apps and DNS-over-HTTPS (DoH). The approach enables visibility into unauthorised or accidental changes to DNS records, such as MX, A, or TXT entries, which could have significant security implications.
The solution requires no third-party agents or premium tools—only Azure-native components and a free DoH service. A sample Logic App template and KQL detection rule are provided to help you deploy the solution in minutes.
Why Monitor DNS Records?
Attackers can exploit DNS by:
Changing MX records to hijack emails.
Altering SPF or TXT records to spoof emails.
Modifying A records to redirect services.
Unless you're actively monitoring DNS, these changes can go unnoticed until it's too late. Microsoft Azure Sentinel doesn't do this natively, so we’re building a custom detection method.
What You’ll Need:
Microsoft Sentinel connected to a Log Analytics workspace.
Permissions to create Logic Apps and deploy templates.
A public DoH resolver like Cloudflare.
At least one domain name you want to monitor.
Step 1: Deploy The Azure Logic App To Collect DNS Records.
This Logic App periodically queries DNS records for your domain via DNS-over-HTTPS (DoH) using Cloudflare's free service, then forwards the data to Microsoft Sentinel via the Log Analytics Data Collector API.
- Create the Logic App:
In the Azure portal, search for Logic Apps and create a new Standard Logic App.
Assign it to the same resource group as your Sentinel workspace for ease of management.
Choose the Region closest to your resources, preferably matching Sentinel workspace location for latency optimisation.
- Configure Variables:
Add an Initialize Variable action named
recordTypes
, typeArray
.Set the value to
["A", "MX", "TXT"]
or other DNS record types you want to monitor.Optionally, add a
domains
variable with the domain(s) you want to query (e.g.,["
cdoherty.co.uk
"]
).
- Add a Recurrence Trigger:
- Configure the Logic App to run every 5 minutes or your preferred interval.
- Add a Foreach Loop Over Record Types:
- Iterate over the
recordTypes
variable.
- Within the Loop, Query DNS Records:
Add an HTTP action to call Cloudflare DoH API:
https://cloudflare-dns.com/dns-query?name=cdoherty.co.uk&type=@{items('For_each_recordType')}
Set method to GET and header
accept: application/dns-json
.
- Decode the Response:
- Use a Compose action to parse the DNS response JSON with the expression:
@json(base64ToString(body('DoH_Request')?['$content']))
- Foreach Over Each DNS Answer:
- Add another foreach loop to iterate over the
Answer
array in the parsed JSON.
- Send Data to Sentinel:
Add an Azure Log Analytics Data Collector API action.
Configure the body with the DNS record details extracted from each answer, ensuring to include:
QueriedDomain
(e.g.,cdoherty.co.uk
)RecordName
(the DNS record’s host/name)RecordType
(A, MX, TXT, etc.)TTL
RecordData
(IP address, MX target, TXT string, etc.)
Here is the complete Logic App JSON Definition:
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"contentVersion": "1.0.0.0",
"triggers": {
"5_Min_Timer": {
"type": "Recurrence",
"recurrence": {
"interval": 5,
"frequency": "Minute"
}
}
},
"actions": {
"Set_recordtypes_variable": {
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "recordTypes",
"type": "array",
"value": [
"A",
"MX",
"TXT"
]
}
]
},
"runAfter": {}
},
"For_each_recordType": {
"type": "Foreach",
"foreach": "@variables('recordTypes')",
"actions": {
"DoH_Request": {
"type": "Http",
"inputs": {
"uri": "https://cloudflare-dns.com/dns-query?name=cdoherty.co.uk&type=@{items('For_each_recordType')}",
"method": "GET",
"headers": {
"accept": "application/dns-json"
}
}
},
"Decode_Response": {
"type": "Compose",
"inputs": "@json(base64ToString(body('DoH_Request')?['$content']))",
"runAfter": {
"DoH_Request": [
"Succeeded"
]
}
},
"For_each_Answer": {
"type": "Foreach",
"foreach": "@outputs('Decode_Response')?['Answer']",
"actions": {
"Send_to_Sentinel": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['azureloganalyticsdatacollector-1']['connectionId']"
}
},
"method": "post",
"path": "/api/logs",
"headers": {
"Log-Type": "dnsmonitor"
},
"body": {
"QueriedDomain": "cdoherty.co.uk",
"RecordName": "@{item()?['name']}",
"RecordType": "@{items('For_each_recordType')}",
"TTL": "@{item()?['TTL']}",
"RecordData": "@{item()?['data']}"
}
}
}
},
"runAfter": {
"Decode_Response": [
"Succeeded"
]
}
}
},
"runAfter": {
"Set_recordtypes_variable": [
"Succeeded"
]
}
}
},
"outputs": {},
"parameters": {
"$connections": {
"type": "Object",
"defaultValue": {}
}
}
},
"parameters": {
"$connections": {
"type": "Object",
"value": {
"azureloganalyticsdatacollector-1": {
"id": "/subscriptions/fb47e429-e603-41ff-86a1-36b85336ac4a/providers/Microsoft.Web/locations/uksouth/managedApis/azureloganalyticsdatacollector",
"connectionId": "/subscriptions/fb47e429-e603-41ff-86a1-36b85336ac4a/resourceGroups/soc-cdoherty-prod-rg/providers/Microsoft.Web/connections/azureloganalyticsdatacollector-1",
"connectionName": "azureloganalyticsdatacollector-1"
}
}
}
}
}
To be continued
Subscribe to my newsletter
Read articles from Ciaran Doherty, AfCIIS, MBCS directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
