def is_admin_policy(policy_doc):
    # TODO Use find_admin.py code instead of copy pasting it here.
    for stmt in make_list(policy_doc['Statement']):
        if stmt['Effect'] == 'Allow':
            actions = make_list(stmt.get('Action', []))
            for action in actions:
                if action == '*' or action == '*:*' or action == 'iam:*':
                    return True
    return False
Example #2
0
def is_admin_policy(policy_doc):
    # TODO Use find_admin.py code instead of copy pasting it here.
    for stmt in make_list(policy_doc["Statement"]):
        if stmt["Effect"] == "Allow":
            actions = make_list(stmt.get("Action", []))
            for action in actions:
                if action == "*" or action == "*:*" or action == "iam:*":
                    return True
    return False
Example #3
0
def is_admin_policy(
    policy_doc, location, findings, region, privs_to_look_for, include_retricted
):
    # This attempts to identify policies that directly allow admin privs, or indirectly through possible
    # privilege escalation (ex. iam:PutRolePolicy to add an admin policy to itself).
    # It is a best effort. It will have false negatives, meaning it may not identify an admin policy
    # when it should, and may have false positives.
    for stmt in make_list(policy_doc["Statement"]):
        if stmt["Effect"] == "Allow":
            # Check for use of NotAction, if they are allowing everything except a set, with no restrictions,
            # this is bad.
            not_actions = make_list(stmt.get("NotAction", []))
            if (
                not_actions != []
                and stmt.get("Resource", "") == "*"
                and stmt.get("Condition", "") == ""
            ):
                if "iam:*" in not_actions:
                    # This is used for PowerUsers, where they can do everything except IAM actions
                    return False
                findings.add(
                    Finding(
                        region,
                        "IAM_NOTACTION_ALLOW",
                        location,
                        resource_details={"Statement": stmt},
                    )
                )
                return True

            actions = make_list(stmt.get("Action", []))
            for action in actions:
                if action == "*" or action == "*:*" or action == "iam:*":
                    if stmt.get("Resource", "") != "*":
                        findings.add(
                            Finding(
                                region,
                                "IAM_UNEXPECTED_FORMAT",
                                location,
                                resource_details={
                                    "comment": "This policy is oddly allowing all actions, but is restricted to a specific resource. This is a confusing way of restricting access that may be more privileged than expected.",
                                    "statement": stmt,
                                },
                            )
                        )
                    return True
                # Look for privilege escalations
                if action_matches(action, privs_to_look_for):
                    if include_retricted:
                        return True
                    elif (
                        stmt.get("Resource", "") == "*"
                        and stmt.get("Condition", "") == ""
                    ):
                        return True

    return False
def is_admin_policy(policy_doc, location):
    # This attempts to identify policies that directly allow admin privs, or indirectly through possible
    # privilege escalation (ex. iam:PutRolePolicy to add an admin policy to itself).
    # It is a best effort. It will have false negatives, meaning it may not identify an admin policy
    # when it should, and may have false positives.
    for stmt in make_list(policy_doc['Statement']):
        if stmt['Effect'] == 'Allow':
            # Check for use of NotAction, if they are allowing everything except a set, with no restrictions,
            # this is bad.
            not_actions = make_list(stmt.get('NotAction', []))
            if not_actions != [] and stmt.get(
                    'Resource', '') == '*' and stmt.get('Condition', '') == '':
                if 'iam:*' in not_actions:
                    # This is used for PowerUsers, where they can do everything except IAM actions
                    return False
                log_warning(
                    'Use of Allow and NotAction on Resource * is likely unwanted',
                    location, [stmt])
                return True

            actions = make_list(stmt.get('Action', []))
            for action in actions:
                if action == '*' or action == '*:*' or action == 'iam:*':
                    if stmt.get('Resource', '') != '*':
                        log_warning('Admin policy not using a Resource of *',
                                    location, [stmt.get('Resource', '')])
                    return True
                # Look for privilege escalations
                if stmt.get('Resource', '') == '*' and stmt.get(
                        'Condition', ''
                ) == '' and (action_matches(
                        action,
                    [
                        'iam:PutRolePolicy',
                        'iam:AddUserToGroup',
                        'iam:AddRoleToInstanceProfile',
                        'iam:AttachGroupPolicy',
                        'iam:AttachRolePolicy',
                        'iam:AttachUserPolicy',
                        'iam:ChangePassword',
                        'iam:CreateAccessKey',
                        # Check for the rare possibility that an actor has a Deny policy on themselves,
                        # so they try to escalate privs by removing that policy
                        'iam:DeleteUserPolicy',
                        'iam:DetachGroupPolicy',
                        'iam:DetachRolePolicy',
                        'iam:DetachUserPolicy'
                    ])):
                    return True

    return False
