Detection rules › Kusto
BTP - Cloud Integration artifact deployment
Identifies deployment and undeployment of integration artifacts in SAP Cloud Integration. Integration flows are executable code that can process, transform, and route data between systems. Unauthorized artifact deployment could indicate: - Attacker deploying malicious integration flows for data exfiltration - Deployment of rogue code for persistent access - Undeployment of critical integrations causing denial of service
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Execution | T1059 Command and Scripting Interpreter |
| Persistence | T1546 Event Triggered Execution |
Rule body kusto
id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
kind: Scheduled
name: BTP - Cloud Integration artifact deployment
description: |
Identifies deployment and undeployment of integration artifacts in SAP Cloud Integration.
Integration flows are executable code that can process, transform, and route data between
systems.
Unauthorized artifact deployment could indicate:
- Attacker deploying malicious integration flows for data exfiltration
- Deployment of rogue code for persistent access
- Undeployment of critical integrations causing denial of service
severity: High
status: Available
requiredDataConnectors:
- connectorId: SAPBTPAuditEvents
dataTypes:
- SAPBTPAuditLog_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
- Persistence
relevantTechniques:
- T1059
- T1546
query: |
SAPBTPAuditLog_CL
| where Category == "audit.configuration"
| extend objectType = tostring(Message.object.type)
| where objectType in ("Deployment", "Undeployment")
| extend attributes = todynamic(Message.attributes)
| mv-apply attr = attributes on (
summarize
SymbolicName = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "symbolicName"),
ArtifactId = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "id"),
ArtifactVersion = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "version"),
DeployedBy = take_anyif(tostring(attr.["new"]), tostring(attr.name) == "deployedBy"),
UndeployedBy = take_anyif(tostring(attr.["new"]), tostring(attr.name) == "undeployedBy"),
Creator = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "creator"),
TenantName = take_anyif(tostring(attr.["new"]), tostring(attr.name) == "tenantName"),
RuntimeLocationId = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "runtimeLocationId")
)
| extend Actor = case(
isnotempty(DeployedBy), DeployedBy,
isnotempty(UndeployedBy), UndeployedBy,
isnotempty(Creator), Creator,
""
)
| extend ActionCategory = iff(objectType == "Deployment", "Deploy", "Undeploy"),
normalizedAction = iff(objectType == "Deployment", "deployed", "undeployed")
| extend MessageText = strcat("Integration artifact '", SymbolicName, "' (version ", ArtifactVersion, ") was ", normalizedAction, " in tenant ", TenantName)
| extend AccountName = iff(Actor has "@", tostring(split(Actor, "@")[0]), ""),
UPNSuffix = iff(Actor has "@", tostring(split(Actor, "@")[1]), "")
| project
UpdatedOn,
Actor,
AccountName,
UPNSuffix,
MessageText,
ArtifactName = SymbolicName,
ArtifactId,
ArtifactVersion,
ActionCategory,
ObjectType = objectType,
TenantName,
RuntimeLocationId,
Tenant,
CloudApp = "SAP Cloud Integration"
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: CloudApp
alertDetailsOverride:
alertDisplayNameFormat: 'SAP Cloud Integration: {{MessageText}}'
alertDescriptionFormat: |
{{MessageText}} by {{Actor}}.
This could indicate:
- Legitimate integration artifact deployment or maintenance
- Unauthorized deployment of malicious integration code
- Attacker undeploying security-relevant integrations
customDetails:
ArtifactName: ArtifactName
ArtifactId: ArtifactId
ArtifactVersion: ArtifactVersion
ActionCategory: ActionCategory
TenantName: TenantName
RuntimeLocationId: RuntimeLocationId
version: 1.1.0
Stages and Predicates
Stage 1: source
SAPBTPAuditLog_CL
Stage 2: where
| where Category == "audit.configuration"
Stage 3: extend
| extend objectType = tostring(Message.object.type)
Stage 4: where
| where objectType in ("Deployment", "Undeployment")
Stage 5: extend
| extend attributes = todynamic(Message.attributes)
Stage 6: kusto:mv-apply
| mv-apply attr = attributes on (
summarize
SymbolicName = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "symbolicName"),
ArtifactId = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "id"),
ArtifactVersion = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "version"),
DeployedBy = take_anyif(tostring(attr.["new"]), tostring(attr.name) == "deployedBy"),
UndeployedBy = take_anyif(tostring(attr.["new"]), tostring(attr.name) == "undeployedBy"),
Creator = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "creator"),
TenantName = take_anyif(tostring(attr.["new"]), tostring(attr.name) == "tenantName"),
RuntimeLocationId = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "runtimeLocationId")
)
Stage 7: extend (4 consecutive steps)
| extend Actor = case(
isnotempty(DeployedBy), DeployedBy,
isnotempty(UndeployedBy), UndeployedBy,
isnotempty(Creator), Creator,
""
)
| extend ActionCategory = iff(objectType == "Deployment", "Deploy", "Undeploy"),
normalizedAction = iff(objectType == "Deployment", "deployed", "undeployed")
| extend MessageText = strcat("Integration artifact '", SymbolicName, "' (version ", ArtifactVersion, ") was ", normalizedAction, " in tenant ", TenantName)
| extend AccountName = iff(Actor has "@", tostring(split(Actor, "@")[0]), ""),
UPNSuffix = iff(Actor has "@", tostring(split(Actor, "@")[1]), "")
Actor =isnotempty(DeployedBy)DeployedByisnotempty(UndeployedBy)UndeployedByisnotempty(Creator)Creator""Stage 8: project
| project
UpdatedOn,
Actor,
AccountName,
UPNSuffix,
MessageText,
ArtifactName = SymbolicName,
ArtifactId,
ArtifactVersion,
ActionCategory,
ObjectType = objectType,
TenantName,
RuntimeLocationId,
Tenant,
CloudApp = "SAP Cloud Integration"
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
Category | eq |
|
objectType | in |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
AccountName | project |
ActionCategory | project |
Actor | project |
ArtifactId | project |
ArtifactName | project |
ArtifactVersion | project |
CloudApp | project |
MessageText | project |
ObjectType | project |
RuntimeLocationId | project |
Tenant | project |
TenantName | project |
UPNSuffix | project |
UpdatedOn | project |