aws-redshift-exfiltrate-via-unload
A principal with query execution on a Redshift cluster can run UNLOAD using an attached IAM role that has s3:PutObject, exfiltrating warehouse data to an S3 location.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?cluster) ==
DataWarehouse
?cluster.provider_type in ['AWS::Redshift::Cluster', 'AWS::RedshiftServerless::Workgroup']
?role has EFFECTIVE s3:PutObject on at least one S3 bucket ARN (CanWriteData to ObjectStorage)
?principal mapped DB user has UNLOAD privilege (superuser or explicit GRANT UNLOAD)
emit
| source type | Identity |
|---|---|
| target type | Data |
| source | ?principal |
| target | ?cluster |
| permissions | redshift-data:ExecuteStatement s3:PutObject |
| conditions | iam_permission service_state |
| state logic | ACTIVE when (1) the contributing CanReadData is ACTIVE (query submission confirmed effective), (2) the cluster's attached role has confirmed effective s3:PutObject on at least one S3 bucket (or iam:PassRole for such a role — even a new role can be attached per rule 1), and (3) the mapped DB user has UNLOAD privilege. CONDITIONAL(iam_permission) when the role's S3 permission is unresolved. CONDITIONAL(service_state) when the cluster is paused. Inherits the weakest state from its contributing CanReadData and ExecutesAs edges. BLOCKED when an SCP or VPC endpoint policy denies s3:PutObject for the cluster's role. |
Narrative
{principal.name} can submit an UNLOAD query to {cluster.name} that exports table data to S3 using the cluster's attached role {role.name} (s3:PutObject) - a full warehouse exfiltration path requiring only SQL execution access and an attached role with S3 write permissions.
Raw rule rules/derived/aws/redshift.yaml
id: aws-redshift-exfiltrate-via-unload
emits: CanExfiltrate
description: A principal with query execution on a Redshift cluster can run UNLOAD using an attached IAM
role that has s3:PutObject, exfiltrating warehouse data to an S3 location.
match:
- - principal: null
- CanReadData
- cluster: null
- - cluster: null
- ExecutesAs
- role: null
where:
- node_type(?cluster) == DataWarehouse
- ?cluster.provider_type in ['AWS::Redshift::Cluster', 'AWS::RedshiftServerless::Workgroup']
- ?role has EFFECTIVE s3:PutObject on at least one S3 bucket ARN (CanWriteData to ObjectStorage)
- ?principal mapped DB user has UNLOAD privilege (superuser or explicit GRANT UNLOAD)
emit:
source_type: Identity
target_type: Data
source: ?principal
target: ?cluster
permissions:
- redshift-data:ExecuteStatement
- s3:PutObject
conditions:
- iam_permission
- service_state
state_logic: "ACTIVE when (1) the contributing CanReadData is ACTIVE (query submission confirmed effective),\
\ (2) the cluster's attached role has confirmed effective s3:PutObject on at least one S3 bucket (or\
\ iam:PassRole for such a role \u2014 even a new role can be attached per rule 1), and (3) the mapped\
\ DB user has UNLOAD privilege. CONDITIONAL(iam_permission) when the role's S3 permission is unresolved.\
\ CONDITIONAL(service_state) when the cluster is paused. Inherits the weakest state from its contributing\
\ CanReadData and ExecutesAs edges. BLOCKED when an SCP or VPC endpoint policy denies s3:PutObject\
\ for the cluster's role."
confidence: min(contributing_confidences) * 0.85
derived_from:
- ?principal CanReadData ?cluster
- ?cluster ExecutesAs ?role
false_positive_note: "UNLOAD requires: (1) EFFECTIVE query submission (CanReadData gate); (2) an attached\
\ IAM role with s3:PutObject on a writable bucket (confirm role's S3 permission \u2014 a role with\
\ only s3:GetObject cannot UNLOAD); (3) DB user having UNLOAD privilege (superusers always do; non-superusers\
\ need an explicit GRANT). If Enhanced VPC Routing (EnhancedVpcRouting: true) is enabled, UNLOAD traffic\
\ goes through the VPC; S3 must be reachable via VPC endpoint or the UNLOAD fails (CONDITIONAL(network_reachability)\
\ if no VPC endpoint). The attacker need not control the target bucket \u2014 they only need to be\
\ able to read data from it afterward; an existing company S3 bucket is a valid exfil destination\
\ if the attacker has s3:GetObject access to it. Model target S3 bucket as target: ?cluster (the data\
\ node) not the S3 bucket specifically (the cluster IS the data; the S3 bucket is just the landing\
\ zone)."
narrative: "{principal.name} can submit an UNLOAD query to {cluster.name} that exports table data to\
\ S3 using the cluster's attached role {role.name} (s3:PutObject) \u2014 a full warehouse exfiltration\
\ path requiring only SQL execution access and an attached role with S3 write permissions."