Introduction
With each service and platform Microsoft adds the underlying core components such as IAM, and any associated security boundaries grow murkier and more complex. Recently I noticed a tweet describing how the Power Platform Administrator role in Entra is not listed as Privileged. This is not intuitive because that role is afforded the explicit permission to perform any action within the Microsoft.Dynamics.CRM namespace.
This namespace now includes services such as Copilot Studio. Copilot Studio affords administrators a low-code no-code experience into custom language model applications such as automated email summary workflows. Copilot Studio features are relatively exhaustive and allow administrators to connect a broad range of Microsoft services together into a single execution environment. It also facilitates cross product communication into AWS or other third party apps. As long as the administrator can instrument the HTTP endpoint it can be built in Copilot Studio.
Importantly, beyond the general notion that an administrator wouldn't expect Power App permissions to expose attack paths to Copilot Studio, those administrators that do have a deeper understanding of the Microsoft control and data planes would intuit that they are afforded a degree of separation in that the underlying ‘Dataverse’ systems utilise separate IAM controls to Entra; Such that being a user in Entra does not afford you access to Dataverse environments and both the user object and a set of permissions must be directly attributed inside Dataverse. So a typical administrator understands that Dataverse has its own set of permissions and role extensions and its own architecture design removed from Entra.
What this article demonstrates is how an Entra user with the role can ultimately enable an attack path into on-premise environments and beyond thanks to the general purpose nature of Copilot Studio.
Maximising potential
Lateral Movement
Once you have successfully phished an Entra user with the Power Platform Administrator Entra role you can pivot into dataverse environments and therefore establish Copilot Studio attack paths by using the ‘self-elevate’ endpoint at
You can utilise the API directly but the shortest possible path is to simply download the ‘pac cli’ tool and execute the commands:
pac auth create --name abuseme
pac admin self-elevate
Executing the self-elevate function adds the requesting user to the Dataverse IAM with ‘System Administrator’ privileges. The user is assigned the category ‘Unlicensed’ due to the new role. This means that you can not perform business functions like use a Power Apps flow but of course with admin privileges you can assign yourself the necessary components to do so.
Better Evasion
You can also pivot to using a service principal in the attack path as they are much less likely to be monitored for abuse. I'm writing a blog series on developing detection content to solve common blind spots here: https://www.goblinloot.net/2026/08/azure-service-principals-detection.html
pac admin assign-user --environment 'ENVIRONMENT ID' --user 'APP ID' --role "Bot Contributor" --application-user
Importantly, Bot Contributor is the minimally viable permission set to abuse a Copilot Studio attack path as it contains the necessary table and misc permissions. Using a minimally viable permission set will increase the likelihood of avoiding detection. As mentioned earlier Dataverse uses its own IAM system so those unfamiliar can introduce themselves here: https://learn.microsoft.com/en-us/power-platform/admin/security-roles-privileges
Once you have utilised your Entra role to self-elevate you can register your attacker controlled service principal with the endpoint
This endpoint registers your service principal as a ‘Power Platform Management App’ and grants it full access to the remainder of dataverse. The indirect registration of the app is necessary because Microsoft possessed the foresight to prevent apps from registering themselves: https://learn.microsoft.com/en-us/power-platform/admin/powerplatform-api-create-service-principal#registering-an-admin-management-application
Now with an attacker controlled service principal we can begin making our API calls or ‘pac cli’ commands using the service principal credentials avoiding extended use of the initially compromised account..
pac auth create --name abusespn --applicationId 'APP ID' --clientSecret 'SECRET' --tenant TENANT ID'
Of course we can use the API directly for better evasion:
Fetch a token from https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token
And then for each action simply build your API request like so:
DATAVERSE_URL/api/data/v9.2/(DATAVERSE_TYPE)(TARGET_OBJECT_ID)
Enumerate Copilot Studio
To discover potential targets for exploitation we can query individual workflow components with:
DATAVERSE_URL/api/data/v9.2/botcomponents?`$select=name,schemaname,botcomponentid,componenttype,statecode,statuscode,createdon,_parentbotid_value&`$orderby=createdon desc
In the above example I've parsed the response to make it easier for me to read. The details for each component type are documented here: https://learn.microsoft.com/en-us/power-apps/developer/data-platform/reference/entities/botcomponent#componenttype-choicesoptions
Additionally we can query the workflows themselves. Workflows are effectively lists of components:
DATAVVERSE_URL/api/data/v9.2/workflows?`$select=name,workflowid,category,type,statecode,statuscode,createdon,modifiedon&`$orderby=createdon desc
Poison everything
Now that we have the access we need and the context for what's available to abuse we can be as creative as we desire. As workflows utilise system managed connections built into the definition file we cant extract the identity credentials used by the workflow but it is possible to hijack preexisting implementations.
Viewing the inbuilt actions and connections available in all Copilot Studio tenants one particularly dangerous option is listed as ‘Microsoft Defender ATP’. This of course is a connector for a service now dubbed Defender for Endpoint. This connector provides a list of pre-built schemas for interacting with Defender for Endpoint within Copilot Studio.
Extending our reaching
To start abusing connectors like Defender for Endpoint we must first retrieve a copy of the workflow. Each workflow is comprised of a definition and metadata file which we can retrieve from the endpoint
DATAVERSE_URL/api/data/v9.2/workflows(WORKFLOWID)
Once retrieved the json file is easiest viewed using graph nodes. In instances where an attacker is fortunate enough to find sensitive details written within workflow notes, those notes are available in cleartext and can be immediately abused elsewhere.
Take note of the clear text password and other surrounding nodes. These are representations of the following graphical experience:
Once we have the definition file we are free to alter it and include or remove features. In particular we can include the Defender for Endpoint connector. The following process allows for the development of an attacker controlled agent workflow that is considerate of the features available and necessary steps to ensure repeatability:
Download the desired target workflow
View the workflow in a graph node format
Note the workflows structure
Which nodes will always fire
What is the triggering condition
Build a duplicate workflow in an attacker controlled tenant
Apply a HTTP endpoint accepting POST requests
Apply a Defender for Endpoint input schema
Establish a differential between the target workflow and simulated workflow
Overwrite simulated workflow with target workflow details
Any changes applied into a workflow that require a connection to another service must be implemented with the identity you have compromised or with another identity you have moved laterally too. Copilot Studio utilises OAuth authentication flows to facilitate the connections in workflows.
While building an attacker workflow or hijacking a current target workflow you can include step action settings. Using these step action settings allows easier adjustments of workflows without causing errors that may be noticed by administrators.
{
"runAfter": {
"connector-{connector_id}": [
"Succeeded",
"Failed",
"TimedOut",
"Skipped"
]
}
}
This will ensure continuity between action steps and prevent your changes breaking the legitimate workflow potentially raising an alarm.
Upload and Abuse
Now we have made the necessary alterations to the Copilot Studio workflow we can upload the changes using the below endpoint by rebuilding the workflow as a json payload from the file and sending it in the body of the request.
Invoke-RestMethod -Uri "DATAVERSE_URL/api/data/v9.2/workflows(TARGET_WORKFLOWID)" -Method Patch -Headers $headers -Body $body
Using our newly established HTTP endpoint we simply make requests to it with the appropriate payload and elicit the newly added Defender for Endpoint connector to perform actions on hosts or servers for us. In this demo ive elicited defender for endpoint to retrieve sensitive files for me via the Copilot Studio workflow
Of course now we have a foothold in Copilot Studio workflows we can also unisolate assets in Defender for endpoint whenever we trigger alerts on-premise.
Threat Detection
As this attack path extends across numerous services no single log source provides a clear picture as to the actions being undertaken or whether they carry any severity when viewed in continuity with each other; however for each surface touched a wealth of logs are available.
List of all relevant log sources (links):
Audit.General Office 365 Management API Subscription
PowerPlatformAdministratorActivityRecord - https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/powerplatformadminactivity
CopilotInteraction - https://learn.microsoft.com/en-us/purview/audit-copilot
Agent 365 inventory data - https://learn.microsoft.com/en-us/microsoft-365/copilot/extensibility/api/admin-settings/package/overview
Defender https://learn.microsoft.com/en-us/defender-xdr/security-for-ai/ai-agent-real-time-protection
BehaviourInfo
CloudAppEvents
When working with clients to deliver new detection content for deployed AI agents it is extremely rare for even a single one of the operation types within any of the listed sources to be collected let alone correlated. Now that AI services are being built regularly these data sources are considered tier zero.
Closing
Analytics
I've captured below some introductory analytics, at a minimum these should be inserted into your SIEM and used to hunt and understand your data. Later I will publish analytics more directly associated with strong adversary signals.
Copilot studio workflow agent invocations
CloudAppEvents
| where ActionType == "CopilotInteraction"
| where RawEventData.AgentPlatform == "CopilotStudio"
| where parse_json(tostring(RawEventData.CopilotEventData)).AppHost == "workflow-agents"
| extend Id_ = tostring(ActivityObjects[0].Id)
Defender for Endpoint Live response from inside Copilot studio
CloudAppEvents
| where ActionType == @"RunLiveResponseApi"
| extend ActivityObjects_0_ApplicationInstance = parse_json(ActivityObjects)["0.ApplicationInstance"]
| where ISP == @"Microsoft Azure"
| extend RawEventData_CommandsString = parse_json(RawEventData)["CommandsString"]
| extend RawEventData_DeviceName = parse_json(RawEventData)["DeviceName"]
| project TimeGenerated, AccountDisplayName, IPAddress, RawEventData_DeviceName, RawEventData_CommandsString
Dataverse environment set to admin mode
PowerPlatformAdminActivity
| extend url_path_ = tostring(Properties.["url.path"])
| extend http_request_method_ = tostring(Properties.["http.request.method"])
| extend powerplatform_analytics_activity_name_ = tostring(Properties.["powerplatform.analytics.activity.name"])
| extend ChangeValue = tostring(parse_json(tostring(Properties.["powerplatform.analytics.resource.environment.property.change"])).NewValue)
| extend powerplatform_analytics_resource_environment_url_ = tostring(Properties.["powerplatform.analytics.resource.environment.url"])
| where ChangeValue == "AdminMode"
Copilot Studio workflows being invoked from new locations
PowerPlatformAdminActivity
| extend url_path_ = tostring(Properties.["url.path"])
| extend powerplatform_analytics_activity_name_ = tostring(Properties.["powerplatform.analytics.activity.name"])
| where url_path_ == "/copilotflows/agentnodes/invokeDefinition"
| where powerplatform_analytics_activity_name_ == "ApiEndpointCallEvent"
| where EventResult == "Succeeded"
| extend user_agent_original_ = tostring(Properties.["user_agent.original"])
| extend enduser_principal_name_ = tostring(Properties.["enduser.principal_name"])
New Copilot Studio agent registrations
CloudAppEvents
| where ActionType == @"Add service principal."
| where AccountDisplayName == @"Microsoft Copilot Studio agent identity blueprint"
Copilot prompts directly inside agent preview (unavailable to non admins)
CloudAppEvents
| where ActionType == "CopilotInteraction"
| extend Id_ = tostring(ActivityObjects[0].Id)
| extend AppIdentity_ = tostring(RawEventData.AppIdentity)
| extend AgentId_ = tostring(RawEventData.AgentId)
| extend AppHost_ = tostring(parse_json(tostring(RawEventData.CopilotEventData)).AppHost)
| extend RecordType_ = tostring(RawEventData.RecordType)
| extend agent = RawEventData.AgentPlatform
| where AppIdentity_ startswith "Copilot.Studio"
| where AppHost_ == "Copilot Studio"
Service principal used to access dataverse
DataverseActivity
| where OriginalObjectId == "UserSignIn authentication"
| where UserId matches regex @"_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
Copilot Studio workflow updated from new address
let WorkflowOverwrite = DataverseActivity
| where ItemType == "Dynamics365"
| where OriginalObjectId has_any ("CommitFileBlocksUpload", "ExportComponentVersionFile", "RetrieveDependentComponentsWithMetadata")
| where ClientIp != "127.0.0.1";
let Auth = union isfuzzy=true SigninLogs, AADServicePrincipalSignInLogs
| project UserPrincipalName, ServicePrincipalId, AppDisplayName, IPAddress, Type;
WorkflowOverwrite
| join kind=leftanti (Auth) on $left.ClientIp == $right.IPAddress