Example #1
0
    def test_from_account_number(self):
        proper_account_numbers = [
            '012345678912', '123456789101', '123456789101'
        ]

        improper_account_numbers = [
            '*',
            'O12345678912',  # 'O' instead of '0'
            'asdfqwer',
            '123456',
            '89789456314356132168978945',
            '568947897*'
        ]

        # Proper account number tests:
        for accnt in proper_account_numbers:
            app.logger.info('Testing Proper Account Number: {}'.format(accnt))
            arn_obj = ARN(accnt)

            self.assertFalse(arn_obj.error)

        # Improper account number tests:
        for accnt in improper_account_numbers:
            app.logger.info(
                'Testing IMPROPER Account Number: {}'.format(accnt))
            arn_obj = ARN(accnt)

            self.assertTrue(arn_obj.error)
Example #2
0
    def check_sqsqueue_crossaccount(self, sqsitem):
        """
        alert on cross account access
        """
        policy = sqsitem.config
        for statement in policy.get("Statement", []):
            account_numbers = []
            princ = statement.get("Principal", None)
            if not princ:
                # It is possible not to define a principal, AWS ignores these statements.
                # We should raise an issue.
                tag = "SQS Policy is lacking Principal field"
                notes = json.dumps(statement)
                self.add_issue(5, tag, sqsitem, notes=notes)
                continue
            if isinstance(princ, dict):
                princ_val = princ.get("AWS") or princ.get("Service")
            else:
                princ_val = princ

            if princ_val == "*":
                condition = statement.get('Condition', {})
                arns = ARN.extract_arns_from_statement_condition(condition)
                if not arns:
                    tag = "SQS Queue open to everyone"
                    notes = "An SQS policy where { 'Principal': { 'AWS': '*' } } must also have"
                    notes += " a {'Condition': {'ArnEquals': { 'AWS:SourceArn': '<ARN>' } } }"
                    notes += " or it is open to the world. In this case, anyone is allowed to perform "
                    notes += " this action(s): {}".format(
                        statement.get("Action"))
                    self.add_issue(10, tag, sqsitem, notes=notes)

                for arn in arns:
                    self._parse_arn(arn, account_numbers, sqsitem)

            else:
                if isinstance(princ_val, list):
                    for entry in princ_val:
                        arn = ARN(entry)
                        if arn.error:
                            self.add_issue(3,
                                           'Auditor could not parse ARN',
                                           sqsitem,
                                           notes=entry)
                            continue

                        if not arn.service:
                            account_numbers.append(arn.account_number)
                else:
                    arn = ARN(princ_val)
                    if arn.error:
                        self.add_issue(3,
                                       'Auditor could not parse ARN',
                                       sqsitem,
                                       notes=princ_val)
                    elif not arn.service:
                        account_numbers.append(arn.account_number)

            for account_number in account_numbers:
                self._check_cross_account(account_number, sqsitem, 'policy')
Example #3
0
    def check_snstopicpolicy_crossaccount(self, snsitem):
        """
        alert on cross account access
        """
        policy = snsitem.config.get('policy', {})
        for statement in policy.get("Statement", []):
            account_numbers = []
            princ = statement.get("Principal", {})
            if isinstance(princ, dict):
                princ_val = princ.get("AWS") or princ.get("Service")
            else:
                princ_val = princ

            if princ_val == "*":
                condition = statement.get('Condition', {})
                arns = ARN.extract_arns_from_statement_condition(condition)

                if not arns:
                    tag = "SNS Topic open to everyone"
                    notes = "An SNS policy where { 'Principal': { 'AWS': '*' } } must also have"
                    notes += " a {'Condition': {'StringEquals': { 'AWS:SourceOwner': '<ARN>' } } }"
                    notes += " or it is open to the world. In this case, anyone is allowed to perform "
                    notes += " this action(s): {}".format(
                        statement.get("Action"))
                    self.add_issue(10, tag, snsitem, notes=notes)

                for arn in arns:
                    self._parse_arn(arn, account_numbers, snsitem)

            else:
                if isinstance(princ_val, list):
                    for entry in princ_val:
                        arn = ARN(entry)
                        if arn.error:
                            self.add_issue(3,
                                           'Auditor could not parse ARN',
                                           snsitem,
                                           notes=entry)
                            continue

                        if not arn.service:
                            account_numbers.append(arn.account_number)
                else:
                    arn = ARN(princ_val)
                    if arn.error:
                        self.add_issue(3,
                                       'Auditor could not parse ARN',
                                       snsitem,
                                       notes=princ_val)
                    elif not arn.service:
                        account_numbers.append(arn.account_number)

            for account_number in account_numbers:
                self._check_cross_account(account_number, snsitem, 'policy')
