audit-labs/audit-tools

A collection of scripts, queries, and other goodies you can use in an audit.

clone: git clone https://gitbay.org/audit-labs/audit-tools.git

v1.0.0: applications/aws/aws_s3_buckets.sh · raw

  1#!/bin/bash
  2
  3# --- Configuration ---
  4# File to store the final report
  5REPORT_FILE="s3_full_public_access_audit.csv"
  6# The region to use for the initial global list-buckets call (e.g., 'us-east-1')
  7MASTER_REGION="us-east-1" 
  8
  9# AWS Regions to check for bucket location. Add more regions if your organization uses them.
 10AWS_REGIONS="us-east-1 us-west-2 eu-central-1 ap-southeast-2"
 11
 12# --- Initialization ---
 13echo "BucketName,Region,PAB_FullyRestricted,Policy_IsPublic,ACL_AllUsersRead,ACL_AllUsersWrite,OverallPublicStatus" > "$REPORT_FILE"
 14echo "Starting FULL S3 Public Access Audit for the CURRENT account..."
 15echo "---"
 16
 17# 1. Retrieve all bucket names
 18echo "1. Retrieving all bucket names..."
 19BUCKET_LIST=$(aws s3api list-buckets --region "$MASTER_REGION" --query 'Buckets[].Name' --output text)
 20
 21if [[ -z "$BUCKET_LIST" ]]; then
 22    echo "✅ No S3 buckets found in this account."
 23    exit 0
 24fi
 25
 26# 2. & 3. Iterate through each bucket to find location and run checks
 27for BUCKET_NAME in $BUCKET_LIST; do
 28    
 29    echo "Processing bucket: $BUCKET_NAME"
 30    BUCKET_REGION=""
 31
 32    # 2. Find the bucket region
 33    for REGION in $AWS_REGIONS; do
 34        BUCKET_LOCATION_RESPONSE=$(aws s3api get-bucket-location --bucket "$BUCKET_NAME" --region "$REGION" 2>/dev/null)
 35        if [[ $? -eq 0 ]]; then
 36            LOCATION_CONSTRAINT=$(echo "$BUCKET_LOCATION_RESPONSE" | jq -r '.LocationConstraint')
 37            BUCKET_REGION=${LOCATION_CONSTRAINT:-"us-east-1"}
 38            break
 39        fi
 40    done
 41    
 42    if [[ -z "$BUCKET_REGION" ]]; then
 43        echo "  ⚠️ WARNING: Could not determine region for $BUCKET_NAME. Skipping all checks."
 44        echo "$BUCKET_NAME,UNKNOWN,N/A,N/A,N/A,N/A,UNKNOWN" >> "$REPORT_FILE"
 45        continue
 46    fi
 47    
 48    echo "  Region determined: $BUCKET_REGION"
 49
 50    # --- Variables for the three checks ---
 51    PAB_FULLY_RESTRICTED="UNKNOWN"
 52    POLICY_IS_PUBLIC="UNKNOWN"
 53    ACL_ALL_USERS_READ="FALSE"
 54    ACL_ALL_USERS_WRITE="FALSE"
 55    OVERALL_PUBLIC_STATUS="FALSE" # Assume safe until proven otherwise
 56
 57    # --- CHECK A: Public Access Block (PAB) ---
 58    PAB_STATUS=$(aws s3api get-public-access-block --bucket "$BUCKET_NAME" --region "$BUCKET_REGION" 2>/dev/null)
 59    
 60    if [[ $? -ne 0 ]]; then
 61        # PAB Missing is the highest risk state.
 62        PAB_FULLY_RESTRICTED="CRITICAL-MISSING"
 63        OVERALL_PUBLIC_STATUS="TRUE - PAB Missing"
 64    else
 65        # Check if ALL four PAB flags are true
 66        PAB_CONFIG=$(echo "$PAB_STATUS" | jq -r '.PublicAccessBlockConfiguration')
 67        if [[ "$(echo "$PAB_CONFIG" | jq -r '.BlockPublicAcls and .IgnorePublicAcls and .BlockPublicPolicy and .RestrictPublicBuckets')" = "true" ]]; then
 68            PAB_FULLY_RESTRICTED="TRUE"
 69        else
 70            PAB_FULLY_RESTRICTED="FALSE-VULNERABLE"
 71        fi
 72    fi
 73
 74    # --- CHECK B: Bucket Policy Status (If S3 service thinks it's public) ---
 75    POLICY_STATUS=$(aws s3api get-bucket-policy-status --bucket "$BUCKET_NAME" --region "$BUCKET_REGION" 2>/dev/null)
 76    
 77    if [[ $? -eq 0 ]]; then
 78        POLICY_IS_PUBLIC=$(echo "$POLICY_STATUS" | jq -r '.PolicyStatus.IsPublic')
 79        if [[ "$POLICY_IS_PUBLIC" = "true" ]]; then
 80            OVERALL_PUBLIC_STATUS="TRUE - Policy"
 81        fi
 82    else
 83        # Expected error if no bucket policy exists, treat as NOT public via policy.
 84        POLICY_IS_PUBLIC="No Policy"
 85    fi
 86
 87    # --- CHECK C: Bucket ACLs (for AllUsers group) ---
 88    ACL_RESPONSE=$(aws s3api get-bucket-acl --bucket "$BUCKET_NAME" --region "$BUCKET_REGION" 2>/dev/null)
 89    
 90    if [[ $? -eq 0 ]]; then
 91        # Find if any grant to 'http://acs.amazonaws.com/groups/global/AllUsers' exists
 92        
 93        # Check for READ access
 94        if echo "$ACL_RESPONSE" | jq -e '.Grants[] | select(.Grantee.URI=="http://acs.amazonaws.com/groups/global/AllUsers") | select(.Permission | test("READ|FULL_CONTROL"))' >/dev/null; then
 95            ACL_ALL_USERS_READ="TRUE"
 96            if [[ "$OVERALL_PUBLIC_STATUS" = "FALSE" ]]; then
 97                 OVERALL_PUBLIC_STATUS="TRUE - ACL Read"
 98            fi
 99        fi
