AWS provides many tools for securing VPCs and, more generally, resources within them.
The most common method to protect Ec2 instances from external network access is to use AWS security groups, which are stateful, and they let you to manage access to ports or protocols to the Ec2 instances. However, they do not allow you to control the IP or subnet which tries to connect to Ec2 instances,
for example as a result of an attack or unlawful conduct.
For this AWS provides NACLs, which are stateless, and allow (among other things) to filter IP or subnet to the VPC on which they are defined.
In this article we won’t provide a complete guide to NACLs, but rather we’ll just show how to use them to temporarily block traffic from a source IP or from a subnet.
We have prepared a simple script that, given an IP and a region, allows you to filter the single IP or all the subnet announced for routing (RIPE source).
This script, therefore, allows you to quickly block an IP address or a subnet, which, for example, is causing abnormal traffic to our services. Obviously, it does not want to be the definitive solution, but only a temporary tool that allows us to block the source of an anomaly, and then be able to carry out a thorough network forensics activity.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
#!/bin/bash RED='\e[1;31m'; GRE='\e[1;32m'; NC='\e[0m'; # No Color function print_msg () { echo -e "${GRE}$1${NC}\n"; return 0; } function print_error () { echo -e "${RED}$1${NC}\n"; return 0; } function usage () { print_msg "Usage: `basename $0` -i IP_BLACKLIST -r AWS_REGION"; print_msg "-i: Insert the ip in the AWS NACL."; print_msg "-r: Insert AWS region."; print_msg "example: `basename $0` -i 1.2.3.4 -r us-east-2"; exit 0; } function exit_check () { if [ $1 -ne 0 ]; then print_error "Found problem in: $2"; print_error "Exit!"; exit 125; fi } function check_executable_file() { FILE_SEARCH=$(which $1); exit_check $? $LINENO; if [ -z $FILE_SEARCH ]; then print_error "$1: executable does not exist, you need to install it. Exit"; exit 124; fi } if [ $# -ne 4 ]; then usage; fi while getopts "i:r:" opt; do case $opt in i) IP_BLACKLIST="${OPTARG}"; ;; r) REGION="${OPTARG}"; ;; h | *) usage; ;; esac done check_executable_file "geoiplookup"; check_executable_file "aws"; check_executable_file "whois"; print_msg "Do you want to filter the following ip $IP_BLACKLIST? [y/n]"; read yn case $yn in [Yy]* ) ;; [Nn]* ) exit;; * ) print_msg "Please answer y or n." && exit;; esac if [[ $IP_BLACKLIST =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then IP_FILTER=$IP_BLACKLIST; else print_error "Invalid ip address. Exit"; exit 126; fi print_msg "IP coming from:"; geoiplookup $IP_FILTER; exit_check $? $LINENO; NET_FILTER=$(whois $IP_FILTER|grep route:|uniq|awk '{print $2}'); exit_check $? $LINENO; if [ "$NET_FILTER" != "" ]; then print_msg "Do you want to filter the subnet $NET_FILTER or single IP $IP_FILTER? [ip|net]"; read ip_net case $ip_net in ip) CIDR_FILTER="$IP_FILTER/32"; ;; net) CIDR_FILTER="$NET_FILTER"; ;; * ) print_msg "Please answer ip or net." && exit;; esac else CIDR_FILTER="$IP_FILTER/32"; fi NACL=$(aws ec2 describe-network-acls --output text --query 'NetworkAcls[*].NetworkAclId' --region $REGION); exit_check $? $LINENO; for id_acl in $NACL; do NUM_EGRESS=$(aws ec2 describe-network-acls \ --output text \ --network-acl-ids $id_acl \ --query 'NetworkAcls[*].Entries[?(RuleAction==`deny` && Egress==`true`)].{RN:RuleNumber}' \ --region $REGION |grep -v "32767"|sort -n|tail -1| tr -d '\n'); exit_check $? $LINENO; NUM_INGRESS=$(aws ec2 describe-network-acls \ --output text \ --network-acl-ids $id_acl \ --query 'NetworkAcls[*].Entries[?(RuleAction==`deny` && Egress==`false`)].{RN:RuleNumber}' \ --region $REGION |grep -v "32767" |sort -n|tail -1|tr -d '\n'); exit_check $? $LINENO; if [ "$NUM_EGRESS" != "$NUM_INGRESS" ]; then print_error "There is an error in the IN / OUT ACL index... Exit."; exit 127; else if [ -n "$CIDR_FILTER" ] && [[ $CIDR_FILTER =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then if [ -z "$NUM_INGRESS" ]; then NUM_INGRESS=0; fi IDX=$((NUM_INGRESS+1)); if [ "$IDX" -eq 100 ]; then print_error "$LINENO You have to fix the number of roles, ALL TRAFFIC must be the last one... Exit"; exit 128; fi aws ec2 create-network-acl-entry --network-acl-id $id_acl --ingress --rule-number $IDX --protocol all \ --port-range From=1,To=65535 --cidr-block $CIDR_FILTER --rule-action deny --region $REGION; if [ "$?" -eq 0 ]; then print_msg "OK $CIDR_FILTER ingress filtered on $id_acl"; fi aws ec2 create-network-acl-entry --network-acl-id $id_acl --egress --rule-number $IDX --protocol all \ --port-range From=1,To=65535 --cidr-block $CIDR_FILTER --rule-action deny --region $REGION; if [ "$?" -eq 0 ]; then print_msg "OK $CIDR_FILTER egress filtered on $id_acl"; fi else print_error "$LINENO The net $CIDR_FILTER does not appear syntactically correct.... Exit"; exit 129; fi fi done |
That’s all folks!