Skip to main content

Command Palette

Search for a command to run...

Monitoring DNS Record Changes in Microsoft Sentinel

Detecting Modifications to MX, A, and TXT Records Using Azure Logic Apps and DNS-over-HTTPS.

Updated
6 min read
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.

A recent report on Risky Business focused on attacks by a group, seemingly exhibiting similar TTPs to Scattered Spider, where MX records for a target's domain were altered to redirect inbound emails. This easily allows for the compromise of multiple other platforms and accounts, as well as inhibiting recovery due to the inability of the victim to receive their own emails.

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.

Some monitoring platforms (such as PRTG) allow for monitoring of specific DNS records and alerting on changes. However for any organizations that don't have this functionality available, a dedicated alternative based in Azure is useful.

This monitoring solution is based on an Azure Logic App which sends details of DNS records into Sentinel where an analytics rule can monitor for changes.

If possible I'd also suggest taking audit logs from the DNS hosting platform to look for these kinds of changes, but some platforms don't provide such functionality.


Prerequisites.

  • 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.


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.

  1. In the Azure portal, search for Logic Apps and create a new Standard Logic App.

  2. Assign it to the same resource group as your Sentinel workspace for ease of management.

  3. Choose the Region closest to your resources, preferably matching Sentinel workspace location for latency optimisation.

Configure Variables.

  1. Add an Initialise Variable action named recordTypes (type: Array).

    1. Set the value to ["A", "MX", "TXT"] or include any additional record types you wish to track.
  2. Optionally, create another variable named domains with your domain list, for example: ["domain.co.uk"].

Add a Recurrence Trigger.

  1. Configure the Logic App to run on a defined schedule, for example, every 5 minutes, depending on how frequently you wish to monitor changes.

Loop Through Record Types.

Within the Logic App, create a loop to iterate through each DNS record type stored in the recordTypes array.

For every type, the Logic App will query Cloudflare’s DNS-over-HTTPS endpoint, interpret the results, and forward the findings to Microsoft Sentinel.

The loop begins by making an HTTP GET request to Cloudflare’s API using the following structure:

https://cloudflare-dns.com/dns-query?name=domain.co.uk&type=@{items('For_each_recordType')}

The request header must specify that the response should be in DNS JSON format:

accept: application/dns-json

Once the request completes, the response can be parsed. In most Logic App environments, the HTTP connector automatically returns JSON data, but if your configuration encodes it in base64, you can decode it using a Compose action with this expression:

@json(base64ToString(body('DoH_Request')?['$content']))

The output from this stage contains an array named Answer, representing the DNS responses. The next loop iterates through this array so that each record—whether it’s an A, MX, or TXT entry—is processed individually.

For each record, the Logic App then sends structured data to Microsoft Sentinel through the Azure Log Analytics Data Collector connector. The payload includes key details such as the domain, record type, name, TTL, and record data, along with a timestamp for correlation. A typical payload looks like this:

{
  "TimeGenerated": "@{utcNow()}",
  "QueriedDomain": "domain.co.uk",
  "RecordName": "@{item()?['name']}",
  "RecordType": "@{items('For_each_recordType')}",
  "TTL": "@{item()?['TTL']}",
  "RecordData": "@{item()?['data']}"
}

Make sure the request headers include:

Log-Type: dnsmonitor
Content-Type: application/json

Once ingested, Sentinel automatically creates a custom log table named dnsmonitor_CL, where each row represents a single DNS record snapshot. These entries can then be queried in KQL to detect any changes or anomalies over time.


Logic App JSON Definition.

Below 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=yourdomain.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": "domain.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/yourresourcegroupnamehere/providers/Microsoft.Web/connections/azureloganalyticsdatacollector-1",
          "connectionName": "azureloganalyticsdatacollector-1"
        }
      }
    }
  }
}

Remember to change your domain name in the uri input string on line 39, and your resource group name on 110.


Microsoft Sentinel (SIEM) Alerting.

Once the information is ingested within Microsoft Sentinel, a straightforward analytics rule can monitor for additions and deletions. This is configured below as a NRT (near real time) rule. If you run this less frequently, you may need to modify the time thresholds.

let records=dnsmonitor_CL
    | mv-expand parse_json(Answer_s)
    | extend
        domain=tostring(parse_json(Question_s)[0]['name']),
        answer=tostring(Answer_s['data'])
    | summarize firstSeen=min(TimeGenerated), lastSeen=max(TimeGenerated) by domain, answer;
let lastRun=toscalar(records
    | summarize max(lastSeen));
let newRecords=records
    | where firstSeen > ago(8m)
    | extend action="New";
let deletedRecords=records
    | where lastSeen < ago(8m)
    | extend action="Removed";
union newRecords, deletedRecords

Security Operations

Part 3 of 6

A technical series on SOC operations and SIEM engineering, covering SIEM design, threat detection, hunting, automation, containment, and continuous monitoring across Microsoft 365 cloud environments.

Up next

Kusto Query Language (KQL) Queries For SOC Investigation.

A Field-Tested Collection of KQL Queries for Microsoft Sentinel Analysts to Accelerate Detection, Investigation, and Threat Correlation.