Example #4
0
    def test_extract_arns_from_statement_condition(self):
        test_condition_list = [
            'ArnEquals',
            'ForAllValues:ArnEquals',
            'ForAnyValue:ArnEquals',
            'ArnLike',
            'ForAllValues:ArnLike',
            'ForAnyValue:ArnLike',
            'StringLike',
            'ForAllValues:StringLike',
            'ForAnyValue:StringLike',
            'StringEquals',
            'ForAllValues:StringEquals',
            'ForAnyValue:StringEquals'
        ]

        bad_condition_list = [
            'NotACondition',
            'ArnLikeSomethingNotARealCondition'
        ]

        arn_types = [
            ('aws:sourcearn', 'arn:aws:s3:::some-s3-bucket'),
            ('aws:sourcearn', 'arn:aws:s3:::some-s3-bucket/*'),
            ('aws:sourcearn', "*"),
            ('aws:sourceowner', '012345678912'),
            ('aws:sourceowner', '*')
        ]

        for condition in test_condition_list:
            for arn_type in arn_types:
                test_condition = {
                    condition: {
                        arn_type[0]: arn_type[1]
                    }
                }

                result = ARN.extract_arns_from_statement_condition(test_condition)
                self.assertIsInstance(result, list)
                self.assertTrue(len(result) > 0)

        for condition in bad_condition_list:
            for arn_type in arn_types:
                test_condition = {
                    condition: {
                        arn_type[0]: arn_type[1]
                    }
                }

                result = ARN.extract_arns_from_statement_condition(test_condition)
                self.assertIsInstance(result, list)
                self.assertTrue(len(result) == 0)
    def _parse_arn(self, arn_input, account_numbers, es_domain):
        if arn_input == '*':
            notes = "An ElasticSearch Service domain policy where { 'Principal': { 'AWS': '*' } } must also have"
            notes += " a {'Condition': {'IpAddress': { 'AWS:SourceIp': '<ARN>' } } }"
            notes += " or it is open to any AWS account."
            self.add_issue(20,
                           'ES cluster open to all AWS accounts',
                           es_domain,
                           notes=notes)
            return

        arn = ARN(arn_input)
        if arn.error:
            self.add_issue(3,
                           'Auditor could not parse ARN',
                           es_domain,
                           notes=arn_input)
            return

        if arn.tech == 's3':
            notes = "The ElasticSearch Service domain allows access from S3 bucket [{}]. ".format(
                arn.name)
            notes += "Security Monkey does not yet have the capability to determine if this is "
            notes += "a friendly S3 bucket.  Please verify manually."
            self.add_issue(3,
                           'ES cluster allows access from S3 bucket',
                           es_domain,
                           notes=notes)
        else:
            account_numbers.append(arn.account_number)
Example #6
0
    def _parse_arn(self, arn_input, account_numbers, sqsitem):
        if arn_input == '*':
            notes = "An SQS policy where { 'Principal': { 'AWS': '*' } } must also have"
            notes += " a {'Condition': {'StringEquals': { 'AWS:SourceOwner': '<ARN>' } } }"
            notes += " or it is open to the world."
            self.add_issue(10,
                           'SQS Queue open to everyone',
                           sqsitem,
                           notes=notes)
            return

        arn = ARN(arn_input)
        if arn.error:
            self.add_issue(3,
                           'Auditor could not parse ARN',
                           sqsitem,
                           notes=arn_input)
            return

        if arn.tech == 's3':
            notes = "SQS allows access from S3 bucket [{}]. ".format(arn.name)
            notes += "Security Monkey does not yet have the capability to determine if this is "
            notes += "a friendly S3 bucket.  Please verify manually."
            self.add_issue(3,
                           'SQS allows access from S3 bucket',
                           sqsitem,
                           notes=notes)
        else:
            account_numbers.append(arn.account_number)
    def test_from_arn(self):
        proper_arns = [
            'events.amazonaws.com', 'cloudtrail.amazonaws.com',
            'arn:aws:iam::012345678910:root',
            'arn:aws:iam::012345678910:role/SomeTestRoleForTesting',
            'arn:aws:iam::012345678910:instance-profile/SomeTestInstanceProfileForTesting',
            'arn:aws:iam::012345678910:role/*',
            'arn:aws:iam::012345678910:role/SomeTestRole*',
            'arn:aws:s3:::some-s3-bucket', 'arn:aws:s3:*:*:some-s3-bucket',
            'arn:aws:s3:::some-s3-bucket/some/path/within/the/bucket'
            'arn:aws:s3:::some-s3-bucket/*',
            'arn:aws:ec2:us-west-2:012345678910:instance/*',
            'arn:aws:ec2:ap-northeast-1:012345678910:security-group/*',
            'arn:aws-cn:ec2:ap-northeast-1:012345678910:security-group/*',
            'arn:aws-us-gov:ec2:gov-west-1:012345678910:instance/*',
            'arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EXXXXXXXXXXXXX'
        ]

        # Proper ARN Tests:
        for arn in proper_arns:
            app.logger.info('Testing Proper ARN: {}'.format(arn))
            arn_obj = ARN(arn)

            self.assertFalse(arn_obj.error)
            if "root" in arn:
                self.assertTrue(arn_obj.root)
            else:
                self.assertFalse(arn_obj.root)

            if ".amazonaws.com" in arn:
                self.assertTrue(arn_obj.service)
            else:
                self.assertFalse(arn_obj.service)

        bad_arns = [
            'arn:aws:iam::012345678910', 'arn:aws:iam::012345678910:', '*',
            'arn:s3::::', "arn:arn:arn:arn:arn:arn"
        ]

        # Improper ARN Tests:
        for arn in bad_arns:
            app.logger.info('Testing IMPROPER ARN: {}'.format(arn))
            arn_obj = ARN(arn)

            self.assertTrue(arn_obj.error)
