Query Details

Copilot Studio - Successful call to internal network target

Copilot Studio Internal Network Target Call

Query

let Legacy=AppDependencies
| where (AppRoleName == "Microsoft Copilot Studio" or DependencyType == "Connector") and Success == true
| project TimeGenerated, OperationId, ClientIP, AccountName=iff(isempty(UserId), "unknown-agent", UserId), Agent="", ConvId=tostring(Properties["conversationId"]), Tool=Name, ToolType="Connector", TargetValue=coalesce(tostring(Properties["gen_ai.tool.server_url"]), tostring(Properties["mcp.server.url"]), tostring(Target)), Source="Agent-level connector";
let Modern=AppDependencies
| where tostring(Properties["gen_ai.operation.name"]) == "execute_tool" and Success == true
| project TimeGenerated, OperationId, ClientIP, AccountName=coalesce(tostring(Properties["user.email"]), tostring(Properties["user.id"]), tostring(Properties["gen_ai.agent.name"]), "unknown-agent"), Agent=tostring(Properties["gen_ai.agent.name"]), ConvId=tostring(Properties["gen_ai.conversation.id"]), Tool=tostring(Properties["gen_ai.tool.name"]), ToolType=tostring(Properties["gen_ai.tool.type"]), TargetValue=coalesce(tostring(Properties["gen_ai.tool.server_url"]), tostring(Properties["mcp.server.url"]), tostring(Target)), Source="Environment execute_tool";
union Legacy, Modern
| extend TargetUrl=extract(@"(?i)https?://[A-Za-z0-9.\-:\[\]]+", 0, TargetValue)
| where isnotempty(TargetUrl)
| extend TargetHost=tolower(tostring(parse_url(TargetUrl)["Host"]))
| extend TargetClass=case(TargetHost in ("localhost", "::1", "[::1]") or TargetHost matches regex @"^127\.", "Loopback", TargetHost matches regex @"^169\.254\." or TargetHost in ("metadata.google.internal", "metadata.azure.internal"), "Link-local or metadata service", TargetHost matches regex @"^10\." or TargetHost matches regex @"^192\.168\." or TargetHost matches regex @"^172\.(1[6-9]|2[0-9]|3[01])\.", "RFC1918 private network", "Public")
| where TargetClass != "Public"
| extend Signal="SUCCESSFUL INTERNAL-TARGET TOOL CALL"
| project TimeGenerated, Signal, TargetClass, TargetUrl, TargetHost, AccountName, ClientIP, Agent, ConvId, Tool, ToolType, TargetValue, Source, OperationId
| order by TimeGenerated desc

Explanation

This KQL query is designed to detect successful network calls made by the Copilot Studio application to internal network targets, which could indicate potential security risks like Server-Side Request Forgery (SSRF). Here's a simplified breakdown:

  1. Purpose: The query identifies successful calls from Copilot Studio to internal network addresses, such as loopback, link-local, cloud metadata services, or private network spaces defined by RFC1918. These are considered SSRF-like destinations.

  2. Data Source: It uses data from Application Insights, specifically the AppDependencies data type.

  3. Query Logic:

    • It checks for successful operations from either legacy connectors or modern execute_tool operations.
    • It extracts and analyzes the target URLs to classify them as loopback, link-local, cloud metadata, or private network addresses.
    • Only non-public targets are flagged.
  4. Output: The query outputs details like the time of the event, the type of signal, target classification, URL, host, account name, client IP, and other relevant information.

  5. Alerting: If any such internal-target tool calls are detected, an alert is generated. The severity of this alert is marked as high.

  6. Incident Management: The query is scheduled to run every hour, and incidents are created for detected events. These incidents can be grouped by account and have a lookback duration of 12 hours for potential reopening of closed incidents.

  7. Tags and Metadata: The query is tagged with relevant identifiers like Sentinel-As-Code, Custom, CopilotStudio, AI, SSRF, and OWASP-LLM06, indicating its context and purpose.

Overall, this query helps in monitoring and alerting on potentially risky internal network calls made by Copilot Studio, aiding in the detection of security vulnerabilities related to SSRF.