def get_cidrs_for_account(account, cidrs): account = Account(None, account) # TODO Need to use CloudMapper's prepare to identify trusted IPs that are actually in use. for region_json in get_regions(account): region = Region(account, region_json) sg_json = query_aws(account, "ec2-describe-security-groups", region) sgs = pyjq.all('.SecurityGroups[]', sg_json) for sg in sgs: cidrs_seen = set() cidr_and_name_list = pyjq.all('.IpPermissions[].IpRanges[]|[.CidrIp,.Description]', sg) for cidr, name in cidr_and_name_list: if not is_external_cidr(cidr): continue if is_unneeded_cidr(cidr): print('WARNING: Unneeded cidr used {} in {}'.format(cidr, sg['GroupId'])) continue for cidr_seen in cidrs_seen: if (IPNetwork(cidr_seen) in IPNetwork(cidr) or IPNetwork(cidr) in IPNetwork(cidr_seen)): print('WARNING: Overlapping CIDRs in {}, {} and {}'.format(sg['GroupId'], cidr, cidr_seen)) cidrs_seen.add(cidr) if cidr.startswith('0.0.0.0') and not cidr.endswith('/0'): print('WARNING: Unexpected CIDR for attempted public access {} in {}'.format(cidr, sg['GroupId'])) continue if cidr == '0.0.0.0/0': continue cidrs[cidr] = cidrs.get(cidr, set()) if name is not None: cidrs[cidr].add(name)
def audit_sg(findings, region): # TODO Check if security groups allow large CIDR range (ex. 1.2.3.4/3) # TODO Check if an SG restricts IPv4 and then opens IPv6 or vice versa. cidrs = {} sg_json = query_aws(region.account, 'ec2-describe-security-groups', region) sgs = pyjq.all('.SecurityGroups[]', sg_json) for sg in sgs: cidr_and_name_list = pyjq.all('.IpPermissions[].IpRanges[]|[.CidrIp,.Description]', sg) for cidr, name in cidr_and_name_list: if not is_external_cidr(cidr): continue if is_unblockable_cidr(cidr): findings.add(Finding( region, 'SG_CIDR_UNNEEDED', sg['GroupId'], resource_details={'cidr': cidr})) continue if cidr.startswith('0.0.0.0') and not cidr.endswith('/0'): findings.add(Finding( region, 'SG_CIDR_UNEXPECTED', sg['GroupId'], resource_details={'cidr': cidr})) continue if cidr == '0.0.0.0/0': continue cidrs[cidr] = cidrs.get(cidr, set()) cidrs[cidr].add(sg['GroupId']) for ip_permissions in sg['IpPermissions']: cidrs_seen = set() for ip_ranges in ip_permissions['IpRanges']: if 'CidrIp' not in ip_ranges: continue cidr = ip_ranges['CidrIp'] for cidr_seen in cidrs_seen: if (IPNetwork(cidr_seen) in IPNetwork(cidr) or IPNetwork(cidr) in IPNetwork(cidr_seen)): findings.add(Finding( region, 'SG_CIDR_OVERLAPS', sg['GroupId'], resource_details={'cidr1': cidr, 'cidr2': cidr_seen})) cidrs_seen.add(cidr) for cidr in cidrs: ip = IPNetwork(cidr) if ip.size > 2048: findings.add(Finding( region, 'SG_LARGE_CIDR', cidr, resource_details={'size': ip.size, 'security_groups': cidrs[cidr]}))
def get_cidrs_for_account(account, cidrs): account = Account(None, account) for region_json in get_regions(account): region = Region(account, region_json) sg_json = query_aws(account, "ec2-describe-security-groups", region) sgs = pyjq.all(".SecurityGroups[]", sg_json) for sg in sgs: cidr_and_name_list = pyjq.all( ".IpPermissions[].IpRanges[]|[.CidrIp,.Description]", sg ) for cidr, name in cidr_and_name_list: if not is_external_cidr(cidr): continue if is_unblockable_cidr(cidr): print( "WARNING: Unneeded cidr used {} in {}".format( cidr, sg["GroupId"] ) ) continue if cidr.startswith("0.0.0.0") and not cidr.endswith("/0"): print( "WARNING: Unexpected CIDR for attempted public access {} in {}".format( cidr, sg["GroupId"] ) ) continue if cidr == "0.0.0.0/0": continue cidrs[cidr] = cidrs.get(cidr, set()) if name is not None: cidrs[cidr].add(name) for ip_permissions in sg["IpPermissions"]: cidrs_seen = set() for ip_ranges in ip_permissions["IpRanges"]: if "CidrIp" not in ip_ranges: continue cidr = ip_ranges["CidrIp"] for cidr_seen in cidrs_seen: if IPNetwork(cidr_seen) in IPNetwork(cidr) or IPNetwork( cidr ) in IPNetwork(cidr_seen): print( "WARNING: Overlapping CIDRs in {}, {} and {}".format( sg["GroupId"], cidr, cidr_seen ) ) cidrs_seen.add(cidr)
def get_external_cidrs(account, config): external_cidrs = [] unique_cidrs = {} for region in account.children: for vpc in region.children: sgs = get_sgs(vpc) # Get external IPs for sg in sgs: cidrs = pyjq.all('.IpPermissions[].IpRanges[].CidrIp', sg) for cidr in cidrs: unique_cidrs[cidr] = 1 # Remove private CIDR ranges for cidr in unique_cidrs.keys(): if is_external_cidr(cidr): # It's something else, so add it external_cidrs.append(Cidr(cidr, get_cidr_name(cidr, config))) return external_cidrs
def get_cidrs_for_account(account, cidrs): account = Account(None, account) # TODO Need to use CloudMapper's prepare to identify trusted IPs that are actually in use. for region_json in get_regions(account): region = Region(account, region_json) sg_json = query_aws(account, "ec2-describe-security-groups", region) sgs = pyjq.all('.SecurityGroups[]', sg_json) for sg in sgs: cidr_and_name_list = pyjq.all( '.IpPermissions[].IpRanges[]|[.CidrIp,.Description]', sg) for cidr, name in cidr_and_name_list: if not is_external_cidr(cidr): continue if is_unneeded_cidr(cidr): print('WARNING: Unneeded cidr used {}'.format(cidr)) continue if cidr == '0.0.0.0/0': continue cidrs[cidr] = cidrs.get(cidr, set()) if name is not None: cidrs[cidr].add(name)
def audit_sg(findings, region): # TODO Check if security groups allow large CIDR range (ex. 1.2.3.4/3) # TODO Check if an SG restricts IPv4 and then opens IPv6 or vice versa. cidrs = {} sg_json = query_aws(region.account, "ec2-describe-security-groups", region) sgs = pyjq.all(".SecurityGroups[]", sg_json) for sg in sgs: cidr_and_name_list = pyjq.all( ".IpPermissions[]?.IpRanges[]|[.CidrIp,.Description]", sg) for cidr, name in cidr_and_name_list: if not is_external_cidr(cidr): continue if is_unblockable_cidr(cidr): findings.add( Finding( region, "SG_CIDR_UNNEEDED", sg["GroupId"], resource_details={"cidr": cidr}, )) continue if cidr.startswith("0.0.0.0") and not cidr.endswith("/0"): findings.add( Finding( region, "SG_CIDR_UNEXPECTED", sg["GroupId"], resource_details={"cidr": cidr}, )) continue if cidr == "0.0.0.0/0": continue cidrs[cidr] = cidrs.get(cidr, list()) cidrs[cidr].append(sg["GroupId"]) for ip_permissions in sg.get("IpPermissions", []): cidrs_seen = set() for ip_ranges in ip_permissions.get("IpRanges", []): if "CidrIp" not in ip_ranges: continue cidr = ip_ranges["CidrIp"] for cidr_seen in cidrs_seen: if IPNetwork(cidr_seen) in IPNetwork(cidr) or IPNetwork( cidr) in IPNetwork(cidr_seen): findings.add( Finding( region, "SG_CIDR_OVERLAPS", sg["GroupId"], resource_details={ "cidr1": cidr, "cidr2": cidr_seen }, )) cidrs_seen.add(cidr) for cidr in cidrs: ip = IPNetwork(cidr) if ip.size > 2048: findings.add( Finding( region, "SG_LARGE_CIDR", cidr, resource_details={ "size": ip.size, "security_groups": list(cidrs[cidr]), }, ))
def get_connections(cidrs, vpc, outputfilter): """ For a VPC, for each instance, find all of the other instances that can connect to it, including those in peered VPCs. Note I do not consider subnet ACLs, routing tables, or some other network concepts. """ connections = {} # Get mapping of security group names to nodes that have that security group sg_to_instance_mapping = {} for instance in vpc.leaves: for sg in instance.security_groups: sg_to_instance_mapping.setdefault(sg, {})[instance] = True # For each security group, find all the instances that are allowed to connect to instances # within that group. for sg in get_sgs(vpc): # Get the CIDRs that are allowed to connect for cidr in pyjq.all('.IpPermissions[].IpRanges[].CidrIp', sg): if not is_external_cidr(cidr): # This is a private IP, ex. 10.0.0.0/16 # See if we should skip this if not outputfilter["internal_edges"]: continue # Find all instances in this VPC and peered VPCs that are in this CIDR for sourceVpc in itertools.chain(vpc.peers, (vpc,)): # Ensure it is possible for instances in this VPC to be in the CIDR if not (IPNetwork(sourceVpc.cidr) in IPNetwork(cidr) or IPNetwork(cidr) in IPNetwork(sourceVpc.cidr)): # The CIDR from the security group does not overlap with the CIDR of the VPC, # so skip it continue # For each instance, check if one of its IPs is within the CIDR for sourceInstance in sourceVpc.leaves: for ip in sourceInstance.ips: if IPAddress(ip) in IPNetwork(cidr): # Instance found that can connect to instances in the SG # So connect this instance (sourceInstance) to every instance # in the SG. for targetInstance in sg_to_instance_mapping.get(sg["GroupId"], {}): add_connection(connections, sourceInstance, targetInstance, sg) else: # This is an external IP (ie. not in a private range). for instance in sg_to_instance_mapping.get(sg["GroupId"], {}): # Ensure it has a public IP, as resources with only private IPs can't be reached if instance.is_public: cidrs[cidr].is_used = True add_connection(connections, cidrs[cidr], instance, sg) else: if cidr == '0.0.0.0/0': # Resource is not public, but allows anything to access it, # so mark set all the resources in the VPC as allowing access to it. for source_instance in vpc.leaves: add_connection(connections, source_instance, instance, sg) if outputfilter["internal_edges"]: # Connect allowed in Security Groups for ingress_sg in pyjq.all('.IpPermissions[].UserIdGroupPairs[].GroupId', sg): # We have an SG and a list of SG's it allows in for target in sg_to_instance_mapping.get(sg["GroupId"], {}): # We have an instance and a list of SG's it allows in for source in sg_to_instance_mapping.get(ingress_sg, {}): if (not outputfilter["inter_rds_edges"] and (source.node_type == "rds" or source.node_type == "rds_rr") and (target.node_type == "rds" or target.node_type == "rds_rr")): continue add_connection(connections, source, target, sg) # Connect everything to the Gateway endpoints for targetResource in vpc.leaves: if targetResource.has_unrestricted_ingress: for sourceVpc in itertools.chain(vpc.peers, (vpc,)): for sourceResource in sourceVpc.leaves: add_connection(connections, sourceResource, targetResource, []) # Remove connections for source nodes that cannot initiate traffic (ex. VPC endpoints) for connection in list(connections): if not connection.source.can_egress: del connections[connection] return connections