Example #8
0
    def check_sqsqueue_crossaccount(self, sqsitem):
        """
        alert on cross account access
        """
        policy = sqsitem.config
        for statement in policy.get("Statement", []):
            account_numbers = []
            princ = statement.get("Principal", None)
            if not princ:
                # It is possible not to define a principal, AWS ignores these statements.
                # We should raise an issue.
                tag = "SQS Policy is lacking Principal field"
                notes = json.dumps(statement)
                self.add_issue(5, tag, sqsitem, notes=notes)
                continue
            if isinstance(princ, dict):
                princ_val = princ.get("AWS") or princ.get("Service")
            else:
                princ_val = princ

            if princ_val == "*":
                condition = statement.get('Condition', {})
                arns = ARN.extract_arns_from_statement_condition(condition)
                if not arns:
                    tag = "SQS Queue open to everyone"
                    notes = "An SQS policy where { 'Principal': { 'AWS': '*' } } must also have"
                    notes += " a {'Condition': {'ArnEquals': { 'AWS:SourceArn': '<ARN>' } } }"
                    notes += " or it is open to the world. In this case, anyone is allowed to perform "
                    notes += " this action(s): {}".format(statement.get("Action"))
                    self.add_issue(10, tag, sqsitem, notes=notes)

                for arn in arns:
                    self._parse_arn(arn, account_numbers, sqsitem)

            else:
                if isinstance(princ_val, list):
                    for entry in princ_val:
                        arn = ARN(entry)
                        if arn.error:
                            self.add_issue(3, 'Auditor could not parse ARN', sqsitem, notes=entry)
                            continue

                        if not arn.service:
                            account_numbers.append(arn.account_number)
                else:
                    arn = ARN(princ_val)
                    if arn.error:
                        self.add_issue(3, 'Auditor could not parse ARN', sqsitem, notes=princ_val)
                    elif not arn.service:
                        account_numbers.append(arn.account_number)

            for account_number in account_numbers:
                self._check_cross_account(account_number, sqsitem, 'policy')
Example #9
0
    def _parse_arn(self, arn_input, account_numbers, snsitem):
        arn = ARN(arn_input)
        if arn.error:
            self.add_issue(3, 'Auditor could not parse ARN', snsitem, notes=arn_input)
            return

        if arn.tech == 's3':
            notes = "SNS allows access from S3 bucket [{}]. ".format(arn.name)
            notes += "Security Monkey does not yet have the capability to determine if this is "
            notes += "a friendly S3 bucket.  Please verify manually."
            self.add_issue(3, 'SNS allows access from S3 bucket', snsitem, notes=notes)
        else:
            account_numbers.append(arn.account_number)
