aws-cfn-template-s3-write-modifies-code
Write access to the S3 object that a stack's TemplateURL references: the next stack update will load and execute the attacker's template as the service role.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'principal': None}
CanWrite {'template_object': None}
{'stack': None} ContainsResourceReference {'template_object': None}
where
node_type(?template_object) ==
ObjectStorage # S3 template bucket/key
node_type(?stack) == BuildWorker # AWS::CloudFormation::Stack
?stack.TemplateURL references ?template_object.s3_uri
?stack has RoleARN set
optional
Present → upgrade the emitted state; absent → downgrade (not a hard requirement).
{'principal': None}
CanModify {'stack': None}
emit
| source type | Identity |
|---|---|
| target type | Compute |
| source | ?principal |
| target | ?stack |
| permissions | s3:PutObject |
| conditions | run_path iam_permission |
| state logic | CONDITIONAL(run_path) by default — overwriting the S3 template object does NOT immediately execute; the stack must be updated (cloudformation:UpdateStack / ExecuteChangeSet) against that URL to pick up the change. ACTIVE when matched(optional[0]): the principal can ALSO call UpdateStack or ExecuteChangeSet on ?stack (the run path is closed). Inherit weakest with the CanWrite edge on ?template_object (an S3 bucket policy or SCP blocking s3:PutObject makes this BLOCKED). |
Narrative
{principal.name} can overwrite the S3 template object {template_object.name} referenced by stack {stack.name} (s3:PutObject). The next time {stack.name} is updated against that TemplateURL, the attacker's template runs as the stack's service role.
Raw rule rules/derived/aws/cloudformation.yaml
id: aws-cfn-template-s3-write-modifies-code
emits: CanModifyCode
description: 'Write access to the S3 object that a stack''s TemplateURL references: the next stack update
will load and execute the attacker''s template as the service role.'
match:
- - principal: null
- CanWrite
- template_object: null
- - stack: null
- ContainsResourceReference
- template_object: null
where:
- 'node_type(?template_object) == ObjectStorage # S3 template bucket/key'
- 'node_type(?stack) == BuildWorker # AWS::CloudFormation::Stack'
- ?stack.TemplateURL references ?template_object.s3_uri
- ?stack has RoleARN set
optional:
- - principal: null
- CanModify
- stack: null
emit:
source_type: Identity
target_type: Compute
source: ?principal
target: ?stack
permissions:
- s3:PutObject
conditions:
- run_path
- iam_permission
state_logic: "CONDITIONAL(run_path) by default \u2014 overwriting the S3 template object does NOT immediately\
\ execute; the stack must be updated (cloudformation:UpdateStack / ExecuteChangeSet) against that\
\ URL to pick up the change. ACTIVE when matched(optional[0]): the principal can ALSO call UpdateStack\
\ or ExecuteChangeSet on ?stack (the run path is closed). Inherit weakest with the CanWrite edge on\
\ ?template_object (an S3 bucket policy or SCP blocking s3:PutObject makes this BLOCKED)."
confidence: 0.85
derived_from:
- ?principal CanWrite ?template_object
- ?stack ContainsResourceReference ?template_object
false_positive_note: "The S3 write alone is NOT an immediate code-injection; the stack must be re-deployed\
\ against the same URL. A stack that pins templates using a version-specific S3 URL (with version\
\ ID lock) is NOT affected by an overwrite of the latest object \u2014 downgrade to POTENTIAL(run_path)\
\ or omit. Verify the TemplateURL matches the S3 object (bucket + key + any version ID). cicd-chains\
\ then collapses this CanModifyCode + ExecutesAs into CanExecuteAs; do not re-derive CanExecuteAs\
\ here."
narrative: '{principal.name} can overwrite the S3 template object {template_object.name} referenced
by stack {stack.name} (s3:PutObject). The next time {stack.name} is updated against that TemplateURL,
the attacker''s template runs as the stack''s service role.'