While working on a solution, i observed an important, yet under-documented difference in log integrity between Azure’s standard monitoring architecture and third-party routing. This difference, which we'll detail later, highlights how architectural choices can potentially undermine the collected data's integrity. Specifically, this flaw creates a forensic blind spot, allowing a determined attacker to easily manipulate or delete critical log entries to hide their tracks.
Normally the way you’d want to ship Windows Event Logs is by using the Azure Monitoring Agent (AMA) installed on Virtual Machines (VMs) with instructions from Azure Data Collection Rules (DCR). The DCRs tells the agent what to collect, and how to forward these to a Log Analytics Workspace. In fact, DCR is actually a central and fundamental part of the Azure Monitor service together with the Azure Log Ingestion Pipeline. It acts as a liaison between the data sources (like the AMA on a VM) and the Azure Log Ingestion Pipeline/Log Analytics Workspace, defining the transformation and destination for the collected data.
But what happens when you need to send logs to an external system and avoid using Log Analytics Workspace as the source of truth?
In most cases the logical thing to do would involve sending these events to an Event Hub and from there forward them to a different SIEM.
However, if you want to avoid using Log Analytics Workspace as the trusted source for logs, and instead opt-in to forward these events to an Event Hub (which in most cases would be the logical thing to do if using a SIEM like Splunk) the authentication is at the sender level, meaning it is between Azure Monitor and the Event Hub namespace itself. This connection authenticates the sender pipeline, but does not provide event-level integrity stamping on the payload, in contrast to sending the events to the Log Analytics ingestion pipeline which takes into account the trusted authentication provided by Azure Monitor and stamps the JSON record with trusted unimmutable fields.
So let us see how this would normally work in such a scenario.
For simplicity, we assume there is already a VM-A and a Event Hub in the ae3c87f8-d90f-4f75-b866-9d44b31043af
subscription.
1. For the AMA to be able to send data to the Event Hub, we need to provide the VM with the Azure Event Hub Data Sender
role assignment to the Event Hub namespace, or the specific Event Hub itself.
2. We create an extension set with the Azure Monitor Windows Agent:
❯ az vm extension set \
--name AzureMonitorWindowsAgent \
--publisher Microsoft.Azure.Monitor \
--ids /subscriptions/ae3c87f8-d90f-4f75-b866-9d44b31043af/resourceGroups/VM-A-RG/providers/Microsoft.Compute/virtualMachines/VM-A \
--enable-auto-upgrade true
Note: When you create a DCR in the Azure portal and associate it with a virtual machine, the portal will check if the AMA is already installed on that VM. If it's not, it will automatically deploy the agent to the VM as a VM extension.
3. We then apply the DCR (this needs to be done using the REST API as the Azure Portal and standard CLI commands do not support the Event Hub destination):
ResourceId="/subscriptions/ae3c87f8-d90f-4f75-b866-9d44b31043af/resourceGroups/VM-A-RG/providers/Microsoft.Insights/dataCollectionRules/windowsEvents" \
FilePath="data.json" \
❯ az rest --method put --url $ResourceId"?api-version=2023-03-11" --body @$FilePath
This is where the documentation gets a bit fuzzy. While Microsoft explains how to configure this for a Log Analytics Workspace using the portal , there is no way of actually setting this up for an Event Hub. To configure this you need to generate the DCR using a JSON file as described here.
4. Associate the VM with DCR (this must be in the same subscription as the VM):
❯ az monitor data-collection rule association create \
--name "test-vm-association" \
--rule-id "/subscriptions/ae3c87f8-d90f-4f75-b866-9d44b31043af/resourceGroups/VM-A-RG/providers/Microsoft.Insights/dataCollectionRules/windowsEvents" \
--resource "/subscriptions/ae3c87f8-d90f-4f75-b866-9d44b31043af/resourceGroups/VM-A-RG/providers/Microsoft.Compute/virtualMachines/VM-A"
With all prerequisites met and the association deployed, the Azure Monitoring Agent will immediately begin the stream of Windows Event Logs to the Event Hub destination.
Here is an example of a JSON object pulled from the Event Hub we just configured:
{
"records": [
{
"category": "WindowsEventLogsTable",
"level": "Informational",
"properties": {
"Channel": "System",
"EventCategory": "0",
"EventDescription": "The Background Intelligent Transfer Service service entered the stopped state.",
"EventLevel": "4",
"EventNumber": "7036",
"EventRecordId": "1188",
"Keywords": "0x8080000000000000",
"LoggingComputer": "VM-A",
"PublisherId": "{555908d1-a6d7-4695-8e1e-26931d2012f4}",
"PublisherName": "Service Control Manager",
"RawXml": "<Event xmlns='<http://schemas.microsoft.com/win/2004/08/events/event>'><System><Provider Name='Service Control Manager' Guid='{555908d1-a6d7-4695-8e1e-26931d2012f4}' EventSourceName='Service Control Manager'/><EventID Qualifiers='16384'>7036</EventID><Version>0</Version><Level>4</Level><Task>0</Task><Opcode>0</Opcode><Keywords>0x8080000000000000</Keywords><TimeCreated SystemTime='2025-08-28T16:31:55.6616074Z'/><EventRecordID>1188</EventRecordID><Correlation/><Execution ProcessID='640' ThreadID='3504'/><Channel>System</Channel><Computer>vmss-test000000</Computer><Security/></System><EventData><Data Name='param1'>Background Intelligent Transfer Service</Data><Data Name='param2'>stopped</Data><Binary>42004900540053002F0031000000</Binary></EventData></Event>",
"RenderingInfo": "",
"Role": "VM-A-RG",
"RoleInstance": "b52bb2d5-5f96-4c9d-ad81-3769d1be398e",
"Tenant": "1d6e2c75-ed31-4266-95d6-f175dc4fdb09",
"TimeCreated": "2025-08-28T16:31:55.6616074Z",
"UserName": "N/A"
},
"resourceId": "/subscriptions/ae3c87f8-d90f-4f75-b866-9d44b31043af/resourceGroups/VM-A-RG/providers/Microsoft.Compute/virtualMachine/VM-A",
"time": "2025-08-28T16:31:55.6616074Z"
}
]
}
Examining the raw JSON object, we see the complete output delivered directly from the AMA to the Azure Monitor Service pipeline and into the Event Hub. Fields like resourceId
and TimeCreated
are present, confirming the Event Log details and resource origin, based exactly on the instructions defined in the DCR. The data flow is technically successful, and on the surface, this log record contains all the necessary source information for a downstream SIEM. So far so good, right?
But as a thought experiment, what if I access the VM and manually write an arbitrary message into the Windows Event Log system?
I won’t go into details on how the procedure of doing this is done, but here is the result from testing this experiment:
Received event from partition: 1.
Message body: {"whatever": "whatever"}
Properties: {}
System properties: {b'x-opt-sequence-number-epoch': -1, b'x-opt-sequence-number': 18, b'x-opt-offset': b'30888', b'x-opt-enqueued-time': 1756400187397}
This is the payload we just created captured by the Event Hub.
Interestingly the Message body contains exactly a JSON object just like the one received from a AMA associated with a DCR.
What if i generate a complete JSON object with the exact same data as the DCR instructions?
To be able to change the actual JSON object that the DCR creates, we need to do something called transformKQL on the DCR object, which is described here.
So let’s try…
I log back into the VM, write a new event containing (almost) the exact instructions as the DCR generated using the transformation query, and immediately monitor the Event Hub.
A few seconds later:
{
"records": [
{
"category": "WindowsEventLogsTable",
"level": "Informational",
"properties": {
"Channel": "System",
"EventCategory": "0",
"EventDescription": "The Background Intelligent Transfer Service service entered the stopped state.",
"EventLevel": "4",
"EventNumber": "7036",
"EventRecordId": "1188",
"Keywords": "0x8080000000000000",
"LoggingComputer": "What-Ever-Computer-Name-I-Want",
"PublisherId": "{555908d1-a6d7-4695-8e1e-26931d2012f4}",
"PublisherName": "Service Control Manager",
"RawXml": "<Event xmlns='<http://schemas.microsoft.com/win/2004/08/events/event>'><System><Provider Name='Service Control Manager' Guid='{555908d1-a6d7-4695-8e1e-26931d2012f4}' EventSourceName='Service Control Manager'/><EventID Qualifiers='16384'>7036</EventID><Version>0</Version><Level>4</Level><Task>0</Task><Opcode>0</Opcode><Keywords>0x8080000000000000</Keywords><TimeCreated SystemTime='2025-08-28T16:31:55.6616074Z'/><EventRecordID>1188</EventRecordID><Correlation/><Execution ProcessID='640' ThreadID='3504'/><Channel>System</Channel><Computer>vmss-test000000</Computer><Security/></System><EventData><Data Name='param1'>Background Intelligent Transfer Service</Data><Data Name='param2'>stopped</Data><Binary>42004900540053002F0031000000</Binary></EventData></Event>",
"RenderingInfo": "",
"Role": "VM-A-RG",
"RoleInstance": "b52bb2d5-5f96-4c9d-ad81-3769d1be398e",
"Tenant": "1d6e2c75-ed31-4266-95d6-f175dc4fdb09",
"TimeCreated": "2025-08-28T16:31:55.6616074Z",
"UserName": "N/A"
},
"resourceId": "/subscriptions/ae3c87f8-d90f-4f75-b866-9d44b31043af/resourceGroups/VM-A-RG/providers/Microsoft.Compute/virtualMachine/VM-A",
"time": "2025-08-28T16:31:55.6616074Z"
}
]
}
Notice the difference? In this example, i chose to change the “LoggingComputer”
field to “What-Ever-Computer-Name-I-Want”
.
Because the Event Hub only validates the senders pipeline identity, specifically the Managed Identity that holds the role assignment of the VM to the Event Hub (Azure Event Hub Data Sender
), this is accepted by the Event Hub, and the downstream SIEM has now received a perfectly valid-looking log entry with a forged field.
So why is this allowed to happen?
Because the nature of an Event Hub is designed as a data transit/transport layer, not a validation or security service. As long as an entity has permission to send data to the Event Hub, it happily accepts any message it receives and forwards it to the desired destination. This is by design.
So, would this flawed integrity happen if sending the same data to a Log Analytics Workspace?
The short answer: no.
When setting the destination in the DCR to a Log Analytics Workspace, the flow of information will be different:
- AMA on the VM asks for a token from the IMDS (to send events to LAW as described in DCR)
- AMA receives the JWT Token, and sends the token along with the event data.
- Azure Monitor receives the data (event) and token. Validates the token by cross-referencing the
xms_mirid
andoid
claims with ARM to verify authenticity. - Based on the signed token from the AMA (verified against ARM), the Azure Monitor service knows with certainty the sender's identity and stamps the data with several immutable integrity fields, including the correct
_ResourceId
field.
To show the difference, here is what a payload would look like when ingested into a Log Analytics Workspace (Notice the additional fields prefixed with “_”):
{
"records": [
{
"_Internal_WorkspaceResourceId": "/subscriptions/ae3c87f8-d90f-4f75-b866-9d44b31043af/resourcegroups/sentinel-rg/providers/microsoft.operationalinsights/workspaces/sentineldev01",
"_ItemId": "9c4da939-825c-11f0-a6fd-6045bdead2af",
"_ResourceId": "/subscriptions/ae3c87f8-d90f-4f75-b866-9d44b31043af/resourceGroups/vm-rg/providers/Microsoft.Compute/virtualMachines/dev01",
"_SubscriptionId": "ae3c87f8-d90f-4f75-b866-9d44b31043af",
"_TimeReceived": "2025-08-26T09:11:13.2059991Z",
"Computer": "dev01",
"EventCategory": 13312,
"EventData": "<DataItem Type=\\"System.XmlData\\" time=\\"2025-08-26T09:10:42.3542013Z\\" sourceHealthServiceId=\\"780ea715-4580-490d-a0f0-a2905bc1de32\\"><EventData xmlns=\\"<http://schemas.microsoft.com/win/2004/08/events/event\\>"><Data Name=\\"SubjectUserSid\\">S-1-5-18</Data><Data Name=\\"SubjectUserName\\">dev01$</Data><Data Name=\\"SubjectDomainName\\">WORKGROUP</Data><Data Name=\\"SubjectLogonId\\">0x3e7</Data><Data Name=\\"NewProcessId\\">0x13e8</Data><Data Name=\\"NewProcessName\\">C:\\\\Windows\\\\System32\\\\wbem\\\\WmiPrvSE.exe</Data><Data Name=\\"TokenElevationType\\">%%1936</Data><Data Name=\\"ProcessId\\">0x368</Data><Data Name=\\"CommandLine\\" /><Data Name=\\"TargetUserSid\\">S-1-0-0</Data><Data Name=\\"TargetUserName\\">-</Data><Data Name=\\"TargetDomainName\\">-</Data><Data Name=\\"TargetLogonId\\">0x0</Data><Data Name=\\"ParentProcessName\\">C:\\\\Windows\\\\System32\\\\svchost.exe</Data><Data Name=\\"MandatoryLabel\\">S-1-16-16384</Data></EventData></DataItem>",
"EventID": 4688,
"EventLevel": 0,
"EventLevelName": "Information",
"EventLog": "Security",
"ManagementGroupName": "AOI-49833a66-a52b-49f4-a74f-03ae6feba6de",
"MG": "00000000-0000-0000-0000-000000000001",
"ParameterXml": "<Param>S-1-5-18</Param><Param>dev01$</Param><Param>WORKGROUP</Param><Param>0x3e7</Param><Param>0x13e8</Param><Param>C:\\\\Windows\\\\System32\\\\wbem\\\\WmiPrvSE.exe</Param><Param>%%1936</Param><Param>0x368</Param><Param></Param><Param>S-1-0-0</Param><Param>-</Param><Param>-</Param><Param>0x0</Param><Param>C:\\\\Windows\\\\System32\\\\svchost.exe</Param><Param>S-1-16-16384</Param>",
"RenderedDescription": "A new process has been created. Creator Subject: \\tSecurity ID:\\t\\tS-1-5-18 \\tAccount Name:\\t\\tdev01$ \\tAccount Domain:\\t\\tWORKGROUP \\tLogon ID:\\t\\t0x3E7 Target Subject: \\tSecurity ID:\\t\\tS-1-0-0 \\tAccount Name:\\t\\t- \\tAccount Domain:\\t\\t- \\tLogon ID:\\t\\t0x0 Process Information: \\tNew Process ID:\\t\\t0x13e8 \\tNew Process Name:\\tC:\\\\Windows\\\\System32\\\\wbem\\\\WmiPrvSE.exe \\tToken Elevation Type:\\tTokenElevationTypeDefault (1) \\tMandatory Label:\\t\\tS-1-16-16384 \\tCreator Process ID:\\t0x368 \\tCreator Process Name:\\tC:\\\\Windows\\\\System32\\\\svchost.exe \\tProcess Command Line:\\t Token Elevation Type indicates the type of token that was assigned to the new process in accordance with User Account Control policy. Type 1 is a full token with no privileges removed or groups disabled. A full token is only used if User Account Control is disabled or if the user is the built-in Administrator account or a service account. Type 2 is an elevated token with no privileges removed or groups disabled. An elevated token is used when User Account Control is enabled and the user chooses to start the program using Run as administrator. An elevated token is also used when an application is configured to always require administrative privilege or to always require maximum privilege, and the user is a member of the Administrators group. Type 3 is a limited token with administrative privileges removed and administrative groups disabled. The limited token is used when User Account Control is enabled, the application does not require administrative privilege, and the user does not choose to start the program using Run as administrator.",
"Source": "Microsoft-Windows-Security-Auditing",
"SourceSystem": "OpsManager",
"TenantId": "1d6e2c75-ed31-4266-95d6-f175dc4fdb09",
"TimeGenerated": "2025-08-26T09:10:42.3542013Z",
"Type": "Event",
"UserName": "N/A"
}
]
}
Trying to tamper with these fields using DCR Transformations we receive the following error:
❯ az rest --method put --url $ResourceId"?api-version=2023-03-11" --body @$FilePath
Bad Request({"error":{"code":"InvalidPayload","message":"Data collection rule is invalid","details":[{"code":"InvalidTransformQuery","message":"Invalid output property '_ResourceId' at index 20. Cannot start with reserved prefix '_'","target":"properties.dataFlows[0]"}]}})
In the example above, I attempted to use a DCR transformation to define the _ResourceId
field. This failed as expected because the Azure Monitor service actively prohibits writing to these fields destined for Log Analytics Workspaces. This protection is a feature of the trusted ingestion pipeline. However, this DCR restriction does not prevent us from spoofing non-reserved fields like LoggingComputer
when sending data to a passive destination like Event Hubs, as demonstrated earlier.
Conclusion
The findings from this exploration reveals an architectural inconsistency within Azure monitoring: routing flexibility comes directly with security trade-offs.
Streaming Windows Event Logs directly from the AMA to an Event Hub works perfectly for feeding systems like external SIEMs, but you must remember Event Hubs is just a transit/transport layer. While the logs are authenticated by the VM's Managed Identity, the Azure platform does not verify the log's actual content. As we demonstrated, if the AMA, DCR or the source VM is compromised, an attacker can manipulate or inject log entries, effectively erasing their tracks and compromising the entire audit trail. This leaves your downstream SIEM with data you simply cannot trust for incident response or compliance.
In contrast, the Log Analytics Ingestion Pipeline serves as your trusted security boundary. After validating the agent's Managed Identity against ARM, Azure takes control, stamping every single log record with immutable fields like _ResourceId
and _TimeReceived
. This process guarantees the log's origin and integrity, giving your security team full assurance in the audit trail.