<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
---
name: auditing-aws-s3-permissions
description: >-
  Audit AWS S3 bucket permissions to detect public access, overly permissive ACLs,
  misconfigured bucket policies, and encryption gaps across accounts.
domain: cybersecurity
subdomain: cloud-security
tags:
  - aws
  - s3
  - permissions
  - bucket-policy
  - acl
  - public-access
  - encryption
  - macie
version: "1.0"
author: defconxt
license: AGPL-3.0
metadata:
  mitre-attack: ["T1530"]
---

# Auditing AWS S3 Permissions

## Overview

S3 misconfigurations remain one of the most common cloud security failures. This skill
provides a systematic approach to auditing S3 bucket permissions — covering public access
block settings, bucket policies, ACLs, encryption configuration, logging, and versioning.
It maps findings to CIS AWS Foundations Benchmark controls and produces actionable
remediation guidance.

Mode: `[MODE: BLUE]` — Defensive posture assessment.

## Prerequisites

- AWS CLI v2 configured with credentials
- IAM permissions: `s3:GetBucketAcl`, `s3:GetBucketPolicy`, `s3:GetBucketPolicyStatus`,
  `s3:GetBucketPublicAccessBlock`, `s3:GetEncryptionConfiguration`, `s3:GetBucketLogging`,
  `s3:GetBucketVersioning`, `s3:ListAllMyBuckets`
- Python 3.10+ with `boto3` installed
- Optional: AWS Macie enabled for sensitive data discovery

## Key Concepts

### Public Access Block Settings

AWS provides account-level and bucket-level public access blocks. All four settings
should be enabled unless there is a documented business justification:

```bash
# Check account-level public access block
aws s3control get-public-access-block --account-id $(aws sts get-caller-identity --query Account --output text)

# Check per-bucket public access block
aws s3api get-public-access-block --bucket $BUCKET_NAME
```

The four block settings:
- `BlockPublicAcls` — Reject PUT requests that include public ACLs
- `IgnorePublicAcls` — Ignore all public ACLs on the bucket
- `BlockPublicPolicy` — Reject bucket policies that grant public access
- `RestrictPublicBuckets` — Restrict public/cross-account access via public policies

### Bucket Policy Analysis

Bucket policies can grant broad access through `Principal: "*"` or overly permissive
conditions. Key patterns to flag:

```bash
# Retrieve bucket policy
aws s3api get-bucket-policy --bucket $BUCKET_NAME --output text | jq .

# Check if bucket policy grants public access
aws s3api get-bucket-policy-status --bucket $BUCKET_NAME
```

Dangerous policy patterns:
- `"Principal": "*"` without IP/VPC conditions
- `"Effect": "Allow"` with `s3:*` action
- Missing `aws:SecureTransport` condition (allows HTTP)
- Cross-account access without `aws:PrincipalOrgID` constraint

### ACL Auditing

Legacy ACLs can grant access to `AllUsers` or `AuthenticatedUsers`:

```bash
# Get bucket ACL
aws s3api get-bucket-acl --bucket $BUCKET_NAME

# Flag public ACLs
aws s3api get-bucket-acl --bucket $BUCKET_NAME | \
  jq '.Grants[] | select(.Grantee.URI != null)'
```

Public ACL URIs to flag:
- `http://acs.amazonaws.com/groups/global/AllUsers` — world-readable
- `http://acs.amazonaws.com/groups/global/AuthenticatedUsers` — any AWS account

### Encryption at Rest

All buckets should enforce server-side encryption:

```bash
# Check default encryption
aws s3api get-bucket-encryption --bucket $BUCKET_NAME

# Verify encryption enforcement in bucket policy
aws s3api get-bucket-policy --bucket $BUCKET_NAME --output text | \
  jq '.Statement[] | select(.Condition.StringNotEquals["s3:x-amz-server-side-encryption"] != null)'
```

### Access Logging

S3 server access logging provides audit trails for bucket operations:

```bash
# Check logging configuration
aws s3api get-bucket-logging --bucket $BUCKET_NAME

# Verify logging target bucket is not publicly accessible
aws s3api get-bucket-acl --bucket $LOG_BUCKET_NAME
```

## Workflow

### Step 1: Enumerate All Buckets

```bash
# List all buckets with region
aws s3api list-buckets --query 'Buckets[].Name' --output text | tr '\t' '\n' | while read bucket; do
  REGION=$(aws s3api get-bucket-location --bucket "$bucket" --query LocationConstraint --output text)
  [ "$REGION" = "None" ] && REGION="us-east-1"
  echo "$bucket,$REGION"
done > /tmp/s3_inventory.csv
```