Example #10
0
    def test_from_arn(self):
        proper_arns = [
            'arn:aws:iam::012345678910:root',
            'arn:aws:iam::012345678910:role/SomeTestRoleForTesting',
            'arn:aws:iam::012345678910:instance-profile/SomeTestInstanceProfileForTesting',
            'arn:aws:iam::012345678910:role/*',
            'arn:aws:iam::012345678910:role/SomeTestRole*',
            'arn:aws:s3:::some-s3-bucket',
            'arn:aws:s3:::some-s3-bucket/some/path/within/the/bucket'
            'arn:aws:s3:::some-s3-bucket/*',
            'arn:aws:ec2:us-west-2:012345678910:instance/*',
            'arn:aws:ec2:ap-northeast-1:012345678910:security-group/*',
            'arn:aws-cn:ec2:ap-northeast-1:012345678910:security-group/*',
            'arn:aws-us-gov:ec2:gov-west-1:012345678910:instance/*'
        ]

        # Proper ARN Tests:
        for arn in proper_arns:
            app.logger.info('Testing Proper ARN: {}'.format(arn))
            arn_obj = ARN(arn)

            self.assertFalse(arn_obj.error)
            if "root" in arn:
                self.assertTrue(arn_obj.root)
            else:
                self.assertFalse(arn_obj.root)

        bad_arns = [
            'arn:aws:iam::012345678910', 'arn:aws:iam::012345678910:', '*',
            'arn:s3::::', "arn:arn:arn:arn:arn:arn"
        ]

        # Improper ARN Tests:
        for arn in bad_arns:
            app.logger.info('Testing IMPROPER ARN: {}'.format(arn))
            arn_obj = ARN(arn)

            self.assertTrue(arn_obj.error)
Example #11
0
    def test_extract_arns_from_statement_condition(self):
        test_condition_list = [
            'ArnEquals', 'ForAllValues:ArnEquals', 'ForAnyValue:ArnEquals',
            'ArnLike', 'ForAllValues:ArnLike', 'ForAnyValue:ArnLike',
            'StringLike', 'ForAllValues:StringLike', 'ForAnyValue:StringLike',
            'StringEquals', 'ForAllValues:StringEquals',
            'ForAnyValue:StringEquals'
        ]

        bad_condition_list = [
            'NotACondition', 'ArnLikeSomethingNotARealCondition'
        ]

        arn_types = [('aws:sourcearn', 'arn:aws:s3:::some-s3-bucket'),
                     ('aws:sourcearn', 'arn:aws:s3:::some-s3-bucket/*'),
                     ('aws:sourcearn', "*"),
                     ('aws:sourceowner', '012345678912'),
                     ('aws:sourceowner', '*')]

        for condition in test_condition_list:
            for arn_type in arn_types:
                test_condition = {condition: {arn_type[0]: arn_type[1]}}

                result = ARN.extract_arns_from_statement_condition(
                    test_condition)
                self.assertIsInstance(result, list)
                self.assertTrue(len(result) > 0)

        for condition in bad_condition_list:
            for arn_type in arn_types:
                test_condition = {condition: {arn_type[0]: arn_type[1]}}

                result = ARN.extract_arns_from_statement_condition(
                    test_condition)
                self.assertIsInstance(result, list)
                self.assertTrue(len(result) == 0)
Example #12
0
    def check_snstopicpolicy_crossaccount(self, snsitem):
        """
        alert on cross account access
        """
        policy = snsitem.config.get('policy', {})
        for statement in policy.get("Statement", []):
            account_numbers = []
            princ = statement.get("Principal", {})
            if isinstance(princ, dict):
                princ_val = princ.get("AWS") or princ.get("Service")
            else:
                princ_val = princ

            if princ_val == "*":
                condition = statement.get('Condition', {})
                arns = ARN.extract_arns_from_statement_condition(condition)

                if not arns:
                    tag = "SNS Topic open to everyone"
                    notes = "An SNS policy where { 'Principal': { 'AWS': '*' } } must also have"
                    notes += " a {'Condition': {'StringEquals': { 'AWS:SourceOwner': '<ARN>' } } }"
                    notes += " or it is open to the world. In this case, anyone is allowed to perform "
                    notes += " this action(s): {}".format(statement.get("Action"))
                    self.add_issue(10, tag, snsitem, notes=notes)

                for arn in arns:
                    self._parse_arn(arn, account_numbers, snsitem)

            else:
                if isinstance(princ_val, list):
                    for entry in princ_val:
                        arn = ARN(entry)
                        if arn.error:
                            self.add_issue(3, 'Auditor could not parse ARN', snsitem, notes=entry)
                            continue

                        if not arn.service:
                            account_numbers.append(arn.account_number)
                else:
                    arn = ARN(princ_val)
                    if arn.error:
                        self.add_issue(3, 'Auditor could not parse ARN', snsitem, notes=princ_val)
                    elif not arn.service:
                        account_numbers.append(arn.account_number)

            for account_number in account_numbers:
                self._check_cross_account(account_number, snsitem, 'policy')
            def check_account_in_arn(input):
                from security_monkey.common.arn import ARN
                arn = ARN(input)

                if arn.error:
                    print('Could not parse ARN in Trust Policy: {arn}'.format(
                        arn=input))

                if not arn.error and arn.account_number:
                    account = Account.query.filter(
                        Account.number == arn.account_number).first()
                    if not account:
                        tag = "IAM Role allows assume-role from an " \
                            + "Unknown Account ({account_number})".format(
                            account_number=arn.account_number)
                        self.add_issue(10,
                                       tag,
                                       iamrole_item,
                                       notes=json.dumps(statement))
