aws-sc-s3-template-write-modifies-code
Write access to the S3 object backing a Service Catalog product version's template (LoadTemplateFromURL): the next ProvisionProduct or UpdateProvisionedProduct loads the attacker's template and executes it as the launch role. cicd-chains propagates this CanModifyCode + ExecutesAs into CanExecuteAs.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
CanWrite {'template_object': None}
{'product': None} ContainsResourceReference {'template_object': None}
where
ObjectStorage # S3 bucket/key from LoadTemplateFromURL
node_type(?product) == BuildWorker # AWS::ServiceCatalog::CloudFormationProduct
?product version Info.LoadTemplateFromURL references ?template_object.s3_uri (no version-ID pin)
?product has a LaunchRole constraint (RoleArn non-empty)
optional
Present → upgrade the emitted state; absent → downgrade (not a hard requirement).
CanTrigger {'product': None}
emit
| source type | Identity |
|---|---|
| target type | Compute |
| source | ?principal |
| target | ?product |
| permissions | s3:PutObject |
| conditions | run_path iam_permission |
| state logic | CONDITIONAL(run_path) by default — overwriting the S3 template object does NOT immediately execute; a ProvisionProduct or UpdateProvisionedProduct call must load the updated URL. ACTIVE when matched(optional[0]): the principal can ALSO trigger provisioning (servicecatalog:ProvisionProduct / UpdateProvisionedProduct), closing the run path. POTENTIAL(run_path) when the product version URL pins a specific S3 object version ID (i.e., the URL includes ?versionId=...) — overwriting the latest object does not change what Service Catalog loads; do not emit CONDITIONAL in that case. 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 product {product.name}'s version (s3:PutObject on {template_object.name}). The next ProvisionProduct or UpdateProvisionedProduct call against that version loads the attacker's template, which executes as the launch role bound to {product.name}.
Raw rule rules/derived/aws/servicecatalog.yaml
id: aws-sc-s3-template-write-modifies-code
emits: CanModifyCode
description: 'Write access to the S3 object backing a Service Catalog product version''s template (LoadTemplateFromURL):
the next ProvisionProduct or UpdateProvisionedProduct loads the attacker''s template and executes it
as the launch role. cicd-chains propagates this CanModifyCode + ExecutesAs into CanExecuteAs.'
match:
- - principal: null
- CanWrite
- template_object: null
- - product: null
- ContainsResourceReference
- template_object: null
where:
- 'node_type(?template_object) == ObjectStorage # S3 bucket/key from LoadTemplateFromURL'
- 'node_type(?product) == BuildWorker # AWS::ServiceCatalog::CloudFormationProduct'
- ?product version Info.LoadTemplateFromURL references ?template_object.s3_uri (no version-ID pin)
- ?product has a LaunchRole constraint (RoleArn non-empty)
optional:
- - principal: null
- CanTrigger
- product: null
emit:
source_type: Identity
target_type: Compute
source: ?principal
target: ?product
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; a ProvisionProduct or UpdateProvisionedProduct call must load the updated URL. ACTIVE when\
\ matched(optional[0]): the principal can ALSO trigger provisioning (servicecatalog:ProvisionProduct\
\ / UpdateProvisionedProduct), closing the run path. POTENTIAL(run_path) when the product version\
\ URL pins a specific S3 object version ID (i.e., the URL includes ?versionId=...) \u2014 overwriting\
\ the latest object does not change what Service Catalog loads; do not emit CONDITIONAL in that case.\
\ 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
- ?product ContainsResourceReference ?template_object (LoadTemplateFromURL)
- ?product LaunchRole constraint present
- cicd-chains.yaml cicd-modify-buildspec-executes-as (consumes this CanModifyCode + ExecutesAs)
false_positive_note: "The S3 write alone is NOT an immediate code-injection; a provisioning trigger\
\ must follow. A product version URL that includes a specific S3 version ID (versionId query param)\
\ is NOT affected by overwriting the latest object version \u2014 downgrade to POTENTIAL(run_path)\
\ or omit. Verify the LoadTemplateFromURL value from DescribeProvisioningArtifact.Info does NOT contain\
\ a versionId pin. Only emit when the product has a LaunchRole constraint. cicd-chains collapses this\
\ CanModifyCode + ExecutesAs into CanExecuteAs \u2014 do not re-derive CanExecuteAs here."
narrative: '{principal.name} can overwrite the S3 template object {template_object.name} referenced
by product {product.name}''s version (s3:PutObject on {template_object.name}). The next ProvisionProduct
or UpdateProvisionedProduct call against that version loads the attacker''s template, which executes
as the launch role bound to {product.name}.'