Example #5
0
def audit_iam(findings, region):
    # By calling the code to find the admins, we'll exercise the code that finds problems.
    find_admins_in_account(region, findings)

    # By default we get the findings for the admins, but we can also look for specific
    # privileges, so we'll look for who has s3:ListAllMyBuckets and then only use those
    # findings that are for a compute resource having this privilege

    s3_listing_findings = Findings()
    s3_get_findings = Findings()

    # TODO Running find_admins_in_account is really slow, and now we're running it 3 times.
    #      So figure out a way to run it once.
    find_admins_in_account(region,
                           s3_listing_findings,
                           privs_to_look_for=["s3:ListAllMyBuckets"])
    find_admins_in_account(region,
                           s3_get_findings,
                           privs_to_look_for=["s3:GetObject"])

    for flist in s3_listing_findings:
        if flist.issue_id != "IAM_UNEXPECTED_ADMIN_PRINCIPAL":
            continue

        services = make_list(
            flist.resource_details.get("Principal", {}).get("Service", ""))
        for service in services:
            if service in [
                    "config.amazonaws.com",
                    "trustedadvisor.amazonaws.com",
                    "macie.amazonaws.com",
            ]:
                continue

            # If we are here then we have a principal that can list S3 buckets,
            # and is associated with an unexpected service,
            # so check if they can read data from them as well

            for fget in s3_get_findings:
                if (fget.issue_id == "IAM_UNEXPECTED_ADMIN_PRINCIPAL"
                        and fget.resource_id == flist.resource_id):
                    # If we are here, then the principal can list S3 buckets and get objects
                    # from them, and is not an unexpected service. Ensure we haven't already
                    # recorded this as an unexpected admin.

                    already_recorded = False
                    for f in findings:
                        if (f.resource_id == fget.resource_id and f.issue_id
                                == "IAM_UNEXPECTED_ADMIN_PRINCIPAL"):
                            already_recorded = True
                            break

                    if not already_recorded:
                        flist.issue_id = "IAM_UNEXPECTED_S3_EXFIL_PRINCIPAL"
                        findings.add(flist)

            # Don't record this multiple times if multiple services are listed
            break
Example #6
0
def check_for_bad_policy(findings, region, arn, policy_text):
    for statement in make_list(policy_text["Statement"]):
        # Checking for signatures of the bad MFA policy from
        # https://web.archive.org/web/20170602002425/https://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_users-self-manage-mfa-and-creds.html
        # and
        # https://github.com/awsdocs/iam-user-guide/blob/cfe14c674c494d07ba0ab952fe546fdd587da65d/doc_source/id_credentials_mfa_enable_virtual.md#permissions-required
        if (
            statement.get("Sid", "") == "AllowIndividualUserToManageTheirOwnMFA"
            or statement.get("Sid", "")
            == "AllowIndividualUserToViewAndManageTheirOwnMFA"
        ):
            if "iam:DeactivateMFADevice" in make_list(statement.get("Action", [])):
                findings.add(Finding(region, "IAM_BAD_MFA_POLICY", arn, policy_text))
                return
        elif (
            statement.get("Sid", "")
            == "BlockAnyAccessOtherThanAboveUnlessSignedInWithMFA"
        ):
            if "iam:*" in make_list(statement.get("NotAction", [])):
                findings.add(Finding(region, "IAM_BAD_MFA_POLICY", arn, policy_text))
                return
