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
main: 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"