Example #14
0
    def process_cross_account(self, input, s3_item):
        from security_monkey.common.arn import ARN
        arn = ARN(input)

        if arn.error and input != input:
            message = "POLICY - Bad ARN"
            notes = "{}".format(arn)
            self.add_issue(3, message, s3_item, notes=notes)
            return

        # 'WILDCARD ARN: *'
        # This is caught by check_policy_allow_all(), so ignore here.
        if '*' == arn.account_number:
            print("This is an odd arn: {}".format(arn))
            return

        account = Account.query.filter(
            Account.identifier == arn.account_number).first()
        if account:
            # Friendly Account.
            if not account.third_party:
                message = "POLICY - Friendly Account Access."
                notes = "{}".format(account.name)
                self.add_issue(0, message, s3_item, notes=notes)
                return
            # Friendly Third Party
            else:
                message = "POLICY - Friendly Third Party Account Access."
                notes = "{}".format(account.name)
                self.add_issue(0, message, s3_item, notes=notes)
                return

        # Foreign Unknown Account
        message = "POLICY - Unknown Cross Account Access."
        notes = "Account ID: {} ARN: {}".format(arn.account_number, input)
        self.add_issue(10, message, s3_item, notes=notes)
        return
    def check_es_access_policy(self, es_domain):
        policy = es_domain.config["policy"]

        for statement in policy.get("Statement", []):
            effect = statement.get("Effect")
            # We only care about "Allows"
            if effect.lower() == "deny":
                continue

            account_numbers = []
            princ = statement.get("Principal", {})
            if isinstance(princ, dict):
                princ_val = princ.get("AWS") or princ.get("Service")
            else:
                princ_val = princ

            if princ_val == "*":
                condition = statement.get('Condition', {})

                # Get the IpAddress subcondition:
                ip_addr_condition = condition.get("IpAddress")

                if ip_addr_condition:
                    source_ip_condition = ip_addr_condition.get("aws:SourceIp")

                if not ip_addr_condition or not source_ip_condition:
                    tag = "ElasticSearch Service domain open to everyone"
                    notes = "An ElasticSearch Service domain policy where { 'Principal': { '*' } } OR"
                    notes += " { 'Principal': { 'AWS': '*' } } must also have a"
                    notes += " {'Condition': {'IpAddress': { 'AWS:SourceIp': '<ARN>' } } }"
                    notes += " or it is open to the world. In this case, anyone is allowed to perform "
                    notes += " this action(s): {}".format(
                        statement.get("Action"))
                    self.add_issue(20, tag, es_domain, notes=notes)

                else:
                    # Check for "aws:SourceIp" as a condition:
                    if isinstance(source_ip_condition, list):
                        for cidr in source_ip_condition:
                            self._check_proper_cidr(cidr, es_domain,
                                                    statement.get("Action"))

                    else:
                        self._check_proper_cidr(source_ip_condition, es_domain,
                                                statement.get("Action"))

            else:
                if isinstance(princ_val, list):
                    for entry in princ_val:
                        arn = ARN(entry)
                        if arn.error:
                            self.add_issue(3,
                                           'Auditor could not parse ARN',
                                           es_domain,
                                           notes=entry)
                            continue

                        if arn.root:
                            self._check_cross_account_root(
                                es_domain, arn, statement.get("Action"))

                        if not arn.service:
                            account_numbers.append(arn.account_number)
                else:
                    arn = ARN(princ_val)
                    if arn.error:
                        self.add_issue(3,
                                       'Auditor could not parse ARN',
                                       es_domain,
                                       notes=princ_val)
                    else:
                        if arn.root:
                            self._check_cross_account_root(
                                es_domain, arn, statement.get("Action"))
                        if not arn.service:
                            account_numbers.append(arn.account_number)

            for account_number in account_numbers:
                self._check_cross_account(account_number, es_domain, 'policy')