Example #7
0
def is_admin_policy(policy_doc, location, findings, region):
    # This attempts to identify policies that directly allow admin privs, or indirectly through possible
    # privilege escalation (ex. iam:PutRolePolicy to add an admin policy to itself).
    # It is a best effort. It will have false negatives, meaning it may not identify an admin policy
    # when it should, and may have false positives.
    for stmt in make_list(policy_doc['Statement']):
        if stmt['Effect'] == 'Allow':
            # Check for use of NotAction, if they are allowing everything except a set, with no restrictions,
            # this is bad.
            not_actions = make_list(stmt.get('NotAction', []))
            if not_actions != [] and stmt.get(
                    'Resource', '') == '*' and stmt.get('Condition', '') == '':
                if 'iam:*' in not_actions:
                    # This is used for PowerUsers, where they can do everything except IAM actions
                    return False
                findings.add(
                    Finding(region,
                            'IAM_NOTACTION_ALLOW',
                            location,
                            resource_details={'Statement': stmt}))
                return True

            actions = make_list(stmt.get('Action', []))
            for action in actions:
                if action == '*' or action == '*:*' or action == 'iam:*':
                    if stmt.get('Resource', '') != '*':
                        findings.add(
                            Finding(
                                region,
                                'IAM_UNEXPECTED_FORMAT',
                                location,
                                resource_details={
                                    'comment':
                                    'This policy is oddly allowing all actions, but is restricted to a specific resource. This is a confusing way of restricting access that may be more privileged than expected.',
                                    'statement': stmt
                                }))
                    return True
                # Look for privilege escalations
                if stmt.get('Resource', '') == '*' and stmt.get(
                        'Condition', ''
                ) == '' and (action_matches(
                        action,
                    [
                        'iam:PutRolePolicy',
                        'iam:AddUserToGroup',
                        'iam:AddRoleToInstanceProfile',
                        'iam:AttachGroupPolicy',
                        'iam:AttachRolePolicy',
                        'iam:AttachUserPolicy',
                        'iam:ChangePassword',
                        'iam:CreateAccessKey',
                        # Check for the rare possibility that an actor has a Deny policy on themselves,
                        # so they try to escalate privs by removing that policy
                        'iam:DeleteUserPolicy',
                        'iam:DetachGroupPolicy',
                        'iam:DetachRolePolicy',
                        'iam:DetachUserPolicy'
                    ])):
                    return True

    return False
Example #8
0
def is_admin_policy(policy_doc, location, findings, region):
    # This attempts to identify policies that directly allow admin privs, or indirectly through possible
    # privilege escalation (ex. iam:PutRolePolicy to add an admin policy to itself).
    # It is a best effort. It will have false negatives, meaning it may not identify an admin policy
    # when it should, and may have false positives.
    for stmt in make_list(policy_doc["Statement"]):
        if stmt["Effect"] == "Allow":
            # Check for use of NotAction, if they are allowing everything except a set, with no restrictions,
            # this is bad.
            not_actions = make_list(stmt.get("NotAction", []))
            if (
                not_actions != []
                and stmt.get("Resource", "") == "*"
                and stmt.get("Condition", "") == ""
            ):
                if "iam:*" in not_actions:
                    # This is used for PowerUsers, where they can do everything except IAM actions
                    return False
                findings.add(
                    Finding(
                        region,
                        "IAM_NOTACTION_ALLOW",
                        location,
                        resource_details={"Statement": stmt},
                    )
                )
                return True

            actions = make_list(stmt.get("Action", []))
            for action in actions:
                if action == "*" or action == "*:*" or action == "iam:*":
                    if stmt.get("Resource", "") != "*":
                        findings.add(
                            Finding(
                                region,
                                "IAM_UNEXPECTED_FORMAT",
                                location,
                                resource_details={
                                    "comment": "This policy is oddly allowing all actions, but is restricted to a specific resource. This is a confusing way of restricting access that may be more privileged than expected.",
                                    "statement": stmt,
                                },
                            )
                        )
                    return True
                # Look for privilege escalations
                if (
                    stmt.get("Resource", "") == "*"
                    and stmt.get("Condition", "") == ""
                    and (
                        action_matches(
                            action,
                            [
                                "iam:PutRolePolicy",
                                "iam:AddUserToGroup",
                                "iam:AddRoleToInstanceProfile",
                                "iam:AttachGroupPolicy",
                                "iam:AttachRolePolicy",
                                "iam:AttachUserPolicy",
                                "iam:ChangePassword",
                                "iam:CreateAccessKey",
                                # Check for the rare possibility that an actor has a Deny policy on themselves,
                                # so they try to escalate privs by removing that policy
                                "iam:DeleteUserPolicy",
                                "iam:DetachGroupPolicy",
                                "iam:DetachRolePolicy",
                                "iam:DetachUserPolicy",
                            ],
                        )
                    )
                ):
                    return True

    return False
Example #9
0
 def test_make_list(self):
     assert_equal(["hello"], make_list("hello"))
     assert_equal(["hello"], make_list(["hello"]))