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.

derived aws emits CanExfiltrate

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'principal': None} CanReadData {'cluster': None} {'cluster': None} ExecutesAs {'role': None}

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 typeIdentity
target typeData
source?principal
target?cluster
permissionsredshift-data:ExecuteStatement s3:PutObject
conditionsiam_permission service_state
state logicACTIVE 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."
move · open · esc close