100
101        # Check for WRITE access (often less common for public, but still public exposure)
102        if echo "$ACL_RESPONSE" | jq -e '.Grants[] | select(.Grantee.URI=="http://acs.amazonaws.com/groups/global/AllUsers") | select(.Permission | test("WRITE|FULL_CONTROL"))' >/dev/null; then
103            ACL_ALL_USERS_WRITE="TRUE"
104            if [[ "$OVERALL_PUBLIC_STATUS" = "FALSE" ]]; then
105                 OVERALL_PUBLIC_STATUS="TRUE - ACL Write"
106            fi
107        fi
108    else
109        ACL_ALL_USERS_READ="ACL Check Failed"
110        ACL_ALL_USERS_WRITE="ACL Check Failed"
111    fi
112    
113    # Final check for PAB failure (PAB is the highest authority)
114    if [[ "$PAB_FULLY_RESTRICTED" = "CRITICAL-MISSING" ]]; then
115        OVERALL_PUBLIC_STATUS="TRUE - PAB Missing (CRITICAL)"
116    elif [[ "$OVERALL_PUBLIC_STATUS" != "FALSE" ]] && [[ "$PAB_FULLY_RESTRICTED" != "TRUE" ]]; then
117        # If the bucket is found public by Policy or ACL AND PAB isn't fully set, confirm it's public
118        : # Status already set by Policy or ACL check above
119    fi
120
121
122    # --- Save the output as CSV ---
123    echo "$BUCKET_NAME,$BUCKET_REGION,$PAB_FULLY_RESTRICTED,$POLICY_IS_PUBLIC,$ACL_ALL_USERS_READ,$ACL_ALL_USERS_WRITE,\"$OVERALL_PUBLIC_STATUS\"" >> "$REPORT_FILE"
124    echo "  Final Status: $OVERALL_PUBLIC_STATUS"
125
126done
127
128echo "---"
129echo "✅ Audit Complete."
130echo "Final report saved to **$REPORT_FILE**"
131cat "$REPORT_FILE"