azure-auto-module-write
Upload a malicious PowerShell module or Python package to an Automation Account; existing runbooks that Import-Module/import it execute the injected code as the account MI.
match (effective permission)
{
"any_of": [
{
"action": "Microsoft.Automation/automationAccounts/modules/write",
"scope_type": "resource|resourceGroup|subscription"
},
{
"action": "Microsoft.Automation/automationAccounts/python2Packages/write",
"scope_type": "resource|resourceGroup|subscription"
},
{
"action": "Microsoft.Automation/automationAccounts/python3Packages/write",
"scope_type": "resource|resourceGroup|subscription"
}
]
}
where
at least one existing runbook in the account imports the module by name (or imports all modules from the gallery implicitly)
emit
| source type | Identity |
|---|---|
| target type | GenericCompute |
| source | <principal> |
| target | <runbook (BuildWorker) that imports the module> |
| permissions | Microsoft.Automation/automationAccounts/modules/write Microsoft.Automation/automationAccounts/python2Packages/write Microsoft.Automation/automationAccounts/python3Packages/write |
| conditions | trigger_exists service_state |
| state logic | CONDITIONAL(trigger_exists) when a runbook is known to Import-Module the poisoned module but no current trigger/schedule fires it for the attacker; ACTIVE when an existing schedule or webhook already runs the consuming runbook without attacker action. If no runbook in the account imports the affected module, do not emit — the supply-chain injection has no consumer. BLOCKED if deny assignment covers modules/write. |
Narrative
{principal.name} can upload a malicious PowerShell/Python module to Automation Account {target.name} (automationAccounts/modules/write); if an existing runbook imports this module by name, the injected code executes as the account's managed identity on the next job trigger.
Raw rule rules/derived/azure/automation.yaml
id: azure-auto-module-write
emits: CanModifyCode
description: Upload a malicious PowerShell module or Python package to an Automation Account; existing
runbooks that Import-Module/import it execute the injected code as the account MI.
match_effective_permission:
any_of:
- action: Microsoft.Automation/automationAccounts/modules/write
scope_type: resource|resourceGroup|subscription
- action: Microsoft.Automation/automationAccounts/python2Packages/write
scope_type: resource|resourceGroup|subscription
- action: Microsoft.Automation/automationAccounts/python3Packages/write
scope_type: resource|resourceGroup|subscription
where:
- at least one existing runbook in the account imports the module by name (or imports all modules from
the gallery implicitly)
emit:
source_type: Identity
target_type: GenericCompute
source: <principal>
target: <runbook (BuildWorker) that imports the module>
permissions:
- Microsoft.Automation/automationAccounts/modules/write
- Microsoft.Automation/automationAccounts/python2Packages/write
- Microsoft.Automation/automationAccounts/python3Packages/write
conditions:
- trigger_exists
- service_state
state_logic: "CONDITIONAL(trigger_exists) when a runbook is known to Import-Module the poisoned module\
\ but no current trigger/schedule fires it for the attacker; ACTIVE when an existing schedule or webhook\
\ already runs the consuming runbook without attacker action. If no runbook in the account imports\
\ the affected module, do not emit \u2014 the supply-chain injection has no consumer. BLOCKED if deny\
\ assignment covers modules/write."
confidence: 0.8
derived_from:
- 'effective permission: Microsoft.Automation/automationAccounts/modules/write (or python2/3Packages/write)'
- 'cicd-chains.yaml: cicd-modify-buildspec-executes-as (module is the definition the runner executes)'
false_positive_note: Only emit when a runbook in the account actually imports the module name being
poisoned. A module upload with no consuming runbook is not a CanModifyCode edge. The attacker must
be able to shadow or replace an existing module (matching the name the runbook imports); uploading
a completely new, unimported module has no execution path. Confidence 0.80 (lower than direct runbook-write)
because the supply-chain path requires an existing consumer and an established trigger.
narrative: '{principal.name} can upload a malicious PowerShell/Python module to Automation Account {target.name}
(automationAccounts/modules/write); if an existing runbook imports this module by name, the injected
code executes as the account''s managed identity on the next job trigger.'