### Step 2: Audit Public Access

```bash
#!/bin/bash
# s3_public_audit.sh — Find publicly accessible buckets
while IFS=',' read -r bucket region; do
  PAB=$(aws s3api get-public-access-block --bucket "$bucket" 2>/dev/null)
  if [ $? -ne 0 ]; then
    echo "[CRITICAL] $bucket — No public access block configured"
    continue
  fi

  BLOCK_ACLS=$(echo "$PAB" | jq -r '.PublicAccessBlockConfiguration.BlockPublicAcls')
  IGNORE_ACLS=$(echo "$PAB" | jq -r '.PublicAccessBlockConfiguration.IgnorePublicAcls')
  BLOCK_POLICY=$(echo "$PAB" | jq -r '.PublicAccessBlockConfiguration.BlockPublicPolicy')
  RESTRICT=$(echo "$PAB" | jq -r '.PublicAccessBlockConfiguration.RestrictPublicBuckets')

  if [ "$BLOCK_ACLS" != "true" ] || [ "$IGNORE_ACLS" != "true" ] || \
     [ "$BLOCK_POLICY" != "true" ] || [ "$RESTRICT" != "true" ]; then
    echo "[HIGH] $bucket — Incomplete public access block: BlockACLs=$BLOCK_ACLS IgnoreACLs=$IGNORE_ACLS BlockPolicy=$BLOCK_POLICY Restrict=$RESTRICT"
  fi
done < /tmp/s3_inventory.csv
```

### Step 3: Audit Encryption and Logging

```bash
while IFS=',' read -r bucket region; do
  # Check encryption
  ENC=$(aws s3api get-bucket-encryption --bucket "$bucket" 2>/dev/null)
  if [ $? -ne 0 ]; then
    echo "[HIGH] $bucket — No default encryption configured"
  fi

  # Check logging
  LOG=$(aws s3api get-bucket-logging --bucket "$bucket" --query 'LoggingEnabled.TargetBucket' --output text 2>/dev/null)
  if [ "$LOG" = "None" ] || [ -z "$LOG" ]; then
    echo "[MEDIUM] $bucket — Access logging not enabled"
  fi

  # Check versioning
  VER=$(aws s3api get-bucket-versioning --bucket "$bucket" --query Status --output text)
  if [ "$VER" != "Enabled" ]; then
    echo "[LOW] $bucket — Versioning not enabled (Status: $VER)"
  fi
done < /tmp/s3_inventory.csv
```

### Step 4: Run Agent Script

```bash
node scripts/agent.js --profile default --output /tmp/s3_audit.json
```

## Detection

```yaml
title: Auditing Aws S3 Permissions Detection
id: 7250c472-0f10-419b-a5e1-ed28420f095c
status: experimental
description: Detects suspicious activity related to auditing aws s3 permissions techniques in cloud security context
logsource:
  product: aws
  service: cloudtrail
detection:
  selection:
    eventName: "*Unauthorized*"
  condition: selection
level: medium
tags:
  - attack.t1530
  - attack.credential_access
falsepositives:
  - Automated cloud infrastructure provisioning by approved CI/CD pipelines
```

## Verification

- [ ] Account-level public access block enabled with all four settings true
- [ ] No buckets with `Principal: "*"` in policy without IP/VPC conditions
- [ ] No ACLs granting access to AllUsers or AuthenticatedUsers
- [ ] Default encryption (SSE-S3 or SSE-KMS) on all buckets
- [ ] Access logging enabled on all sensitive buckets
- [ ] Versioning enabled on all critical data buckets
- [ ] Bucket policies enforce `aws:SecureTransport` (deny HTTP)
- [ ] Cross-account access uses `aws:PrincipalOrgID` condition
- [ ] CIS AWS Foundations Benchmark 2.1.x controls passing

## References

- [CIS AWS Foundations Benchmark — S3 Controls](https://www.cisecurity.org/benchmark/amazon_web_services)
- [AWS S3 Security Best Practices](https://docs.aws.amazon.com/AmazonS3/latest/userguide/security-best-practices.html)
- [MITRE ATT&CK T1530 — Data from Cloud Storage](https://attack.mitre.org/techniques/T1530/)
- [AWS Macie — Sensitive Data Discovery](https://docs.aws.amazon.com/macie/)
