def get_managed_policy(iam, policy_arn): """ Given the IAM data for an account and the ARN for a policy, return the policy document """ for policy in iam["Policies"]: if policy_arn == policy["Arn"]: return get_current_policy_doc(policy) raise Exception("Policy not found: {}".format(policy_arn))
def find_admins_in_account(region, findings, privs_to_look_for=None, include_restricted=False): if privs_to_look_for is None: privs_to_look_for = [ "iam:PutRolePolicy", "iam:AddUserToGroup", "iam:AddRoleToInstanceProfile", "iam:AttachGroupPolicy", "iam:AttachRolePolicy", "iam:AttachUserPolicy", "iam:ChangePassword", "iam:CreateAccessKey", "iam:DeleteUserPolicy", "iam:DetachGroupPolicy", "iam:DetachRolePolicy", "iam:DetachUserPolicy", ] account = region.account location = {"account": account.name} admins = [] try: file_name = "account-data/{}/{}/{}".format( account.name, "us-east-1", "iam-get-account-authorization-details.json") iam = json.load(open(file_name)) except: raise Exception("No IAM data for account {}".format(account.name)) admin_policies = [] policy_action_counts = {} for policy in iam["Policies"]: location["policy"] = policy["Arn"] policy_doc = get_current_policy_doc(policy) check_for_bad_policy(findings, region, policy["Arn"], policy_doc) analyzed_policy = analyze_policy_string(json.dumps(policy_doc)) for f in analyzed_policy.findings: findings.add( Finding( region, "IAM_LINTER", policy["Arn"], resource_details={ "issue": str(f.issue), "severity": str(f.severity), "location": str(f.location), "policy": policy_doc }, )) policy_action_counts[policy["Arn"]] = policy_action_count( policy_doc, location) if is_admin_policy( policy_doc, location, findings, region, privs_to_look_for, include_restricted, ): admin_policies.append(policy["Arn"]) if ("arn:aws:iam::aws:policy/AdministratorAccess" in policy["Arn"] or "arn:aws:iam::aws:policy/IAMFullAccess" in policy["Arn"]): # Ignore the admin policies that are obviously admin continue if "arn:aws:iam::aws:policy" in policy["Arn"]: # Detects the deprecated `AmazonElasticTranscoderFullAccess` findings.add( Finding( region, "IAM_MANAGED_POLICY_UNINTENTIONALLY_ALLOWING_ADMIN", policy["Arn"], resource_details={"policy": policy_doc}, )) continue findings.add( Finding( region, "IAM_CUSTOM_POLICY_ALLOWS_ADMIN", policy["Arn"], resource_details={"policy": policy_doc}, )) location.pop("policy", None) # Identify roles that allow admin access for role in iam["RoleDetailList"]: location["role"] = role["Arn"] reasons_for_being_admin = [] # Check if this role is an admin for policy in role["AttachedManagedPolicies"]: if policy["PolicyArn"] in admin_policies: reasons_for_being_admin.append( "Attached managed policy: {}".format(policy["PolicyArn"])) if policy["PolicyArn"] in KNOWN_BAD_POLICIES: findings.add( Finding( region, "IAM_KNOWN_BAD_POLICY", role["Arn"], resource_details={ "comment": KNOWN_BAD_POLICIES[policy["PolicyArn"]], "policy": policy["PolicyArn"], }, )) for policy in role["RolePolicyList"]: policy_doc = policy["PolicyDocument"] analyzed_policy = analyze_policy_string(json.dumps(policy_doc)) for f in analyzed_policy.findings: findings.add( Finding( region, "IAM_LINTER", policy["Arn"], resource_details={ "issue": str(f.issue), "severity": str(f.severity), "location": str(f.location), "policy": policy_doc }, )) if is_admin_policy( policy_doc, location, findings, region, privs_to_look_for, include_restricted, ): if ':role/OrganizationAccountAccessRole' in role['Arn']: # AWS creates this role and adds an inline policy to it granting full # privileges, so this just causes false positives as the purpose # of this detection rule is to find unexpected admins continue reasons_for_being_admin.append("Custom policy: {}".format( policy["PolicyName"])) findings.add( Finding( region, "IAM_CUSTOM_POLICY_ALLOWS_ADMIN", role["Arn"], resource_details={ "comment": "Role has custom policy allowing admin", "policy": policy_doc, }, )) # Check if role is accessible from anywhere policy = Policy(role["AssumeRolePolicyDocument"]) if policy.is_internet_accessible(): findings.add( Finding( region, "IAM_ROLE_ALLOWS_ASSUMPTION_FROM_ANYWHERE", role["Arn"], resource_details={ "statement": role["AssumeRolePolicyDocument"] }, )) # Check if anything looks malformed for stmt in role["AssumeRolePolicyDocument"]["Statement"]: if stmt["Effect"] != "Allow": findings.add( Finding( region, "IAM_UNEXPECTED_FORMAT", role["Arn"], resource_details={ "comment": "Unexpected Effect in AssumeRolePolicyDocument", "statement": stmt, }, )) continue if stmt["Action"] == "sts:AssumeRole": if len(reasons_for_being_admin) != 0: # Admin assumption should be done by users or roles, not by AWS services if "AWS" not in stmt["Principal"] or len( stmt["Principal"]) != 1: findings.add( Finding( region, "IAM_UNEXPECTED_ADMIN_PRINCIPAL", role["Arn"], resource_details={ "comment": "Unexpected Principal in AssumeRolePolicyDocument for an admin", "Principal": stmt["Principal"], }, )) elif stmt["Action"] in [ "sts:AssumeRoleWithSAML", "sts:AssumeRoleWithWebIdentity" ]: continue else: findings.add( Finding( region, "IAM_UNEXPECTED_FORMAT", role["Arn"], resource_details={ "comment": "Unexpected Action in AssumeRolePolicyDocument", "statement": [stmt], }, )) if len(reasons_for_being_admin) != 0: record_admin(admins, account.name, "role", role["RoleName"]) # TODO Should check users or other roles allowed to assume this role to show they are admins location.pop("role", None) # Identify groups that allow admin access admin_groups = [] for group in iam["GroupDetailList"]: location["group"] = group["Arn"] is_admin = False for policy in group["AttachedManagedPolicies"]: if policy["PolicyArn"] in admin_policies: is_admin = True if "admin" not in group["Arn"].lower(): findings.add( Finding( region, "IAM_NAME_DOES_NOT_INDICATE_ADMIN", group["Arn"], None, )) if policy["PolicyArn"] in KNOWN_BAD_POLICIES: findings.add( Finding( region, "IAM_KNOWN_BAD_POLICY", role["Arn"], resource_details={ "comment": KNOWN_BAD_POLICIES[policy["PolicyArn"]], "policy": policy["PolicyArn"], }, )) for policy in group["GroupPolicyList"]: policy_doc = policy["PolicyDocument"] if is_admin_policy( policy_doc, location, findings, region, privs_to_look_for, include_restricted, ): is_admin = True findings.add( Finding( region, "IAM_CUSTOM_POLICY_ALLOWS_ADMIN", group["Arn"], resource_details={ "comment": "Group has custom policy allowing admin", "policy": policy_doc, }, )) if is_admin: admin_groups.append(group["GroupName"]) location.pop("group", None) # Check users for user in iam["UserDetailList"]: location["user"] = user["UserName"] reasons_for_being_admin = [] # Check the different ways in which the user could be an admin for policy in user["AttachedManagedPolicies"]: if policy["PolicyArn"] in admin_policies: reasons_for_being_admin.append( "Attached managed policy: {}".format(policy["PolicyArn"])) if policy["PolicyArn"] in KNOWN_BAD_POLICIES: findings.add( Finding( region, "IAM_KNOWN_BAD_POLICY", role["Arn"], resource_details={ "comment": KNOWN_BAD_POLICIES[policy["PolicyArn"]], "policy": policy["PolicyArn"], }, )) for policy in user.get("UserPolicyList", []): policy_doc = policy["PolicyDocument"] analyzed_policy = analyze_policy_string(json.dumps(policy_doc)) for f in analyzed_policy.findings: findings.add( Finding( region, "IAM_LINTER", policy["Arn"], resource_details={ "issue": str(f.issue), "severity": str(f.severity), "location": str(f.location), "policy": policy_doc }, )) if is_admin_policy( policy_doc, location, findings, region, privs_to_look_for, include_restricted, ): reasons_for_being_admin.append("Custom user policy: {}".format( policy["PolicyName"])) findings.add( Finding( region, "IAM_CUSTOM_POLICY_ALLOWS_ADMIN", user["UserName"], resource_details={ "comment": "User has custom policy allowing admin", "policy": policy_doc, }, )) for group in user["GroupList"]: if group in admin_groups: reasons_for_being_admin.append( "In admin group: {}".format(group)) # Log them if they are an admin if len(reasons_for_being_admin) != 0: record_admin(admins, account.name, "user", user["UserName"]) location.pop("user", None) return admins