azure-synapse-execute-as-via-library
Upload malicious library to a Spark pool (CanModifyCode) + the pool runs as the workspace MI (ExecutesAs) => execute code as the workspace MI. Cites can-execute-as.yaml execute-as-via-code-modify.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'principal': None}
CanModifyCode {'sparkPool': None}
{'workspace': None} ExecutesAs {'mi': None}
{'workspace': None} Contains {'sparkPool': None}
where
node_type(?sparkPool) ==
AnalyticsService
node_type(?workspace) == AnalyticsService
?mi.privilege_level >= ?principal.privilege_level # escalation condition
optional
Present → upgrade the emitted state; absent → downgrade (not a hard requirement).
{'principal': None}
CanExecuteCommand {'sparkPool': None}
emit
| source type | Identity |
|---|---|
| target type | Identity |
| source | <principal> |
| target | <workspace managed identity> |
| permissions | Microsoft.Synapse/workspaces/bigDataPools/write |
| conditions | trigger_exists |
| state logic | ACTIVE if a run path exists (matched(optional[0]) — the principal can submit a Spark job — OR an existing trigger/scheduled session will run the pool); else CONDITIONAL(trigger_exists) — the malicious library is uploaded but awaits a job. Inherit weakest: if CanModifyCode is CONDITIONAL(service_state), this edge inherits that condition. If ?mi.privilege_level <= ?principal.privilege_level, do not emit. |
Narrative
{principal.name} can upload a malicious library to Spark pool {sparkPool.name} (bigDataPools/write), which runs on Spark workers executing as workspace MI {mi.name}; triggering a session executes the library as {mi.name}.
Raw rule rules/derived/azure/synapse.yaml
id: azure-synapse-execute-as-via-library
emits: CanExecuteAs
description: Upload malicious library to a Spark pool (CanModifyCode) + the pool runs as the workspace
MI (ExecutesAs) => execute code as the workspace MI. Cites can-execute-as.yaml execute-as-via-code-modify.
match:
- - principal: null
- CanModifyCode
- sparkPool: null
- - workspace: null
- ExecutesAs
- mi: null
- - workspace: null
- Contains
- sparkPool: null
where:
- node_type(?sparkPool) == AnalyticsService
- node_type(?workspace) == AnalyticsService
- '?mi.privilege_level >= ?principal.privilege_level # escalation condition'
optional:
- - principal: null
- CanExecuteCommand
- sparkPool: null
emit:
source_type: Identity
target_type: Identity
source: <principal>
target: <workspace managed identity>
permissions:
- Microsoft.Synapse/workspaces/bigDataPools/write
conditions:
- trigger_exists
state_logic: "ACTIVE if a run path exists (matched(optional[0]) \u2014 the principal can submit a Spark\
\ job \u2014 OR an existing trigger/scheduled session will run the pool); else CONDITIONAL(trigger_exists)\
\ \u2014 the malicious library is uploaded but awaits a job. Inherit weakest: if CanModifyCode is\
\ CONDITIONAL(service_state), this edge inherits that condition. If ?mi.privilege_level <= ?principal.privilege_level,\
\ do not emit."
derived_from:
- <CanModifyCode edge_id>
- <ExecutesAs edge_id>
- <Contains edge_id>
- <CanExecuteCommand edge_id?>
note: "CITES can-execute-as.yaml execute-as-via-code-modify. Dedupes on (CanExecuteAs, ?principal, ?mi)\
\ \u2014 do not double-count weight when the generic rule also fires. The generic linchpin execute-as-via-code-modify\
\ does NOT apply here because it checks node_class(?compute) == Compute; AnalyticsService is Data\
\ class. This rule is the sole authoritative emitter for the library-injection path; there is no deduplication\
\ with the generic linchpin. The match clause includes [ ?workspace, Contains, ?sparkPool ] to formalize\
\ the parent-child relationship; this is a structural edge (not walked for attack paths) but required\
\ to properly bind ?workspace from the CanModifyCode match. The escalation roll-up (CanEscalateTo)\
\ is can-control.yaml escalate-via-execute-as \u2014 CITE, DO NOT re-derive."
false_positive_note: "Emit only when the workspace MI out-privileges the principal (where guard). A\
\ Spark pool with NO workspace MI yields no token loot; verify ExecutesAs exists. The library executes\
\ only when a Spark session starts on the pool \u2014 ensure a trigger path exists for ACTIVE. Deny\
\ assignment on bigDataPools/write -> CanModifyCode is BLOCKED -> this edge is BLOCKED."
narrative: '{principal.name} can upload a malicious library to Spark pool {sparkPool.name} (bigDataPools/write),
which runs on Spark workers executing as workspace MI {mi.name}; triggering a session executes the
library as {mi.name}.'