Skip to content

Instantly share code, notes, and snippets.

@breser
Created August 16, 2019 16:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save breser/870e9f21471546bf6d9cbd2df862fba8 to your computer and use it in GitHub Desktop.
Save breser/870e9f21471546bf6d9cbd2df862fba8 to your computer and use it in GitHub Desktop.
Audit EBS Storage for overly open permissions.
#!/bin/bash
# Audit EBS Storage
# Looks for AWS Storage that is externally shared outside of the
# AWS Organization. This is a reaction to:
# https://www.defcon.org/html/defcon-27/dc-27-speakers.html#Morris
# https://techcrunch.com/2019/08/09/aws-ebs-cloud-backups-leak/
#
# Assumes that ~/.aws/credentials has credentials to the root account for the
# AWS Organization. Assumes that the user/role you're logged into in the
# root account has permissions to assume the OrganizationAccountAccessRole in
# each member account. Requires jq and aws cli be installed on the path.
# Copyright (c) 2019 Vibes Media LLC
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
set -uo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
function assume_role {
local role="$1"
local results=$(aws sts assume-role --role-arn "$role" \
--role-session-name "audit-ec2-store-$(date +%Y%m%d%H%M%S)")
if [ $? -ne 0 ]; then
printf "%s\n" "$results" >&2
exit 1
fi
export AWS_SECRET_ACCESS_KEY=$(printf "%s" "$results" | \
jq -r '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(printf "%s" "$results" | \
jq -r '.Credentials.SessionToken')
export AWS_ACCESS_KEY_ID=$(printf "%s" "$results" | \
jq -r '.Credentials.AccessKeyId')
}
function drop_role {
unset AWS_SECRET_ACCESS_KEY
unset AWS_SESSION_TOKEN
unset AWS_ACCESS_KEY_ID
}
# build a filter to use with jq that validates that all of the
# userids are account ids in the organization
function build_jq_account_filter {
local account_ids="$1"
local jq_account_filter='| inside(['
for account_id in $account_ids; do
jq_account_filter="${jq_account_filter}\"$account_id\","
done
jq_account_filter="${jq_account_filter%,}"
jq_account_filter="${jq_account_filter}])"
printf "%s" "$jq_account_filter"
}
function check_permissions {
local permissions="$1"
local jq_account_filter="$2"
# empty array means private
[ "$permissions" == '[]' ] || \
# accept UserIds (i.e. AWS account ids) as long as they are all members
# of the organization
[ $(printf "%s" "$permissions" | \
jq "[.[] | select(.UserId).UserId] $jq_account_filter") == 'true' ] && \
# any groups means it's public since only allowed group is all
[ $(printf "%s" "$permissions" | \
jq '[.[] | select(.Group)] | length == 0') == 'true' ]
return $?
}
# Find the account id for the organization account
org_account_id=$(aws sts get-caller-identity --query Account --output text)
if [ $? -ne 0 ]; then
printf "%s\n" "$master_account_id" >&2
exit 1
fi
# Find all the account ids in the organization while handling pagination
account_ids=''
next=''
while true; do
results=$(aws organizations list-accounts $next)
if [ $? -ne 0 ]; then
printf "%s\n" "$results" >&2
exit 1
fi
next_token=$(printf "%s" "$results" | jq -r '.NextToken')
account_ids="${account_ids} $(printf "%s" "$results" | jq -r '.Accounts[].Id')"
if [ "$next_token" != "null" ]; then
printf -v next -- "--starting-token %q" "$next_token"
else
break
fi
done
jq_account_filter=$(build_jq_account_filter "$account_ids")
for account_id in $account_ids; do
printf "AccountId %s\n" "$account_id"
# only assume_role for an account different than our starting account
if [ "$account_id" != "$org_account_id" ]; then
assume_role "arn:aws:iam::${account_id}:role/OrganizationAccountAccessRole"
fi
# Determine the regions available in the account
regions=$(aws ec2 describe-regions --query 'Regions[*].RegionName' \
--output text)
if [ $? -ne 0 ]; then
printf "%s\n" "$regions" >&2
exit 1
fi
for region in $regions; do
printf "%s\n" "$region"
# Iterate over the EBS Snapshots while handling pagination
next=''
while true; do
results=$(aws ec2 describe-snapshots --region "$region" \
--owner-ids "$account_id" $next)
if [ $? -ne 0 ]; then
printf "%s\n" "$results" >&2
exit 1
fi
next_token=$(printf "%s" "$results" | jq -r '.NextToken')
snapshots=$(printf "%s" "$results" | jq -r '.Snapshots[].SnapshotId')
for snapshot in $snapshots; do
printf "%s " "$snapshot"
permissions=$(aws ec2 describe-snapshot-attribute --region "$region" \
--snapshot-id "$snapshot" \
--attribute createVolumePermission \
--query CreateVolumePermissions)
if [ $? -ne 0 ]; then
printf "%s\n" "$permissions" >&2
exit 1
fi
check_permissions "$permissions" "$jq_account_filter"
if [ $? -eq 0 ]; then
printf "${GREEN}[OK]${NC}\n"
else
printf "${RED}[FAIL]${NC}\n"
printf "%s\n" "$permissions"
fi
done
if [ "$next_token" != "null" ]; then
printf -v next -- "--starting-token %q" "$next_token"
else
break
fi
done
# Iterate over the AMIs (this command doesn't support pagination)
images=$(aws ec2 describe-images --region "$region" \
--owners "$account_id" --query 'Images[*].ImageId' \
--output text)
if [ $? -ne 0 ]; then
printf "%s\n" "$images" >&2
exit 1
fi
for image in $images; do
printf "%s " "$image"
permissions=$(aws ec2 describe-image-attribute --region "$region" \
--image-id "$image" --attribute launchPermission --query \
LaunchPermissions)
if [ $? -ne 0 ]; then
printf "%s\n" "$permissions" >&2
exit 1
fi
check_permissions "$permissions" "$jq_account_filter"
if [ $? -eq 0 ]; then
printf "${GREEN}[OK]${NC}\n"
else
printf "${RED}[FAIL]${NC}\n"
printf "%s\n" "$permissions"
fi
done
done
drop_role
printf "\n"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment