From e94b322829411828f27b277d1f69947fb58f0851 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Tue, 9 Jul 2024 16:13:31 -0700 Subject: [PATCH 01/33] Added additional permissions required for ADA-CP. --- doc/aws-minimal-permission-policy.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/aws-minimal-permission-policy.json b/doc/aws-minimal-permission-policy.json index ff551fabc..9cfe3fca3 100644 --- a/doc/aws-minimal-permission-policy.json +++ b/doc/aws-minimal-permission-policy.json @@ -5,12 +5,16 @@ "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ + "account:GetAlternateContact", + "account:GetContactInformation", "acm:DescribeCertificate", "acm:ListCertificates", "cloudformation:DescribeStacks", "cloudformation:GetStackPolicy", "cloudformation:GetTemplate", "cloudformation:ListStacks", + "cloudtrail:DescribeAlarms", + "cloudtrail:DescribeMetricFilters", "cloudtrail:DescribeTrails", "cloudtrail:GetEventSelectors", "cloudtrail:GetTrailStatus", @@ -50,8 +54,8 @@ "ec2:DescribeVpcs", "ec2:DescribeVpnConnections", "ec2:DescribeVpnGateways", - "ec2:GetEbsDefaultKmsKeyId", - "ec2:GetEbsEncryptionByDefault", + "ec2:GetEbsDefaultKmsKeyId", + "ec2:GetEbsEncryptionByDefault", "ecr:DescribeImages", "ecr:DescribeRepositories", "ecr:GetLifecyclePolicy", @@ -60,6 +64,7 @@ "ecs:DescribeClusters", "ecs:ListAccountSettings", "ecs:ListClusters", + "efs:DescribeFileSystems", "eks:DescribeCluster", "eks:ListClusters", "elasticache:DescribeCacheClusters", @@ -93,6 +98,7 @@ "iam:GetUserPolicy", "iam:ListAccessKeys", "iam:ListAttachedRolePolicies", + "iam:ListAttachedUserPolicies", "iam:ListEntitiesForPolicy", "iam:ListGroupPolicies", "iam:ListGroups", @@ -121,6 +127,7 @@ "rds:DescribeDBClusterSnapshots", "rds:DescribeDBClusters", "rds:DescribeDBInstances", + "rds:DescribeDBLogFiles", "rds:DescribeDBParameterGroups", "rds:DescribeDBParameters", "rds:DescribeDBSecurityGroups", @@ -144,6 +151,7 @@ "s3:GetBucketWebsite", "s3:GetEncryptionConfiguration", "s3:GetBucketPublicAccessBlock", + "s3:GetPublicAccessBlock", "s3:ListAllMyBuckets", "secretsmanager:ListSecrets", "secretsmanager:DescribeSecret", @@ -156,6 +164,7 @@ "ssm:GetParameters", "sns:GetTopicAttributes", "sns:ListSubscriptions", + "sns:ListSubscriptionsByTopic", "sns:ListTopics", "sqs:GetQueueAttributes", "sqs:ListQueues" From e141947871f7d0b458a41015f499577ff39e5577 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 15 Jul 2024 16:12:07 -0700 Subject: [PATCH 02/33] Corrected errors in the minimal AWS permission policy. --- doc/aws-minimal-permission-policy.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/aws-minimal-permission-policy.json b/doc/aws-minimal-permission-policy.json index 9cfe3fca3..56646afcd 100644 --- a/doc/aws-minimal-permission-policy.json +++ b/doc/aws-minimal-permission-policy.json @@ -13,8 +13,6 @@ "cloudformation:GetStackPolicy", "cloudformation:GetTemplate", "cloudformation:ListStacks", - "cloudtrail:DescribeAlarms", - "cloudtrail:DescribeMetricFilters", "cloudtrail:DescribeTrails", "cloudtrail:GetEventSelectors", "cloudtrail:GetTrailStatus", @@ -64,7 +62,7 @@ "ecs:DescribeClusters", "ecs:ListAccountSettings", "ecs:ListClusters", - "efs:DescribeFileSystems", + "elasticfilesystem:DescribeFileSystems", "eks:DescribeCluster", "eks:ListClusters", "elasticache:DescribeCacheClusters", @@ -151,7 +149,6 @@ "s3:GetBucketWebsite", "s3:GetEncryptionConfiguration", "s3:GetBucketPublicAccessBlock", - "s3:GetPublicAccessBlock", "s3:ListAllMyBuckets", "secretsmanager:ListSecrets", "secretsmanager:DescribeSecret", From e2b5606257759121cc6a8d0c925c445a5c1b6d10 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 15 Jul 2024 16:14:10 -0700 Subject: [PATCH 03/33] Removed a redundant entry from the minimal AWS permission policy. --- doc/aws-minimal-permission-policy.json | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/aws-minimal-permission-policy.json b/doc/aws-minimal-permission-policy.json index 56646afcd..35d9c0686 100644 --- a/doc/aws-minimal-permission-policy.json +++ b/doc/aws-minimal-permission-policy.json @@ -62,7 +62,6 @@ "ecs:DescribeClusters", "ecs:ListAccountSettings", "ecs:ListClusters", - "elasticfilesystem:DescribeFileSystems", "eks:DescribeCluster", "eks:ListClusters", "elasticache:DescribeCacheClusters", From 6585ac029e05fbc140c48f3d58d53c7bb4fa304c Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 15 Jul 2024 18:56:00 -0700 Subject: [PATCH 04/33] Added a rule for Lambda runtime deprecation. AWS doesn't have a programmatic interface for runtime deprecation dates. Consequently, I used a horrible kludge: hard-coding dates copied from https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html. If the table hasn't been updated in the last 180 days, ScoutSuite will warn the user. Owing to this kludge, this rule has *not* been added to the default ruleset. However, it is in the detailed ruleset. If AWS ever adds a programmatic interface, we can update this rule and add it to the default ruleset. --- ...rvices.awslambda.regions.id.functions.html | 2 + .../aws/resources/awslambda/functions.py | 58 +++++++++++++++++++ .../awslambda-runtime-deprecated.json | 19 ++++++ .../aws/rules/rulesets/detailed.json | 6 ++ 4 files changed, 85 insertions(+) create mode 100644 ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json diff --git a/ScoutSuite/output/data/html/partials/aws/services.awslambda.regions.id.functions.html b/ScoutSuite/output/data/html/partials/aws/services.awslambda.regions.id.functions.html index e7ecff276..19c6215bb 100755 --- a/ScoutSuite/output/data/html/partials/aws/services.awslambda.regions.id.functions.html +++ b/ScoutSuite/output/data/html/partials/aws/services.awslambda.regions.id.functions.html @@ -9,6 +9,8 @@

Information

Description: {{value_or_none description}}
Last Modified: {{format_date last_modified}}
Runtime: {{value_or_none runtime}}
+
Runtime deprecated: {{value_or_none runtime_deprecated}}
+
Runtime deprecation date: {{value_or_none date_runtime_deprecated}}
Version: {{value_or_none version}}
Revision ID: {{value_or_none revision_id}}
diff --git a/ScoutSuite/providers/aws/resources/awslambda/functions.py b/ScoutSuite/providers/aws/resources/awslambda/functions.py index 12723c623..18f2283d7 100755 --- a/ScoutSuite/providers/aws/resources/awslambda/functions.py +++ b/ScoutSuite/providers/aws/resources/awslambda/functions.py @@ -1,12 +1,15 @@ +import datetime from ScoutSuite.providers.aws.facade.base import AWSFacade from ScoutSuite.providers.aws.resources.base import AWSResources from ScoutSuite.providers.utils import get_non_provider_id +from ScoutSuite.core.console import print_exception class Functions(AWSResources): def __init__(self, facade: AWSFacade, region: str): super().__init__(facade) self.region = region + self._deprecationWarningShown = False async def fetch_all(self): raw_functions = await self.facade.awslambda.get_functions(self.region) @@ -31,6 +34,10 @@ async def _parse_function(self, raw_function): function_dict['tracing_config'] = raw_function.get('TracingConfig') function_dict['revision_id'] = raw_function.get('RevisionId') + deprecation_date = self._get_deprecation_date(function_dict['runtime']) + function_dict['runtime_deprecated'] = not None is deprecation_date and datetime.date.today() >= deprecation_date + function_dict['date_runtime_deprecated'] = str(deprecation_date) + await self._add_role_information(function_dict, raw_function.get('Role')) await self._add_access_policy_information(function_dict) await self._add_env_variables(function_dict) @@ -70,3 +77,54 @@ async def _add_env_variables(self, function_dict): else: function_dict["env_variable_names"] = [] function_dict["env_variable_values"] = [] + + def _get_deprecation_date(self, runtime): + # As of July 2024, the Lambda API does not have a way to determine whether a Lambda + # runtime is deprecated; that information is only available in AWS documentation. + # Consequently, the table here will need to be updated from time to time. + # Upcoming deprecation dates: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtimes-supported + # Past deprecation dates: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtimes-deprecated + + # Table of runtime identifier : deprecation date + # If a particular runtime identifier does not appear in the table, then no deprecation + # date for the runtime has been announced. + last_updated = datetime.date(2024, 7, 15) + deprecations = { + 'dotnet6': datetime.date(2024, 12, 20), # Dec 20, 2024 + 'python3.8': datetime.date(2024, 10, 14), # Oct 14, 2024 + 'nodejs16.x': datetime.date(2024, 6, 12), # Jun 12, 2024 + 'dotnet7': datetime.date(2024, 5, 14), # May 14, 2024 + 'java8': datetime.date(2024, 1, 8), # Jan 8, 2024 + 'go1.x': datetime.date(2024, 1, 8), # Jan 8, 2024 + 'provided': datetime.date(2024, 1, 8), # Jan 8, 2024 + 'ruby2.7': datetime.date(2023, 12, 7), # Dec 7, 2023 + 'nodejs14.x': datetime.date(2023, 12, 4), # Dec 4, 2023 + 'python3.7': datetime.date(2023, 12, 4), # Dec 4, 2023 + 'dotnetcore3.1': datetime.date(2023, 4, 3), # Apr 3, 2023 + 'nodejs12.x': datetime.date(2023, 3, 31), # Mar 31, 2023 + 'python3.6': datetime.date(2022, 7, 18), # Jul 18, 2022 + 'dotnet5.0': datetime.date(2022, 5, 10), # May 10, 2022 + 'dotnetcore2.1': datetime.date(2022, 1, 5), # Jan 5, 2022 + 'nodejs10.x': datetime.date(2021, 7, 30), # Jul 30, 2021 + 'ruby2.5': datetime.date(2021, 7, 30), # Jul 30, 2021 + 'python2.7': datetime.date(2021, 7, 15), # Jul 15, 2021 + 'nodejs8.10': datetime.date(2020, 3, 6), # Mar 6, 2020 + 'nodejs4.3': datetime.date(2020, 3, 5), # Mar 5, 2020 + 'nodejs4.3-edge': datetime.date(2020, 3, 5), # Mar 5, 2020 + 'nodejs6.10': datetime.date(2019, 8, 12), # Aug 12, 2019 + 'dotnetcore1.0': datetime.date(2019, 7, 27), # Jun 27, 2019 + 'dotnetcore2.0': datetime.date(2019, 5, 30), # May 30, 2019 + 'nodejs': datetime.date(2016, 10, 31), # Oct 31, 2016 + } + + # Warn if the table hasn't been updated + if datetime.timedelta(days=180) < datetime.date.today() - last_updated \ + and not self._deprecationWarningShown: + print_exception('Deprecation table has not been updated in over 180 days. Please ' + 'update ScoutSuite to the latest release or update the deprecations table in ' + 'ScoutSuite/providers/aws/resources/awslambda/functions.py') + self._deprecationWarningShown = True + + if not runtime in deprecations: + return None + return deprecations[runtime] diff --git a/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json b/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json new file mode 100644 index 000000000..e40b9517f --- /dev/null +++ b/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json @@ -0,0 +1,19 @@ +{ + "description": "Lambda Function Using Deprecated Runtime", + "rationale": "Deprecated Lambda runtimes no longer receive security updates.", + "remediation": "Switch to a newer, supported runtime.", + "references": [ + "https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html" + ], + "dashboard_name": "Functions", + "path": "awslambda.regions.id.functions.id", + "conditions": [ + "and", + [ + "runtime_deprecated", + "true", + "" + ] + ], + "class_suffix": "runtime_deprecated" +} \ No newline at end of file diff --git a/ScoutSuite/providers/aws/rules/rulesets/detailed.json b/ScoutSuite/providers/aws/rules/rulesets/detailed.json index 38d6f366f..479986636 100755 --- a/ScoutSuite/providers/aws/rules/rulesets/detailed.json +++ b/ScoutSuite/providers/aws/rules/rulesets/detailed.json @@ -16,6 +16,12 @@ "level": "warning" } ], + "awslambda-runtime-deprecated.json": [ + { + "enabled": true, + "level": "warning" + } + ], "cloudformation-stack-with-role.json": [ { "enabled": true, From 3f544349f3f551feebe7429e056335d086524dee Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 15 Jul 2024 19:03:35 -0700 Subject: [PATCH 05/33] Unit test for the new AWS lambda deprecation rule. This test handle only rule processing, not data acquisition (which is where most of the logic is). test_rules_processingengine.py only used rules from the default ruleset. Since awslambda-runtime-deprecated is *not* in the default ruleset, I repurposed some dead code relating to a test ruleset: the test ruleset is now merged with the default ruleset when running this test. --- .../awslambda-runtime-deprecated.json | 1 + tests/data/rule-configs/awslambda.json | 343 ++++++++++++++++++ .../awslambda-runtime-deprecated.json | 4 + tests/data/ruleset-test.json | 10 +- tests/test_rules_processingengine.py | 13 +- 5 files changed, 365 insertions(+), 6 deletions(-) create mode 120000 tests/data/rule-configs/awslambda-runtime-deprecated.json create mode 100644 tests/data/rule-configs/awslambda.json create mode 100644 tests/data/rule-results/awslambda-runtime-deprecated.json diff --git a/tests/data/rule-configs/awslambda-runtime-deprecated.json b/tests/data/rule-configs/awslambda-runtime-deprecated.json new file mode 120000 index 000000000..5b59178b2 --- /dev/null +++ b/tests/data/rule-configs/awslambda-runtime-deprecated.json @@ -0,0 +1 @@ +awslambda.json \ No newline at end of file diff --git a/tests/data/rule-configs/awslambda.json b/tests/data/rule-configs/awslambda.json new file mode 100644 index 000000000..abd221974 --- /dev/null +++ b/tests/data/rule-configs/awslambda.json @@ -0,0 +1,343 @@ +{ + "account_id": "123456789012", + "services": { + "awslambda": { + "filters": {}, + "findings": { + "awslambda-runtime-deprecated": { + "checked_items": 2, + "class_suffix": "runtime_deprecated", + "compliance": null, + "dashboard_name": "Functions", + "description": "Lambda Function Using Deprecated Runtime", + "flagged_items": 1, + "items": [ + "awslambda.regions.us-east-1.functions.scoutid-b47af3e35341b60759c8e3c3e426b5b0216a1e35.runtime_deprecated" + ], + "level": "warning", + "path": "awslambda.regions.id.functions.id", + "rationale": "Deprecated Lambda runtimes no longer receive security updates.", + "references": [ + "https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html" + ], + "remediation": "Switch to a newer, supported runtime.", + "service": "Lambda" + } + }, + "functions_count": 2, + "regions": { + "us-east-1": { + "functions": { + "scoutid-640ab2bae07bedc4c163f679a746f7ab7fb5d1fa": { + "access_policy": { + "Id": "default", + "Statement": [], + "Version": "2012-10-17" + }, + "arn": "arn:aws:lambda:us-east-1:123456789012:function:Test1", + "code_sha256": "ozqmTyk2fnrKvm6fOjTpTgCCWSzYe3fyTMHO8rh6Iis=", + "code_size": 287, + "date_runtime_deprecated": "None", + "description": "", + "env_variable_names": [], + "env_variable_values": [], + "env_variables": [], + "execution_role": { + "Arn": "arn:aws:iam::123456789012:role/service-role/Test1-role", + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Condition": { + "ArnLike": { + "aws:SourceArn": "arn:aws:lambda:us-east-1:123456789012:function:Test1" + }, + "Bool": { + "aws:ViaAWSService": "false" + }, + "Null": { + "aws:SourceAccount": "false", + "aws:SourceArn": "false" + }, + "StringEquals": { + "aws:SourceAccount": "123456789012" + } + }, + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "CreateDate": "2024-01-24 23:50:06+00:00", + "MaxSessionDuration": 3600, + "Path": "/service-role/", + "RoleId": "AROAAAAAAAAAAAAAAAAAA", + "RoleLastUsed": { + "LastUsedDate": "2024-05-23 00:00:37+00:00", + "Region": "us-east-1" + }, + "RoleName": "Test1-role-gs4r4cl9", + "policies": [ + { + "Document": { + "Statement": [ + { + "Action": "logs:CreateLogGroup", + "Effect": "Allow", + "Resource": "arn:aws:logs:us-east-1:123456789012:*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/Test1:*" + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyArn": "arn:aws:iam::123456879012:policy/service-role/AWSLambdaBasicExecutionRole-c693a6e5-35e0-4e71-9176-a449e4efc40d", + "PolicyName": "AWSLambdaBasicExecutionRole-c693a6e5-35e0-4e71-9176-a449e4efc40d" + } + ], + "policy_statements": [ + { + "Action": "logs:CreateLogGroup", + "Effect": "Allow", + "Resource": "arn:aws:logs:us-east-1:123456789012:*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/Test1:*" + ] + } + ] + }, + "handler": "lambda_function.lambda_handler", + "last_modified": "2024-05-22T20:43:52.000+0000", + "memory_size": 128, + "name": "Test1", + "revision_id": "c9906c99-df60-4a13-94cc-b90ca69b970d", + "role_arn": "arn:aws:iam::123456789012:role/service-role/Test1-role-gs4r4cl9", + "runtime": "python3.12", + "runtime_deprecated": false, + "timeout": 3, + "tracing_config": { + "Mode": "PassThrough" + }, + "version": "$LATEST" + }, + "scoutid-b47af3e35341b60759c8e3c3e426b5b0216a1e35": { + "access_policy": { + "Id": "default", + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Condition": { + "ArnLike": { + "AWS:SourceArn": "arn:aws:events:us-east-1:123456789012:rule/TestRule" + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": "arn:aws:lambda:us-east-1:123456789012:function:Test2" + } + ], + "Version": "2012-10-17" + }, + "arn": "arn:aws:lambda:us-east-1:123456789012:function:Test2", + "code_sha256": "m2FX3CW1rbebsw7RjAuG7YeRu/Ej7SVvExLbgAYUt4M=", + "code_size": 2379, + "date_runtime_deprecated": "2022-07-18", + "description": "Test 2", + "env_variable_names": [ + "GROUP", + "POLICY_ARN", + "INSTANCE_PROFILE_ARN" + ], + "env_variable_values": [ + "DEV", + "arn:aws:iam::123456789012:policy/TestPolicy", + "arn:aws:iam::123456789012:instance-profile/TestProfile" + ], + "env_variables": { + "INSTANCE_PROFILE_ARN": "arn:aws:iam::123456789012:instance-profile/TestProfile", + "GROUP": "DEV", + "POLICY_ARN": "arn:aws:iam::123456789012:policy/TestPolicy" + }, + "execution_role": { + "Arn": "arn:aws:iam::123456789012:role/Test2-role", + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "CreateDate": "2021-03-23 19:09:46+00:00", + "Description": "", + "MaxSessionDuration": 3600, + "Path": "/", + "RoleId": "AROABBBBBBBBBBBBBBBBB", + "RoleLastUsed": { + "LastUsedDate": "2024-03-13 19:17:35+00:00", + "Region": "us-east-1" + }, + "RoleName": "Test2-role", + "policies": [ + { + "Document": { + "Statement": [ + { + "Action": [ + "iam:CreateInstanceProfile", + "iam:ListInstanceProfiles", + "iam:ListAttachedRolePolicies", + "iam:GetInstanceProfile", + "ec2:DescribeIamInstanceProfileAssociations", + "ec2:AssociateIamInstanceProfile", + "ec2:DisassociateIamInstanceProfile", + "ec2:DescribeTags", + "ec2:CreateTags" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:AttachRolePolicy", + "Condition": { + "ArnLike": { + "iam:PolicyARN": [ + "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + ] + } + }, + "Effect": "Allow", + "Resource": "arn:aws:iam::123456789012:role/*" + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringEquals": { + "iam:PassedToService": "ec2.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": "arn:aws:iam::123456789012:role/Test2-role" + }, + { + "Action": "iam:AddRoleToInstanceProfile", + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::123456789012:instance-profile/*", + "arn:aws:iam::123456789012:role/Test2-role" + ] + }, + { + "Action": "logs:*", + "Effect": "Allow", + "Resource": "arn:aws:logs:*" + } + ], + "Version": "2012-10-17" + }, + "PolicyArn": "arn:aws:iam::123456789012:policy/TestPolicy", + "PolicyName": "TestPolicy" + } + ], + "policy_statements": [ + { + "Action": [ + "iam:CreateInstanceProfile", + "iam:ListInstanceProfiles", + "iam:ListAttachedRolePolicies", + "iam:GetInstanceProfile", + "ec2:DescribeIamInstanceProfileAssociations", + "ec2:AssociateIamInstanceProfile", + "ec2:DisassociateIamInstanceProfile", + "ec2:DescribeTags", + "ec2:CreateTags" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:AttachRolePolicy", + "Condition": { + "ArnLike": { + "iam:PolicyARN": [ + "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + ] + } + }, + "Effect": "Allow", + "Resource": "arn:aws:iam::123456789012:role/*" + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringEquals": { + "iam:PassedToService": "ec2.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": "arn:aws:iam::123456789012:role/Test2-role" + }, + { + "Action": "iam:AddRoleToInstanceProfile", + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::123456789012:instance-profile/*", + "arn:aws:iam::123456789012:role/Test2-role" + ] + }, + { + "Action": "logs:*", + "Effect": "Allow", + "Resource": "arn:aws:logs:*" + } + ] + }, + "handler": "handler.event_handler", + "last_modified": "2021-03-23T21:55:59.377+0000", + "memory_size": 128, + "name": "Test2", + "revision_id": "8733e9d3-7ece-4974-8d99-3c5afc7f948e", + "role_arn": "arn:aws:iam::123456789012:role/Test2-role", + "runtime": "python3.6", + "runtime_deprecated": true, + "timeout": 300, + "tracing_config": { + "Mode": "PassThrough" + }, + "version": "$LATEST" + } + }, + "functions_count": 2, + "id": "us-east-1", + "name": "us-east-1", + "region": "us-east-1" + } + }, + "regions_count": 1 + } + } +} diff --git a/tests/data/rule-results/awslambda-runtime-deprecated.json b/tests/data/rule-results/awslambda-runtime-deprecated.json new file mode 100644 index 000000000..7f4edc0f4 --- /dev/null +++ b/tests/data/rule-results/awslambda-runtime-deprecated.json @@ -0,0 +1,4 @@ +[ + "awslambda.regions.us-east-1.functions.scoutid-b47af3e35341b60759c8e3c3e426b5b0216a1e35.runtime_deprecated" +] + diff --git a/tests/data/ruleset-test.json b/tests/data/ruleset-test.json index 5732a788b..d631480f9 100755 --- a/tests/data/ruleset-test.json +++ b/tests/data/ruleset-test.json @@ -5,7 +5,13 @@ "enabled": true, "level": "danger" } + ], + "awslambda-runtime-deprecated.json": [ + { + "enabled": true, + "level": "warning" + } ] }, - "about": "regression test" -} \ No newline at end of file + "about": "extra test rules" +} diff --git a/tests/test_rules_processingengine.py b/tests/test_rules_processingengine.py index 8e0ee7b06..53ddc0ece 100755 --- a/tests/test_rules_processingengine.py +++ b/tests/test_rules_processingengine.py @@ -23,24 +23,29 @@ def setUp(self): # Check that one testcase per finding rule exists (should be within default ruleset) def test_all_finding_rules(self): - ruleset_file_name = os.path.join(self.test_dir, 'data/ruleset-test.json') + # Test everything in the "default" ruleset # FIXME this is only for AWS with open(os.path.join(self.test_dir, '../ScoutSuite/providers/aws/rules/rulesets/default.json'), 'rt') as f: - ruleset = json.load(f) + default_ruleset = json.load(f) + # Also test additional test rules + with open(os.path.join(self.test_dir, 'data/ruleset-test.json'), 'rt') as f: + extra_ruleset = json.load(f) + + ruleset = default_ruleset | extra_ruleset for rule_file_name in ruleset['rules']: self.rule_counters['found'] += 1 rule = ruleset['rules'][rule_file_name][0] rule['enabled'] = True print(rule_file_name) - self._test_rule(ruleset_file_name, rule_file_name, rule) + self._test_rule(rule_file_name, rule) print('Existing rules: %d' % self.rule_counters['found']) print('Processed rules: %d' % self.rule_counters['tested']) print('Verified rules: %d' % self.rule_counters['verified']) - def _test_rule(self, ruleset_file_name, rule_file_name, rule): + def _test_rule(self, rule_file_name, rule): test_config_file_name = os.path.join(self.test_dir, 'data/rule-configs/%s' % rule_file_name) if not os.path.isfile(test_config_file_name): return From 25adc04b28ef1806a6d6c73f76fd377523c3b710 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 15 Jul 2024 19:17:26 -0700 Subject: [PATCH 06/33] New ruleset for ADA-CP. Contains only one rule at the moment, more to come. --- .../providers/aws/rules/rulesets/ada-cp-1.0.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json new file mode 100644 index 000000000..e3c7481bb --- /dev/null +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -0,0 +1,11 @@ +{ + "about": "This ruleset enforces the rules of the App Defense Alliance's Cloud Profile, version 1.0. See for details.", + "rules": { + "awslambda-runtime-deprecated.json": [ + { + "enabled": true, + "level": "danger" + } + ] + } +} \ No newline at end of file From 98decc4c4d46fed9787efe644c8a146859f98501 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Tue, 16 Jul 2024 17:00:44 -0700 Subject: [PATCH 07/33] Fixed ruleset merging in processing engine unit tests. --- tests/test_rules_processingengine.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_rules_processingengine.py b/tests/test_rules_processingengine.py index 53ddc0ece..6112f598f 100755 --- a/tests/test_rules_processingengine.py +++ b/tests/test_rules_processingengine.py @@ -32,13 +32,13 @@ def test_all_finding_rules(self): with open(os.path.join(self.test_dir, 'data/ruleset-test.json'), 'rt') as f: extra_ruleset = json.load(f) - ruleset = default_ruleset | extra_ruleset - for rule_file_name in ruleset['rules']: - self.rule_counters['found'] += 1 - rule = ruleset['rules'][rule_file_name][0] - rule['enabled'] = True - print(rule_file_name) - self._test_rule(rule_file_name, rule) + for ruleset in [default_ruleset, extra_ruleset]: + for rule_file_name in ruleset['rules']: + self.rule_counters['found'] += 1 + rule = ruleset['rules'][rule_file_name][0] + rule['enabled'] = True + print(rule_file_name) + self._test_rule(rule_file_name, rule) print('Existing rules: %d' % self.rule_counters['found']) print('Processed rules: %d' % self.rule_counters['tested']) From 39d994cc60359f2b4b5c3213942206dcb1b70b94 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Tue, 16 Jul 2024 17:13:03 -0700 Subject: [PATCH 08/33] Fixed the AWS rule "iam-no-support-role". The existing rule did not work: the IAM facade only enumerated AWS-managed permission policies that were attached to things so the rule would never trigger. Additionally, the existing rule allowed AWSSupportAccess to be attached to a User or Group, whereas CIS and ADA specifically require it to be attached to a Role. Unfortunately, there is no way to call iam:ListPolicies for only a specific permission policy; the only filtering is by attachment status (which doesn't help) and by path (which also doesn't help since AWSSupportAccess is at the root path). So we have no choice but to pull all permission policies, including the hundreds of AWS-managed policies that the account doesn't use at all. Then we end up pulling their contents and attachments, which takes a long time. I optimize this by skipping unattached policies when querying for contents and attachments. *That* violated an assumption in the policy-parsing logic that all permission policies are valid, so I patched that to skip empty policies (yielding empty lists of statements, which seems to be handled safely everywhere else). With this done, I changed the rule's logic to fail if either AWSSupportAccess is attached to something but not to a Role or if it is not attached to anything at all. I added the rule to the ADA-CP ruleset. I dropped it from "danger" to "warning" in the "detailed" ruleset because it's sort of a silly check as it assumes that an account is being managed in a very specific way. --- ScoutSuite/providers/aws/facade/iam.py | 12 ++- .../providers/aws/resources/iam/base.py | 3 + .../rules/findings/iam-no-support-role.json | 30 +++++-- .../aws/rules/rulesets/ada-cp-1.0.json | 6 ++ .../aws/rules/rulesets/detailed.json | 2 +- .../rule-configs/iam-no-support-role.json | 87 +++++++++++++++++++ .../rule-results/iam-no-support-role.json | 5 ++ tests/data/ruleset-test.json | 6 ++ 8 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 tests/data/rule-configs/iam-no-support-role.json create mode 100644 tests/data/rule-results/iam-no-support-role.json diff --git a/ScoutSuite/providers/aws/facade/iam.py b/ScoutSuite/providers/aws/facade/iam.py index d74c61bbd..60cfc008e 100755 --- a/ScoutSuite/providers/aws/facade/iam.py +++ b/ScoutSuite/providers/aws/facade/iam.py @@ -64,12 +64,22 @@ async def get_groups(self): return groups async def get_policies(self): + # We can't restrict to only attached policies because we need to be able to detect when + # AWSSupportAccess is not attached to anything. And there's no server-side filter that we + # can use to request only that one -- we need to request every AWS-managed policy, + # regardless of whether they're attached to anything. policies = await AWSFacadeUtils.get_all_pages( - 'iam', None, self.session, 'list_policies', 'Policies', OnlyAttached=True) + 'iam', None, self.session, 'list_policies', 'Policies') await get_and_set_concurrently([self._get_and_set_policy_details], policies) return policies async def _get_and_set_policy_details(self, policy): + # Don't look up details for unattached policies; fill in dummy details + if 0 == policy['AttachmentCount']: + policy['PolicyDocument'] = {} + policy['attached_to'] = {} + return + client = AWSFacadeUtils.get_client('iam', self.session) try: policy_version = await run_concurrently( diff --git a/ScoutSuite/providers/aws/resources/iam/base.py b/ScoutSuite/providers/aws/resources/iam/base.py index 0937c1d52..f64099ada 100755 --- a/ScoutSuite/providers/aws/resources/iam/base.py +++ b/ScoutSuite/providers/aws/resources/iam/base.py @@ -78,6 +78,9 @@ def _get_id_for_resource(self, iam_resource_type, resource_name): return resource_id def _parse_permissions(self, policy_name, policy_document, policy_type, iam_resource_type, resource_name): + # Skip empty policies + if not policy_document: + return # Enforce list of statements (Github issue #99) if type(policy_document['Statement']) != list: policy_document['Statement'] = [policy_document['Statement']] diff --git a/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json b/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json index 9bf87e6b2..1bfe62cb2 100644 --- a/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json @@ -1,7 +1,7 @@ { - "description": "No Authorized User to Manage Incidents with AWS Support", - "rationale": "The arn:aws:iam::aws:policy/AWSSupportAccess AWS Managed Policy was not found to be attached to any principal. There should be at least one user authorized to manage incidents with AWS Support.", - "remediation": "Attach the AWSSupportAccess to a role or group", + "description": "No Authorized Role to Manage Incidents with AWS Support", + "rationale": "The arn:aws:iam::aws:policy/AWSSupportAccess AWS Managed Policy was not found to be attached to any Role. There should be at least one Role authorized to manage incidents with AWS Support.", + "remediation": "Attach the AWSSupportAccess to a Role.", "compliance": [ { "name": "CIS Amazon Web Services Foundations", @@ -12,6 +12,16 @@ "name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "1.20" + }, + { + "name": "CIS Amazon Web Services Foundations", + "version": "2.0.0", + "reference": "1.17" + }, + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.2.1" } ], "dashboard_name": "Policies", @@ -25,9 +35,17 @@ "arn:aws:iam::aws:policy/AWSSupportAccess" ], [ - "iam.policies.id.attached_to", - "empty", - "" + "or", + [ + "iam.policies.id.attached_to", + "withoutKey", + "roles" + ], + [ + "iam.policies.id.attached_to.roles", + "empty", + "" + ] ] ] } \ No newline at end of file diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json index e3c7481bb..9dd2e7451 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -6,6 +6,12 @@ "enabled": true, "level": "danger" } + ], + "iam-no-support-role.json": [ + { + "enabled": true, + "level": "danger" + } ] } } \ No newline at end of file diff --git a/ScoutSuite/providers/aws/rules/rulesets/detailed.json b/ScoutSuite/providers/aws/rules/rulesets/detailed.json index 479986636..0d4cce139 100755 --- a/ScoutSuite/providers/aws/rules/rulesets/detailed.json +++ b/ScoutSuite/providers/aws/rules/rulesets/detailed.json @@ -657,7 +657,7 @@ "iam-no-support-role.json": [ { "enabled": true, - "level": "danger" + "level": "warning" } ], "iam-password-policy-expiration-threshold.json": [ diff --git a/tests/data/rule-configs/iam-no-support-role.json b/tests/data/rule-configs/iam-no-support-role.json new file mode 100644 index 000000000..08a1eec6b --- /dev/null +++ b/tests/data/rule-configs/iam-no-support-role.json @@ -0,0 +1,87 @@ +{ + "account_id": "123456879012", + "services": { + "iam": { + "policies": { + "ANPAAAAAAAAAAAAAAAAAA": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "support:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "arn": "arn:aws:iam::aws:policy/AWSSupportAccess", + "attached_to": { + "roles": [ + { + "id": "AROAAAAAAAAAAAAAAAAAA", + "name": "SupportTest" + } + ] + }, + "id": "ANPAAAAAAAAAAAAAAAAAA", + "management": "AWS", + "name": "AWSSupportAccess" + }, + "ANPABBBBBBBBBBBBBBBBB": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "support:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "arn": "arn:aws:iam::aws:policy/AWSSupportAccess", + "attached_to": { + "users": [ + { + "id": "AROAAAAAAAAAAAAAAAAAA", + "name": "SupportTest" + } + ] + }, + "id": "ANPABBBBBBBBBBBBBBBBB", + "management": "AWS", + "name": "AWSSupportAccess" + }, + "ANPACCCCCCCCCCCCCCCCC": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "support:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "arn": "arn:aws:iam::aws:policy/AWSSupportAccess", + "attached_to": { + }, + "id": "ANPACCCCCCCCCCCCCCCCC", + "management": "AWS", + "name": "AWSSupportAccess" + } + } + } + } +} diff --git a/tests/data/rule-results/iam-no-support-role.json b/tests/data/rule-results/iam-no-support-role.json new file mode 100644 index 000000000..2194be88e --- /dev/null +++ b/tests/data/rule-results/iam-no-support-role.json @@ -0,0 +1,5 @@ +[ + "iam.policies.ANPABBBBBBBBBBBBBBBBB", + "iam.policies.ANPACCCCCCCCCCCCCCCCC" +] + diff --git a/tests/data/ruleset-test.json b/tests/data/ruleset-test.json index d631480f9..6b440e1df 100755 --- a/tests/data/ruleset-test.json +++ b/tests/data/ruleset-test.json @@ -11,6 +11,12 @@ "enabled": true, "level": "warning" } + ], + "iam-no-support-role.json": [ + { + "enabled": true, + "level": "danger" + } ] }, "about": "extra test rules" From ba6e33c288b53e1146afbcea822daf90148f443c Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Tue, 16 Jul 2024 17:15:38 -0700 Subject: [PATCH 09/33] Added compliance details to the AWS deprecated Lambda runtime rule. --- .../aws/rules/findings/awslambda-runtime-deprecated.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json b/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json index e40b9517f..5fde5eb97 100644 --- a/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json +++ b/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json @@ -2,6 +2,13 @@ "description": "Lambda Function Using Deprecated Runtime", "rationale": "Deprecated Lambda runtimes no longer receive security updates.", "remediation": "Switch to a newer, supported runtime.", + "compliance": [ + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "1.2.1" + } + ], "references": [ "https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html" ], From c9d4e2e75c610f08f306e86aa8d9e2c4f05467a1 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Tue, 16 Jul 2024 18:41:05 -0700 Subject: [PATCH 10/33] Facade and resources for AWS Accounts contact details. --- ScoutSuite/providers/aws/facade/account.py | 25 +++++++++++++++++++ ScoutSuite/providers/aws/facade/base.py | 2 ++ ScoutSuite/providers/aws/metadata.json | 8 ++++++ .../aws/resources/account/__init__.py | 0 .../providers/aws/resources/account/base.py | 15 +++++++++++ .../aws/resources/account/contacts.py | 18 +++++++++++++ ScoutSuite/providers/aws/services.py | 3 +++ 7 files changed, 71 insertions(+) create mode 100644 ScoutSuite/providers/aws/facade/account.py create mode 100644 ScoutSuite/providers/aws/resources/account/__init__.py create mode 100644 ScoutSuite/providers/aws/resources/account/base.py create mode 100644 ScoutSuite/providers/aws/resources/account/contacts.py diff --git a/ScoutSuite/providers/aws/facade/account.py b/ScoutSuite/providers/aws/facade/account.py new file mode 100644 index 000000000..b49341350 --- /dev/null +++ b/ScoutSuite/providers/aws/facade/account.py @@ -0,0 +1,25 @@ +from ScoutSuite.core.console import print_exception +from ScoutSuite.providers.aws.facade.basefacade import AWSBaseFacade +from ScoutSuite.providers.aws.facade.utils import AWSFacadeUtils +from ScoutSuite.providers.utils import run_concurrently + + +class AccountFacade(AWSBaseFacade): + async def get_contact_information(self): + client = AWSFacadeUtils.get_client('account', self.session) + try: + contact_info = await run_concurrently(lambda: client.get_contact_information()) + return contact_info + except Exception as e: + print_exception(f'Failed to retrieve account contact details: {e}') + + async def get_alternate_contact(self, contact_type): + client = AWSFacadeUtils.get_client('account', self.session) + try: + contact_info = await run_concurrently(lambda: client.get_alternate_contact(AlternateContactType=contact_type)) + return contact_info + except client.exceptions.ResourceNotFoundException as e: + # AWS throws if there is no contact of the requested type + return {'AlternateContact': None} + except Exception as e: + print_exception(f'Failed to retrieve account alternate contact details: {e}') diff --git a/ScoutSuite/providers/aws/facade/base.py b/ScoutSuite/providers/aws/facade/base.py index bbe8ee4a1..c3dc5e6e5 100755 --- a/ScoutSuite/providers/aws/facade/base.py +++ b/ScoutSuite/providers/aws/facade/base.py @@ -1,5 +1,6 @@ from boto3.session import Session +from ScoutSuite.providers.aws.facade.account import AccountFacade from ScoutSuite.providers.aws.facade.acm import AcmFacade from ScoutSuite.providers.aws.facade.awslambda import LambdaFacade from ScoutSuite.providers.aws.facade.basefacade import AWSBaseFacade @@ -247,6 +248,7 @@ async def build_region_list(self, service: str, chosen_regions=None, excluded_re def _instantiate_facades(self): self.ec2 = EC2Facade(self.session, self.owner_id) + self.account = AccountFacade(self.session) self.acm = AcmFacade(self.session) self.awslambda = LambdaFacade(self.session) self.cloudformation = CloudFormation(self.session) diff --git a/ScoutSuite/providers/aws/metadata.json b/ScoutSuite/providers/aws/metadata.json index 804dbb9ed..a3b9ab952 100755 --- a/ScoutSuite/providers/aws/metadata.json +++ b/ScoutSuite/providers/aws/metadata.json @@ -14,6 +14,14 @@ } }, "management": { + "account": { + "resources": { + "contacts": { + "cols": 2, + "path": "services.account.contacts" + } + } + }, "cloudformation": { "resources": { "stacks": { diff --git a/ScoutSuite/providers/aws/resources/account/__init__.py b/ScoutSuite/providers/aws/resources/account/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ScoutSuite/providers/aws/resources/account/base.py b/ScoutSuite/providers/aws/resources/account/base.py new file mode 100644 index 000000000..d6561a733 --- /dev/null +++ b/ScoutSuite/providers/aws/resources/account/base.py @@ -0,0 +1,15 @@ +from ScoutSuite.providers.aws.facade.base import AWSFacade +from ScoutSuite.providers.aws.resources.base import AWSCompositeResources +from .contacts import Contacts + +class Account(AWSCompositeResources): + _children = [ + (Contacts, 'contacts') + ] + + def __init__(self, facade: AWSFacade): + super().__init__(facade) + self.service = 'account' + + async def fetch_all(self, partition_name='aws', **kwargs): + await self._fetch_children(self) diff --git a/ScoutSuite/providers/aws/resources/account/contacts.py b/ScoutSuite/providers/aws/resources/account/contacts.py new file mode 100644 index 000000000..d55136c50 --- /dev/null +++ b/ScoutSuite/providers/aws/resources/account/contacts.py @@ -0,0 +1,18 @@ +from ScoutSuite.providers.aws.facade.base import AWSFacade +from ScoutSuite.providers.aws.resources.base import AWSResources + + +class Contacts(AWSResources): + def __init__(self, facade: AWSFacade): + super().__init__(facade) + self.partition = facade.partition + self.service = 'account' + self.resource_type = 'contact' + + async def fetch_all(self): + # These settings are associated directly with the service, not with any resource. + # However, ScoutSuite seems to assume that every setting is tied to a resource so we make + # up a fake resource to hold them. + self[0] = {} + self[0]['contact_information'] = (await self.facade.account.get_contact_information())['ContactInformation'] + self[0]['security_contact'] = (await self.facade.account.get_alternate_contact(contact_type="SECURITY"))['AlternateContact'] diff --git a/ScoutSuite/providers/aws/services.py b/ScoutSuite/providers/aws/services.py index 4b9656b28..0587b37c2 100755 --- a/ScoutSuite/providers/aws/services.py +++ b/ScoutSuite/providers/aws/services.py @@ -1,4 +1,5 @@ from ScoutSuite.providers.aws.facade.base import AWSFacade +from ScoutSuite.providers.aws.resources.account.base import Account from ScoutSuite.providers.aws.resources.acm.base import Certificates from ScoutSuite.providers.aws.resources.awslambda.base import Lambdas from ScoutSuite.providers.aws.resources.cloudformation.base import CloudFormation @@ -63,6 +64,7 @@ class AWSServicesConfig(BaseServicesConfig): """ Object that holds the necessary AWS configuration for all services in scope. + :ivar account Account configuration :ivar cloudtrail: CloudTrail configuration :ivar cloudwatch: CloudWatch configuration: :ivar cloudfront: CloudFront configuration @@ -89,6 +91,7 @@ def __init__(self, credentials=None, **kwargs): facade = AWSFacade(credentials) + self.account = Account(facade) self.acm = Certificates(facade) self.awslambda = Lambdas(facade) self.cloudformation = CloudFormation(facade) From eb55a2cfd2db609b4e6e08742bd6fa033ee4e174 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Wed, 17 Jul 2024 15:48:31 -0700 Subject: [PATCH 11/33] Rules and partial for AWS account contacts. --- ScoutSuite/core/conditions.py | 6 ++- .../aws/services.account.contacts.html | 31 +++++++++++ .../account-contact-details-missing.json | 51 +++++++++++++++++++ ...ount-security-contact-details-missing.json | 41 +++++++++++++++ .../aws/rules/rulesets/ada-cp-1.0.json | 12 +++++ .../aws/rules/rulesets/detailed.json | 12 +++++ 6 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 ScoutSuite/output/data/html/partials/aws/services.account.contacts.html create mode 100644 ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json create mode 100644 ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json diff --git a/ScoutSuite/core/conditions.py b/ScoutSuite/core/conditions.py index d0f140beb..1a9569806 100755 --- a/ScoutSuite/core/conditions.py +++ b/ScoutSuite/core/conditions.py @@ -91,7 +91,7 @@ def pass_condition(b, test, a): # Empty tests elif test == 'empty': - result = ((type(b) == dict and b == {}) or (type(b) == list and b == []) or (type(b) == list and b == [None])) + result = ((type(b) == dict and b == {}) or (type(b) == list and b == []) or (type(b) == list and b == [None]) or (type(b) == str and len(b.strip()) == 0)) elif test == 'notEmpty': result = (not pass_condition(b, 'empty', 'a')) elif test == 'null': @@ -122,6 +122,10 @@ def pass_condition(b, test, a): result = a.lower() in map(str.lower, b) elif test == 'withoutKeyCaseInsensitive': result = a.lower() not in map(str.lower, b) + elif test == 'withValue': + result = ((a in b) and (not pass_condition(b[a], 'null', '')) and (not pass_condition(b[a], 'empty', ''))) + elif test == 'withoutValue': + result = (not pass_condition(b, 'withValue', a)) # String test elif test == 'containString': diff --git a/ScoutSuite/output/data/html/partials/aws/services.account.contacts.html b/ScoutSuite/output/data/html/partials/aws/services.account.contacts.html new file mode 100644 index 000000000..1ae42b265 --- /dev/null +++ b/ScoutSuite/output/data/html/partials/aws/services.account.contacts.html @@ -0,0 +1,31 @@ + + + + diff --git a/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json b/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json new file mode 100644 index 000000000..b12d21d52 --- /dev/null +++ b/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json @@ -0,0 +1,51 @@ +{ + "description": "Account Contact Information Missing Key Details", + "rationale": "Account contact information is used by AWS when security or billing problems are detected.", + "remediation": "Add appropriate contact details and ensure that they are kept up to date", + "compliance": [ + { + "name": "CIS Amazon Web Services Foundations", + "version": "2.0.0", + "reference": "1.1" + }, + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.3.1" + } + ], + "references": [ + "https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html" + ], + "dashboard_name": "Contacts", + "path": "account.contacts.id", + "conditions": [ + "or", + [ + "contact_information", + "withoutValue", + "FullName" + ], + [ + "contact_information", + "withoutValue", + "AddressLine1" + ], + [ + "contact_information", + "withoutValue", + "City" + ], + [ + "contact_information", + "withoutValue", + "CountryCode" + ], + [ + "contact_information", + "withoutValue", + "PhoneNumber" + ] + ], + "class_suffix": "contact_information" +} \ No newline at end of file diff --git a/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json b/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json new file mode 100644 index 000000000..52bbf051b --- /dev/null +++ b/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json @@ -0,0 +1,41 @@ +{ + "description": "Account Security Contact Information Missing Key Details", + "rationale": "Account security contact information is used by AWS when security problems are detected.", + "remediation": "Add appropriate security contact details and ensure that they are kept up to date", + "compliance": [ + { + "name": "CIS Amazon Web Services Foundations", + "version": "2.0.0", + "reference": "1.2" + }, + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.3.2" + } + ], + "references": [ + "https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html" + ], + "dashboard_name": "Contacts", + "path": "account.contacts.id", + "conditions": [ + "or", + [ + "security_contact", + "null", + "" + ], + [ + "security_contact", + "withoutValue", + "Name" + ], + [ + "security_contact", + "withoutValue", + "EmailAddress" + ] + ], + "class_suffix": "security_contact" +} \ No newline at end of file diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json index 9dd2e7451..7a5a961f1 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -12,6 +12,18 @@ "enabled": true, "level": "danger" } + ], + "account-contact-details-missing.json": [ + { + "enabled": true, + "level": "danger" + } + ], + "account-security-contact-details-missing.json": [ + { + "enabled": true, + "level": "danger" + } ] } } \ No newline at end of file diff --git a/ScoutSuite/providers/aws/rules/rulesets/detailed.json b/ScoutSuite/providers/aws/rules/rulesets/detailed.json index 0d4cce139..e4111d30e 100755 --- a/ScoutSuite/providers/aws/rules/rulesets/detailed.json +++ b/ScoutSuite/providers/aws/rules/rulesets/detailed.json @@ -1,6 +1,18 @@ { "about": "This ruleset consists of numerous rules that are considered standard by NCC Group. The rules enabled range from violations of well-known security best practices to gaps resulting from less-known security implications of provider-specific mechanisms. Additional rules exist, some of them requiring extra-parameters to be configured, and some of them being applicable to a limited number of users.", "rules": { + "account-contact-details-missing.json": [ + { + "enabled": true, + "level": "warning" + } + ], + "account-security-contact-details-missing.json": [ + { + "enabled": true, + "level": "warning" + } + ], "acm-certificate-with-close-expiration-date.json": [ { "args": [ From 56e9f5a4ac1fa6dc68d590e818808ed7e07a4261 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 22 Jul 2024 00:05:22 -0700 Subject: [PATCH 12/33] Rule for ADA-CP 0.7 2.7.2 "Do not setup access keys during initial user setup for all IAM users that have a console password" This implementation is based off the description and Console investigation procedure. The CLI investigation procedure and verification describe a very different check. https://github.com/appdefensealliance/ASA-WG/issues/69 tracks the discrepancy in the ADA-CP spec. --- ScoutSuite/core/conditions.py | 6 + .../aws/services.iam.credential_reports.html | 4 +- ...ser-password-key-assigned-at-creation.json | 60 ++++++ .../aws/rules/rulesets/ada-cp-1.0.json | 6 + ...ser-password-key-assigned-at-creation.json | 184 ++++++++++++++++++ ...ser-password-key-assigned-at-creation.json | 4 + tests/data/ruleset-test.json | 6 + tests/test_rules_processingengine.py | 2 +- 8 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json create mode 100644 tests/data/rule-configs/iam-user-password-key-assigned-at-creation.json create mode 100644 tests/data/rule-results/iam-user-password-key-assigned-at-creation.json diff --git a/ScoutSuite/core/conditions.py b/ScoutSuite/core/conditions.py index 1a9569806..ee9736442 100755 --- a/ScoutSuite/core/conditions.py +++ b/ScoutSuite/core/conditions.py @@ -1,5 +1,6 @@ import datetime import dateutil.parser +import dateutil.utils import json import netaddr import re @@ -216,6 +217,11 @@ def pass_condition(b, test, a): elif test == 'newerThan': age, threshold = __prepare_age_test(a, b) result = (age < threshold) + elif test == 'equalDate': + result = dateutil.utils.within_delta( + dateutil.parser.parse(str(b)).replace(tzinfo=None), + dateutil.parser.parse(str(a)).replace(tzinfo=None), + datetime.timedelta(minutes=10)) # CIDR tests elif test == 'inSubnets': diff --git a/ScoutSuite/output/data/html/partials/aws/services.iam.credential_reports.html b/ScoutSuite/output/data/html/partials/aws/services.iam.credential_reports.html index 0d49c3bce..57b187ffc 100755 --- a/ScoutSuite/output/data/html/partials/aws/services.iam.credential_reports.html +++ b/ScoutSuite/output/data/html/partials/aws/services.iam.credential_reports.html @@ -15,10 +15,10 @@

Credentials Report

Hardware MFA Active: {{getValueAt 'services' 'iam' 'credential_reports' @key 'mfa_active_hardware'}}
Access Key 1 Active: {{getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_1_active'}}
Access Key 1 Last Used: {{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_1_last_used_date')}}
-
Access Key 1 Last Rotated: {{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_1_last_rotated')}}
+
Access Key 1 Last Rotated: {{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_1_last_rotated')}}
Access Key 2 Active: {{getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_2_active'}}
Access Key 2 Last Used: {{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_2_last_used_date')}}
-
Access Key 2 Last Rotated: {{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_2_last_rotated')}}
+
Access Key 2 Last Rotated: {{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_2_last_rotated')}}
Signing Cert 1 Active: {{getValueAt 'services' 'iam' 'credential_reports' @key 'cert_1_active'}}
Signing Cert 2 Active: {{getValueAt 'services' 'iam' 'credential_reports' @key 'cert_2_active'}}
diff --git a/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json b/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json new file mode 100644 index 000000000..c7c9633f1 --- /dev/null +++ b/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json @@ -0,0 +1,60 @@ +{ + "description": "IAM Access Keys Provisioned At Creation For Users With Passwords", + "rationale": "IAM Users with Console passwords should not automatically be provisioned with access keys.", + "remediation": "IAM Users with Console access should be required to create their own API credentials or to submit support requests to account admins to obtain API credentials.", + "compliance": [ + { + "name": "CIS Amazon Web Services Foundations", + "version": "2.0.0", + "reference": "1.11" + }, + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.7.2" + } + ], + "references": [ + "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html" + ], + "dashboard_name": "Users", + "path": "iam.credential_reports.id", + "conditions": [ + "and", + [ + "iam.credential_reports.id.password_enabled", + "true", + "" + ], + [ + "or", + [ + "and", + [ + "iam.credential_reports.id.access_key_1_last_rotated", + "notNull", + "" + ], + [ + "iam.credential_reports.id.user_creation_time", + "equalDate", + "_GET_VALUE_AT_(iam.credential_reports.id.access_key_1_last_rotated)" + ] + ], + [ + "and", + [ + "iam.credential_reports.id.access_key_2_last_rotated", + "notNull", + "" + ], + [ + "iam.credential_reports.id.user_creation_time", + "equalDate", + "_GET_VALUE_AT_(iam.credential_reports.id.access_key_2_last_rotated)" + ] + ] + ] + ], + "id_suffix": "access_key_assigned_at_creation" +} diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json index 7a5a961f1..131200297 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -24,6 +24,12 @@ "enabled": true, "level": "danger" } + ], + "iam-user-password-key-assigned-at-creation.json": [ + { + "enabled": true, + "level": "danger" + } ] } } \ No newline at end of file diff --git a/tests/data/rule-configs/iam-user-password-key-assigned-at-creation.json b/tests/data/rule-configs/iam-user-password-key-assigned-at-creation.json new file mode 100644 index 000000000..341038e17 --- /dev/null +++ b/tests/data/rule-configs/iam-user-password-key-assigned-at-creation.json @@ -0,0 +1,184 @@ +{ + "account_id": "123456789012", + "services": { + "iam": { + "credential_reports": { + "scoutid-1d5a2b269989f95f33bbf2dc5b2bb9acd74fe980": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/ConsoleOnly", + "cert_1_active": "false", + "cert_1_last_rotated": "N/A", + "cert_2_active": "false", + "cert_2_last_rotated": "N/A", + "id": "scoutid-1d5a2b269989f95f33bbf2dc5b2bb9acd74fe980", + "last_used": null, + "mfa_active": "false", + "mfa_active_hardware": false, + "name": "ConsoleOnly", + "partition": "aws", + "password_enabled": "true", + "password_last_changed": "2024-07-19T23:47:43Z", + "password_last_used": null, + "password_next_rotation": "N/A", + "user": "ConsoleOnly", + "user_creation_time": "2024-07-19T23:46:54Z" + }, + "scoutid-3a9335a009692b3acc6a2a25de820ca90b51e558": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2024-07-19T23:50:34Z", + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/CliOnly", + "cert_1_active": "false", + "cert_1_last_rotated": "N/A", + "cert_2_active": "false", + "cert_2_last_rotated": "N/A", + "id": "scoutid-3a9335a009692b3acc6a2a25de820ca90b51e558", + "last_used": null, + "mfa_active": "false", + "mfa_active_hardware": false, + "name": "CliOnly", + "partition": "aws", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null, + "password_next_rotation": "N/A", + "user": "CliOnly", + "user_creation_time": "2024-07-19T23:50:17Z" + }, + "scoutid-4c4674bc5c6933438664315b59abacfa40b4cfdc": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2024-07-20T00:53:59Z", + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/ConsoleCliManual", + "cert_1_active": "false", + "cert_1_last_rotated": "N/A", + "cert_2_active": "false", + "cert_2_last_rotated": "N/A", + "id": "scoutid-4c4674bc5c6933438664315b59abacfa40b4cfdc", + "last_used": null, + "mfa_active": "false", + "mfa_active_hardware": false, + "name": "ConsoleCliManual", + "partition": "aws", + "password_enabled": "true", + "password_last_changed": "2024-07-19T23:52:09Z", + "password_last_used": null, + "password_next_rotation": "N/A", + "user": "ConsoleCliManual", + "user_creation_time": "2024-07-19T23:52:08Z" + }, + "scoutid-5471d55e4fc8115ccaa5d60c9a525362057ae4c0": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2024-07-19T23:51:33Z", + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/ConsoleCli", + "cert_1_active": "false", + "cert_1_last_rotated": "N/A", + "cert_2_active": "false", + "cert_2_last_rotated": "N/A", + "id": "scoutid-5471d55e4fc8115ccaa5d60c9a525362057ae4c0", + "last_used": null, + "mfa_active": "false", + "mfa_active_hardware": false, + "name": "ConsoleCli", + "partition": "aws", + "password_enabled": "true", + "password_last_changed": "2024-07-19T23:51:06Z", + "password_last_used": null, + "password_next_rotation": "N/A", + "user": "ConsoleCli", + "user_creation_time": "2024-07-19T23:51:06Z" + }, + "scoutid-68dcc047c3da5bbbc3f3e9d54000b7357f0e507e": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:root", + "cert_1_active": "false", + "cert_1_last_rotated": "N/A", + "cert_2_active": "false", + "cert_2_last_rotated": "N/A", + "id": "scoutid-68dcc047c3da5bbbc3f3e9d54000b7357f0e507e", + "last_used": null, + "mfa_active": "false", + "mfa_active_hardware": false, + "name": "", + "partition": "aws", + "password_enabled": "not_supported", + "password_last_changed": "not_supported", + "password_last_used": null, + "password_next_rotation": "not_supported", + "user": "", + "user_creation_time": "2019-03-15T21:43:30Z" + }, + "scoutid-a11b1609e80e3da57b9fa0aa395d9340821d3e99": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/ConsoleOnlyInit", + "cert_1_active": "false", + "cert_1_last_rotated": "N/A", + "cert_2_active": "false", + "cert_2_last_rotated": "N/A", + "id": "scoutid-a11b1609e80e3da57b9fa0aa395d9340821d3e99", + "last_used": null, + "mfa_active": "false", + "mfa_active_hardware": false, + "name": "ConsoleOnlyInit", + "partition": "aws", + "password_enabled": "true", + "password_last_changed": "2024-07-19T23:49:58Z", + "password_last_used": null, + "password_next_rotation": "N/A", + "user": "ConsoleOnlyInit", + "user_creation_time": "2024-07-19T23:49:58Z" + } + }, + "credential_reports_count": 10 + } + } +} diff --git a/tests/data/rule-results/iam-user-password-key-assigned-at-creation.json b/tests/data/rule-results/iam-user-password-key-assigned-at-creation.json new file mode 100644 index 000000000..f2681c4bc --- /dev/null +++ b/tests/data/rule-results/iam-user-password-key-assigned-at-creation.json @@ -0,0 +1,4 @@ +[ + "iam.credential_reports.scoutid-5471d55e4fc8115ccaa5d60c9a525362057ae4c0.access_key_assigned_at_creation" +] + diff --git a/tests/data/ruleset-test.json b/tests/data/ruleset-test.json index 6b440e1df..2d8eab242 100755 --- a/tests/data/ruleset-test.json +++ b/tests/data/ruleset-test.json @@ -17,6 +17,12 @@ "enabled": true, "level": "danger" } + ], + "iam-user-password-key-assigned-at-creation.json": [ + { + "enabled": true, + "level": "danger" + } ] }, "about": "extra test rules" diff --git a/tests/test_rules_processingengine.py b/tests/test_rules_processingengine.py index 6112f598f..c944675f4 100755 --- a/tests/test_rules_processingengine.py +++ b/tests/test_rules_processingengine.py @@ -37,7 +37,7 @@ def test_all_finding_rules(self): self.rule_counters['found'] += 1 rule = ruleset['rules'][rule_file_name][0] rule['enabled'] = True - print(rule_file_name) + #print(rule_file_name) self._test_rule(rule_file_name, rule) print('Existing rules: %d' % self.rule_counters['found']) From a3ecaa1e0ded4177ad35e4bf514c79ce28d92d6d Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 22 Jul 2024 00:08:35 -0700 Subject: [PATCH 13/33] Added the esisting rule "Root Account Has Active Keys" to ADA-CP ruleset. --- .../findings/iam-root-account-with-active-keys.json | 10 ++++++++++ .../providers/aws/rules/rulesets/ada-cp-1.0.json | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json index 51d533d66..2884554bc 100755 --- a/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json @@ -17,6 +17,16 @@ "name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "1.12" + }, + { + "name": "CIS Amazon Web Services Foundations", + "version": "2.0.0", + "reference": "1.4" + }, + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.7.1" } ], "references": [ diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json index 131200297..b05a1e1e2 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -25,6 +25,12 @@ "level": "danger" } ], + "iam-root-account-with-active-keys.json": [ + { + "enabled": true, + "level": "danger" + } + ], "iam-user-password-key-assigned-at-creation.json": [ { "enabled": true, From 9ed3794a7b20b06318fda1c6aa293ba8d2f96048 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Wed, 24 Jul 2024 17:48:30 -0700 Subject: [PATCH 14/33] Added a descriptive comment to "IAM Access Keys Provisioned At Creation For Users With Passwords" --- .../findings/iam-user-password-key-assigned-at-creation.json | 1 + 1 file changed, 1 insertion(+) diff --git a/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json b/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json index c7c9633f1..295862216 100644 --- a/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json @@ -1,4 +1,5 @@ { + "comment": "The description and Console investigation procedure say to look for Users with Console passwords that also have API keys created at User creation. The CLI investigation procedure and verification say to look for Users with Console passwords and API keys that have never been used. There are also other issues with this requirement. See https://github.com/appdefensealliance/ASA-WG/issues/69 and https://github.com/appdefensealliance/ASA-WG/issues/72. Changes to this rule will likely be required once those issues are resolved.", "description": "IAM Access Keys Provisioned At Creation For Users With Passwords", "rationale": "IAM Users with Console passwords should not automatically be provisioned with access keys.", "remediation": "IAM Users with Console access should be required to create their own API credentials or to submit support requests to account admins to obtain API credentials.", From e07a09346a15c42fd68cbdf62748199f41db6a4e Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Wed, 18 Dec 2024 17:52:43 -0800 Subject: [PATCH 15/33] Added links to the ADA-CP spec for all rules currently in the ADA-CP ruleset. --- .../aws/rules/findings/account-contact-details-missing.json | 1 + .../findings/account-security-contact-details-missing.json | 1 + .../aws/rules/findings/awslambda-runtime-deprecated.json | 1 + .../providers/aws/rules/findings/iam-no-support-role.json | 3 +++ .../aws/rules/findings/iam-root-account-with-active-keys.json | 1 + .../findings/iam-user-password-key-assigned-at-creation.json | 3 ++- 6 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json b/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json index b12d21d52..888aeee50 100644 --- a/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json +++ b/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json @@ -15,6 +15,7 @@ } ], "references": [ + "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#231-maintain-current-contact-details", "https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html" ], "dashboard_name": "Contacts", diff --git a/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json b/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json index 52bbf051b..b5e15ff61 100644 --- a/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json +++ b/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json @@ -15,6 +15,7 @@ } ], "references": [ + "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#232-ensure-security-contact-information-is-registered", "https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html" ], "dashboard_name": "Contacts", diff --git a/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json b/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json index 5fde5eb97..ef8589dc3 100644 --- a/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json +++ b/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json @@ -10,6 +10,7 @@ } ], "references": [ + "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#121-ensure-that-all-aws-lambda-functions-are-configured-to-use-a-current-not-deprecated-runtime", "https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html" ], "dashboard_name": "Functions", diff --git a/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json b/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json index 1bfe62cb2..d0dc5eb8c 100644 --- a/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json @@ -24,6 +24,9 @@ "reference": "2.2.1" } ], + "references": [ + "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#221-ensure-a-support-role-has-been-created-to-manage-incidents-with-aws-support" + ], "dashboard_name": "Policies", "display_path": "iam.policies.id", "path": "iam.policies.id", diff --git a/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json index 2884554bc..9ea98fb31 100755 --- a/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json @@ -31,6 +31,7 @@ ], "references": [ "https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html#securityhub-standards-cis-controls-1.1", + "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#271-ensure-no-root-user-account-access-key-exists", "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-use", "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_review-access", "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_document-processes" diff --git a/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json b/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json index 295862216..370ed1cba 100644 --- a/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json @@ -1,5 +1,5 @@ { - "comment": "The description and Console investigation procedure say to look for Users with Console passwords that also have API keys created at User creation. The CLI investigation procedure and verification say to look for Users with Console passwords and API keys that have never been used. There are also other issues with this requirement. See https://github.com/appdefensealliance/ASA-WG/issues/69 and https://github.com/appdefensealliance/ASA-WG/issues/72. Changes to this rule will likely be required once those issues are resolved.", + "comment": "The description and Console investigation procedure say to look for Users with Console passwords that also have API keys created at User creation. The CLI investigation procedure and verification say to look for Users with Console passwords and API keys that have never been used. There are also other issues with this requirement. See https://github.com/appdefensealliance/ASA-WG/issues/69 and https://github.com/appdefensealliance/ASA-WG/issues/107. Changes to this rule will likely be required once those issues are resolved.", "description": "IAM Access Keys Provisioned At Creation For Users With Passwords", "rationale": "IAM Users with Console passwords should not automatically be provisioned with access keys.", "remediation": "IAM Users with Console access should be required to create their own API credentials or to submit support requests to account admins to obtain API credentials.", @@ -16,6 +16,7 @@ } ], "references": [ + "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#272-do-not-setup-access-keys-during-initial-user-setup-for-all-iam-users-that-have-a-console-password", "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html" ], "dashboard_name": "Users", From b4b7d9afeabc339a65fb390a80f843abe281670d Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Wed, 18 Dec 2024 17:57:06 -0800 Subject: [PATCH 16/33] Rule for ADA-CP 2.7.3 "Ensure IAM policies that allow full "*:*" administrative privileges are not attached" The rule is intended to force operators to grant admin permission only using the built-in "AdministratorAccess" permission policy, but the spec currently ignores the possibility of an in-line permission policy granting admin. https://github.com/appdefensealliance/ASA-WG/issues/17 tracks the issue. --- .../findings/iam-admin-policy-attached.json | 64 +++++++++++++++++++ .../aws/rules/rulesets/ada-cp-1.0.json | 6 ++ 2 files changed, 70 insertions(+) create mode 100644 ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json diff --git a/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json b/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json new file mode 100644 index 000000000..fad690686 --- /dev/null +++ b/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json @@ -0,0 +1,64 @@ +{ + "comment":"This rule is very similar to iam-managed-policy-allows-full-privileges.json. It only checks for Managed permission policies that grant unconditional admin; it does *not* check for in-line permission policies that grant unconditional admin. See https://github.com/appdefensealliance/ASA-WG/issues/17. Changes to this rule may be required once that issue is resolved.", + "description": "Attached Managed Permission Policy Allows Full Administrative Privileges", + "rationale": "Only the built-in \"AdministratorAccess\" permission policy should be used to grant full administrative privileges.", + "remediation": "Ensure no Managed policies are configured with Effect: Allow, Action: * and Resource: *", + "compliance": [ + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.7.3" + } + ], + "references": [ + "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#273-ensure-iam-policies-that-allow-full--administrative-privileges-are-not-attached", + "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html", + "https://aws.amazon.com/blogs/security/back-to-school-understanding-the-iam-policy-grammar/" + ], + "dashboard_name": "Statements", + "display_path": "iam.policies.id", + "path": "iam.policies.id.PolicyDocument.Statement.id", + "conditions": [ + "and", + [ + "iam.policies.id.arn", + "notMatch", + "^arn:aws:iam::aws:policy/.*$" + ], + [ + "iam.policies.id.attached_to", + "notEmpty", + "" + ], + [ + "iam.policies.id.PolicyDocument.Statement.id.Effect", + "equal", + "Allow" + ], + [ + "iam.policies.id.PolicyDocument.Statement.id.", + "withKey", + "Action" + ], + [ + "iam.policies.id.PolicyDocument.Statement.id.Action", + "containAtLeastOneOf", + [ + "*", + "*:*" + ] + ], + [ + "iam.policies.id.PolicyDocument.Statement.id.", + "withKey", + "Resource" + ], + [ + "iam.policies.id.PolicyDocument.Statement.id.Resource", + "containAtLeastOneOf", + [ + "*" + ] + ] + ] +} \ No newline at end of file diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json index b05a1e1e2..8e2fb5915 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -36,6 +36,12 @@ "enabled": true, "level": "danger" } + ], + "iam-admin-policy-attached.json": [ + { + "enabled": true, + "level": "danger" + } ] } } \ No newline at end of file From 26b95fa3f25f6fe68d77fb4b27403589880aebea Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Thu, 19 Dec 2024 11:47:28 -0800 Subject: [PATCH 17/33] Rule for ADA-CP 2.7.1 "Ensure no 'root' user account access key exists" I had been using the existing rule "iam-root-account-with-active-keys" for this requirement, but then noticed that the title and AWS CLI investigation procedure for this ADA-CP requirement also prohibit inactive root access keys; our existing rule only prohibits *active* root access keys. The AWS Console investigation procedure in the ADA-CP requirement also allows inactive root access keys. The issue is tracked at https://github.com/appdefensealliance/ASA-WG/issues/138. Assuming that they resolve it in favour of the title, I added this new rule. If they go the other way, we can remove this rule. --- .../iam-root-account-with-access-keys.json | 42 +++++++++++++++++++ .../iam-root-account-with-active-keys.json | 6 --- .../aws/rules/rulesets/ada-cp-1.0.json | 2 +- 3 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 ScoutSuite/providers/aws/rules/findings/iam-root-account-with-access-keys.json diff --git a/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-access-keys.json b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-access-keys.json new file mode 100644 index 000000000..937d33d0d --- /dev/null +++ b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-access-keys.json @@ -0,0 +1,42 @@ +{ + "comment": "This rule is very similar to 'iam-root-account-with-active-keys.json', the difference being that this rule prohibits *any* root access keys, including inactive ones. ADA-CP 2.7.1 is not clear whether inactive keys are allowed: the title and CLI investigation procedure do not allow them but the Console investigation procedure does. The flaw is tracked by https://github.com/appdefensealliance/ASA-WG/issues/138. If ADA chooses to permit inactive keys, then we can drop this rule and switch to 'iam-root-account-with-active-keys.json'.", + "description": "Root Account Has Access Keys", + "rationale": "AWS root account access keys should be deleted as they provide unrestricted access to the AWS Account.", + "remediation": "Delete or disable root account access keys", + "compliance": [ + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.7.1" + } + ], + "references": [ + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#271-ensure-no-root-user-account-access-key-exists", + "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-use", + "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_review-access", + "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_document-processes" + ], + "dashboard_name": "Root account", + "path": "iam.credential_reports.id", + "conditions": [ + "and", + [ + "iam.credential_reports.id.name", + "equal", + "" + ], + [ + "or", + [ + "iam.credential_reports.id.access_key_1_last_rotated", + "notEqual", + "N/A" + ], + [ + "iam.credential_reports.id.access_key_2_last_rotated", + "notEqual", + "N/A" + ] + ] + ] +} diff --git a/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json index 9ea98fb31..56cac39a4 100755 --- a/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json @@ -22,16 +22,10 @@ "name": "CIS Amazon Web Services Foundations", "version": "2.0.0", "reference": "1.4" - }, - { - "name": "ADA Cloud Profile", - "version": "1.0", - "reference": "2.7.1" } ], "references": [ "https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html#securityhub-standards-cis-controls-1.1", - "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#271-ensure-no-root-user-account-access-key-exists", "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-use", "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_review-access", "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_document-processes" diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json index 8e2fb5915..640d9bf26 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -25,7 +25,7 @@ "level": "danger" } ], - "iam-root-account-with-active-keys.json": [ + "iam-root-account-with-access-keys.json": [ { "enabled": true, "level": "danger" From 13c63300037c88e7b5f7a58eb31de91eff49b799 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Thu, 19 Dec 2024 11:52:41 -0800 Subject: [PATCH 18/33] Updated ADA-CP links to the v1.0 branch from the dev branch. --- .../aws/rules/findings/account-contact-details-missing.json | 2 +- .../findings/account-security-contact-details-missing.json | 2 +- .../aws/rules/findings/awslambda-runtime-deprecated.json | 2 +- .../providers/aws/rules/findings/iam-admin-policy-attached.json | 2 +- .../providers/aws/rules/findings/iam-no-support-role.json | 2 +- .../findings/iam-user-password-key-assigned-at-creation.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json b/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json index 888aeee50..1c814fc2a 100644 --- a/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json +++ b/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json @@ -15,7 +15,7 @@ } ], "references": [ - "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#231-maintain-current-contact-details", + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#231-maintain-current-contact-details", "https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html" ], "dashboard_name": "Contacts", diff --git a/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json b/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json index b5e15ff61..017e8e59d 100644 --- a/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json +++ b/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json @@ -15,7 +15,7 @@ } ], "references": [ - "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#232-ensure-security-contact-information-is-registered", + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#232-ensure-security-contact-information-is-registered", "https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html" ], "dashboard_name": "Contacts", diff --git a/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json b/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json index ef8589dc3..7e9088e8e 100644 --- a/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json +++ b/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json @@ -10,7 +10,7 @@ } ], "references": [ - "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#121-ensure-that-all-aws-lambda-functions-are-configured-to-use-a-current-not-deprecated-runtime", + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#121-ensure-that-all-aws-lambda-functions-are-configured-to-use-a-current-not-deprecated-runtime", "https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html" ], "dashboard_name": "Functions", diff --git a/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json b/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json index fad690686..b284513a5 100644 --- a/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json @@ -11,7 +11,7 @@ } ], "references": [ - "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#273-ensure-iam-policies-that-allow-full--administrative-privileges-are-not-attached", + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#273-ensure-iam-policies-that-allow-full--administrative-privileges-are-not-attached", "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html", "https://aws.amazon.com/blogs/security/back-to-school-understanding-the-iam-policy-grammar/" ], diff --git a/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json b/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json index d0dc5eb8c..2eccde6d3 100644 --- a/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json @@ -25,7 +25,7 @@ } ], "references": [ - "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#221-ensure-a-support-role-has-been-created-to-manage-incidents-with-aws-support" + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#221-ensure-a-support-role-has-been-created-to-manage-incidents-with-aws-support" ], "dashboard_name": "Policies", "display_path": "iam.policies.id", diff --git a/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json b/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json index 370ed1cba..284763f6b 100644 --- a/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json @@ -16,7 +16,7 @@ } ], "references": [ - "https://github.com/appdefensealliance/ASA-WG/blob/main/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#272-do-not-setup-access-keys-during-initial-user-setup-for-all-iam-users-that-have-a-console-password", + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#272-do-not-setup-access-keys-during-initial-user-setup-for-all-iam-users-that-have-a-console-password", "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html" ], "dashboard_name": "Users", From 9a226aa522a6ed108ff495e97140e0f70eb2933b Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Thu, 19 Dec 2024 11:53:27 -0800 Subject: [PATCH 19/33] Rule for ADA-CP 2.8.2 "Ensure IAM password policy requires minimum length of 14 or greater" This is an unmodified existing rule; I only added compliance and reference details. --- .../findings/iam-password-policy-minimum-length.json | 6 ++++++ ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/ScoutSuite/providers/aws/rules/findings/iam-password-policy-minimum-length.json b/ScoutSuite/providers/aws/rules/findings/iam-password-policy-minimum-length.json index 06213d207..848b6b39f 100755 --- a/ScoutSuite/providers/aws/rules/findings/iam-password-policy-minimum-length.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-password-policy-minimum-length.json @@ -17,10 +17,16 @@ "name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "1.9" + }, + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.8.2" } ], "references": [ "https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html#securityhub-cis-controls-1.9", + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#282-ensure-iam-password-policy-requires-minimum-length-of-14-or-greater", "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_complex-password", "https://docs.aws.amazon.com/organizations/latest/userguide/best-practices_member-acct.html#best-practices_mbr-acct_complex-password" ], diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json index 640d9bf26..e274bd7ce 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -42,6 +42,15 @@ "enabled": true, "level": "danger" } + ], + "iam-password-policy-minimum-length.json": [ + { + "args": [ + "14" + ], + "enabled": true, + "level": "danger" + } ] } } \ No newline at end of file From 9307cc0a9a7924f431e70733f4575a2df2763325 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Thu, 19 Dec 2024 14:22:29 -0800 Subject: [PATCH 20/33] Rules for ADA-CP 2.8.3, 2.8.4. "Ensure there is only one active access key available for any single IAM user" and "Ensure access keys are rotated every 90 days or less". These are unmodified existing rules; I only added compliance and reference details. --- .../rules/findings/iam-user-no-key-rotation.json | 6 ++++++ .../iam-user-with-multiple-access-keys.json | 8 ++++++++ .../providers/aws/rules/rulesets/ada-cp-1.0.json | 16 ++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/ScoutSuite/providers/aws/rules/findings/iam-user-no-key-rotation.json b/ScoutSuite/providers/aws/rules/findings/iam-user-no-key-rotation.json index 984b7f317..ffd3af098 100755 --- a/ScoutSuite/providers/aws/rules/findings/iam-user-no-key-rotation.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-user-no-key-rotation.json @@ -17,9 +17,15 @@ "name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "1.4" + }, + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.8.4" } ], "references": [ + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#284-ensure-access-keys-are-rotated-every-90-days-or-less", "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#rotate-credentials" ], "dashboard_name": "Access keys", diff --git a/ScoutSuite/providers/aws/rules/findings/iam-user-with-multiple-access-keys.json b/ScoutSuite/providers/aws/rules/findings/iam-user-with-multiple-access-keys.json index 44a27fd1d..c640e93cf 100755 --- a/ScoutSuite/providers/aws/rules/findings/iam-user-with-multiple-access-keys.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-user-with-multiple-access-keys.json @@ -1,7 +1,15 @@ { "description": "User with Multiple API Keys", "rationale": "The user was configured to have more than one active API keys associated with the account. Redundant or unused API keys should be removed.", + "compliance": [ + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.8.3" + } + ], "references": [ + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#283-ensure-there-is-only-one-active-access-key-available-for-any-single-iam-user", "https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html" ], "dashboard_name": "Users", diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json index e274bd7ce..4bf8c4de9 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -51,6 +51,22 @@ "enabled": true, "level": "danger" } + ], + "iam-user-with-multiple-access-keys.json": [ + { + "enabled": true, + "level": "danger" + } + ], + "iam-user-no-key-rotation.json": [ + { + "args": [ + "Active", + "90" + ], + "enabled": true, + "level": "danger" + } ] } } \ No newline at end of file From b7d24caf26e2df8a1840f68c622e382922d639f2 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Thu, 19 Dec 2024 14:25:42 -0800 Subject: [PATCH 21/33] Rule for ADA-CP 2.9.1 "Ensure IAM password policy prevents password reuse" For this one, I modified the existing rule "iam-password-policy-reuse-enabled". Previously, that rule only checked that there the account was configured to prevent reuse of some number of previous passwords; I added a parameter to put a lower bound on the number of remembered passwords. This required me to modify the existing rulesets; a parameter of 1 is equivalent to the old check. I also modified the data model and rule display somewhat: ScoutSuite previously synthesized a boolean to indicate whether password reuse prevention was on in addition to storing the number of past passwords to remember. But AWS doesn't have that boolean; reuse prevention is disabled if there is no number of past passwords to remember. It only allows 1-24 passwords to be remembered, so remembering 0 is equivalent to having the feature disabled. So I changed the data model to store 0 if it's disabled and check based on that. This makes the rule logic and display simpler. While I was changing the data display, I found that output for the settings controlling whether users are allowed to change their own passwords was gated on password reuse prevention being enabled. There is no such dependency within AWS, so I removed that condition. --- .../aws/services.iam.password_policy.html | 9 +++------ .../aws/resources/iam/passwordpolicy.py | 7 +++---- .../iam-password-policy-reuse-enabled.json | 17 ++++++++++++----- .../aws/rules/rulesets/ada-cp-1.0.json | 9 +++++++++ .../providers/aws/rules/rulesets/cis-1.2.0.json | 3 +++ .../providers/aws/rules/rulesets/default.json | 3 +++ .../providers/aws/rules/rulesets/detailed.json | 3 +++ 7 files changed, 36 insertions(+), 15 deletions(-) diff --git a/ScoutSuite/output/data/html/summaries/aws/services.iam.password_policy.html b/ScoutSuite/output/data/html/summaries/aws/services.iam.password_policy.html index 349bee9fd..ebec404e4 100755 --- a/ScoutSuite/output/data/html/summaries/aws/services.iam.password_policy.html +++ b/ScoutSuite/output/data/html/summaries/aws/services.iam.password_policy.html @@ -20,12 +20,9 @@

Password policy

{{#if items.MaxPasswordAge}}
Password expiration period (in days): {{items.MaxPasswordAge}}
{{/if}} -
Prevent password reuse: {{items.PasswordReusePrevention}}
- {{#if items.PreviousPasswordPrevented}} -
Number of passwords to remember: {{items.PreviousPasswordPrevented}}
-
Allow users to change their own password: {{items.AllowUsersToChangePassword}}
-
Allow users to set a new password after their password has expired: {{items.HardExpiry}}
- {{/if}} +
Prevent reuse of previous passwords: {{items.PasswordReusePrevention}}
+
Allow users to change their own password: {{items.AllowUsersToChangePassword}}
+
Allow users to set a new password after their password has expired: {{items.HardExpiry}}
diff --git a/ScoutSuite/providers/aws/resources/iam/passwordpolicy.py b/ScoutSuite/providers/aws/resources/iam/passwordpolicy.py index 00b750ee1..087ecbfcf 100755 --- a/ScoutSuite/providers/aws/resources/iam/passwordpolicy.py +++ b/ScoutSuite/providers/aws/resources/iam/passwordpolicy.py @@ -15,15 +15,14 @@ def _parse_password_policy(self, raw_password_policy): 'RequireLowercaseCharacters': False, 'RequireNumbers': False, 'RequireSymbols': False, - 'PasswordReusePrevention': False, + 'PasswordReusePrevention': 0, 'ExpirePasswords': False } if 'PasswordReusePrevention' not in raw_password_policy: - raw_password_policy['PasswordReusePrevention'] = False + raw_password_policy['PasswordReusePrevention'] = 0 else: - raw_password_policy['PreviousPasswordPrevented'] = raw_password_policy['PasswordReusePrevention'] - raw_password_policy['PasswordReusePrevention'] = True + raw_password_policy['PasswordReusePrevention'] = raw_password_policy['PasswordReusePrevention'] # There is a bug in the API: ExpirePasswords always returns false if 'MaxPasswordAge' in raw_password_policy: raw_password_policy['ExpirePasswords'] = True diff --git a/ScoutSuite/providers/aws/rules/findings/iam-password-policy-reuse-enabled.json b/ScoutSuite/providers/aws/rules/findings/iam-password-policy-reuse-enabled.json index 025eb7362..bf43192a3 100755 --- a/ScoutSuite/providers/aws/rules/findings/iam-password-policy-reuse-enabled.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-password-policy-reuse-enabled.json @@ -1,7 +1,8 @@ { + "comment": "Technically, this rule doesn't do exactly what ADA-CP requires: it requires password history of *exactly* 24 rather than 24 or more as implemented here. However, the maximum value that AWS permits for password history is 24, so there is no difference in practice (unless AWS changes their limit).", "description": "Password Policy Allows the Reuse of Passwords", - "rationale": "The password policy allowed password reuse. As a result, password complexity requirements were not in line with security best practice.", - "remediation": "Ensure the password policy is configured to prevent password reuse", + "rationale": "The password policy does not prevent reuse of the previous _ARG_0_ passwords. As a result, password complexity requirements were not in line with security best practice.", + "remediation": "Ensure that the password policy is configured to prevent reuse of the previous _ARG_0_ or more passwords.", "compliance": [ { "name": "CIS Amazon Web Services Foundations", @@ -17,10 +18,16 @@ "name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "1.10" + }, + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.9.1" } ], "references": [ - "https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html#securityhub-cis-controls-1.10" + "https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html#securityhub-cis-controls-1.10", + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#291-ensure-iam-password-policy-prevents-password-reuse" ], "dashboard_name": "Password policy", "path": "iam.password_policy.PasswordReusePrevention", @@ -28,8 +35,8 @@ "or", [ "this", - "false", - "" + "lessThan", + "_ARG_0_" ] ] } diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json index 4bf8c4de9..6aedc058c 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -67,6 +67,15 @@ "enabled": true, "level": "danger" } + ], + "iam-password-policy-reuse-enabled.json": [ + { + "args": [ + "24" + ], + "enabled": true, + "level": "danger" + } ] } } \ No newline at end of file diff --git a/ScoutSuite/providers/aws/rules/rulesets/cis-1.2.0.json b/ScoutSuite/providers/aws/rules/rulesets/cis-1.2.0.json index b20eaec02..a4478479a 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/cis-1.2.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/cis-1.2.0.json @@ -162,6 +162,9 @@ "iam-password-policy-reuse-enabled.json": [ { "comment": "Recommendation 1.10", + "args": [ + "1" + ], "enabled": true, "level": "danger", "scored": true diff --git a/ScoutSuite/providers/aws/rules/rulesets/default.json b/ScoutSuite/providers/aws/rules/rulesets/default.json index 61dfeb5d7..bd55a25ca 100755 --- a/ScoutSuite/providers/aws/rules/rulesets/default.json +++ b/ScoutSuite/providers/aws/rules/rulesets/default.json @@ -683,6 +683,9 @@ ], "iam-password-policy-reuse-enabled.json": [ { + "args": [ + "1" + ], "enabled": true, "level": "danger" } diff --git a/ScoutSuite/providers/aws/rules/rulesets/detailed.json b/ScoutSuite/providers/aws/rules/rulesets/detailed.json index e4111d30e..72535fd8f 100755 --- a/ScoutSuite/providers/aws/rules/rulesets/detailed.json +++ b/ScoutSuite/providers/aws/rules/rulesets/detailed.json @@ -722,6 +722,9 @@ ], "iam-password-policy-reuse-enabled.json": [ { + "args": [ + "1" + ], "enabled": true, "level": "danger" } From 3b54713ae9e11a7eb680befa013df12c49bcb34a Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Thu, 19 Dec 2024 17:44:08 -0800 Subject: [PATCH 22/33] AWS IAM Account Summary Load the AWS IAM Account Summary, displayed in its own page. Re-implemented 'iam-root-account-with-access-keys' using data from the IAM account summary. --- .../aws/services.iam.account_summary.html | 25 +++++++++++++++++ ScoutSuite/providers/aws/facade/iam.py | 9 +++++++ ScoutSuite/providers/aws/metadata.json | 4 +++ .../aws/resources/iam/accountsummary.py | 27 +++++++++++++++++++ .../providers/aws/resources/iam/base.py | 7 +++-- .../iam-root-account-with-access-keys.json | 23 ++++------------ doc/aws-minimal-permission-policy.json | 1 + 7 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 ScoutSuite/output/data/html/summaries/aws/services.iam.account_summary.html create mode 100644 ScoutSuite/providers/aws/resources/iam/accountsummary.py diff --git a/ScoutSuite/output/data/html/summaries/aws/services.iam.account_summary.html b/ScoutSuite/output/data/html/summaries/aws/services.iam.account_summary.html new file mode 100644 index 000000000..9540dc6ed --- /dev/null +++ b/ScoutSuite/output/data/html/summaries/aws/services.iam.account_summary.html @@ -0,0 +1,25 @@ + + diff --git a/ScoutSuite/providers/aws/facade/iam.py b/ScoutSuite/providers/aws/facade/iam.py index 60cfc008e..6edce1d55 100755 --- a/ScoutSuite/providers/aws/facade/iam.py +++ b/ScoutSuite/providers/aws/facade/iam.py @@ -183,6 +183,15 @@ async def get_password_policy(self): print_exception(f'Failed to get account password policy: {e}') return None + async def get_account_summary(self): + client = AWSFacadeUtils.get_client('iam', self.session) + try: + return (await run_concurrently(client.get_account_summary))['SummaryMap'] + except ClientError as e: + if e.response['Error']['Code'] != 'NoSuchEntity': + print_exception(f'Failed to get account account summary: {e}') + return None + async def _get_and_set_user_access_keys(self, user: {}): client = AWSFacadeUtils.get_client('iam', self.session) try: diff --git a/ScoutSuite/providers/aws/metadata.json b/ScoutSuite/providers/aws/metadata.json index a3b9ab952..e62478bc0 100755 --- a/ScoutSuite/providers/aws/metadata.json +++ b/ScoutSuite/providers/aws/metadata.json @@ -399,6 +399,10 @@ "password_policy": { "cols": 1, "path": "services.iam.password_policy" + }, + "account_summary": { + "cols": 1, + "path": "services.iam.account_summary" } } }, diff --git a/ScoutSuite/providers/aws/resources/iam/accountsummary.py b/ScoutSuite/providers/aws/resources/iam/accountsummary.py new file mode 100644 index 000000000..a3a9d0475 --- /dev/null +++ b/ScoutSuite/providers/aws/resources/iam/accountsummary.py @@ -0,0 +1,27 @@ +from ScoutSuite.providers.aws.resources.base import AWSResources + +class AccountSummary(AWSResources): + async def fetch_all(self): + raw_account_summary = await self.facade.iam.get_account_summary() + account_summary = self._parse_account_summary(raw_account_summary) + self.update(account_summary) + + def _parse_account_summary(self, raw_account_summary): + if raw_account_summary is None: + return { + 'Users': 0, + 'Roles': 0, + 'Groups': 0, + 'Policies': 0, + 'InstanceProfiles': 0, + 'AccountAccessKeysPresent': 0, + 'AccountPasswordPresent': 0, + 'AccountMFAEnabled': 0, + 'MFADevicesInUse': 0, + 'MFADevices': 0, + 'AccountSigningCertificatesPresent': 0, + 'PolicyVersionsInUse': 0, + 'ServerCertificates': 0 + } + + return raw_account_summary diff --git a/ScoutSuite/providers/aws/resources/iam/base.py b/ScoutSuite/providers/aws/resources/iam/base.py index f64099ada..cb395c2ea 100755 --- a/ScoutSuite/providers/aws/resources/iam/base.py +++ b/ScoutSuite/providers/aws/resources/iam/base.py @@ -5,6 +5,7 @@ from ScoutSuite.providers.aws.resources.iam.users import Users from ScoutSuite.providers.aws.resources.iam.roles import Roles from ScoutSuite.providers.aws.resources.iam.passwordpolicy import PasswordPolicy +from ScoutSuite.providers.aws.resources.iam.accountsummary import AccountSummary from ScoutSuite.providers.aws.facade.base import AWSFacade from ScoutSuite.core.console import print_exception @@ -16,7 +17,8 @@ class IAM(AWSCompositeResources): (Policies, 'policies'), (Users, 'users'), (Roles, 'roles'), - (PasswordPolicy, 'password_policy') + (PasswordPolicy, 'password_policy'), + (AccountSummary, 'account_summary') ] def __init__(self, facade: AWSFacade): @@ -26,8 +28,9 @@ def __init__(self, facade: AWSFacade): async def fetch_all(self, partition_name='aws', **kwargs): await self._fetch_children(self) - # We do not want the report to count the password policies as resources, they aren't really resources. + # We do not want the report to count password policies or account summaries as resources, they aren't really resources. self['password_policy_count'] = 0 + self['account_summary_count'] = 0 async def finalize(self): try: diff --git a/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-access-keys.json b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-access-keys.json index 937d33d0d..7a32d53ed 100644 --- a/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-access-keys.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-access-keys.json @@ -17,26 +17,13 @@ "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_document-processes" ], "dashboard_name": "Root account", - "path": "iam.credential_reports.id", + "path": "iam.account_summary.AccountAccessKeysPresent", "conditions": [ - "and", + "or", [ - "iam.credential_reports.id.name", - "equal", - "" - ], - [ - "or", - [ - "iam.credential_reports.id.access_key_1_last_rotated", - "notEqual", - "N/A" - ], - [ - "iam.credential_reports.id.access_key_2_last_rotated", - "notEqual", - "N/A" - ] + "this", + "moreThan", + "0" ] ] } diff --git a/doc/aws-minimal-permission-policy.json b/doc/aws-minimal-permission-policy.json index 35d9c0686..f64c41d12 100644 --- a/doc/aws-minimal-permission-policy.json +++ b/doc/aws-minimal-permission-policy.json @@ -84,6 +84,7 @@ "guardduty:ListDetectors", "iam:GenerateCredentialReport", "iam:GetAccountPasswordPolicy", + "iam:GetAccountSummary", "iam:GetCredentialReport", "iam:GetGroup", "iam:GetGroupPolicy", From e634ae60f70c735eace777dddc431d897c092be6 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Thu, 19 Dec 2024 18:07:37 -0800 Subject: [PATCH 23/33] Fixed unit tests relating to AWS IAM password policies. --- tests/data/rule-configs/iam-password-policy.json | 2 +- tests/data/ruleset-test.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/data/rule-configs/iam-password-policy.json b/tests/data/rule-configs/iam-password-policy.json index 430c49a9b..0b8c587f3 100755 --- a/tests/data/rule-configs/iam-password-policy.json +++ b/tests/data/rule-configs/iam-password-policy.json @@ -5,7 +5,7 @@ "password_policy": { "ExpirePasswords": false, "MinimumPasswordLength": "1", - "PasswordReusePrevention": false, + "PasswordReusePrevention": 0, "RequireLowercaseCharacters": false, "RequireNumbers": false, "RequireSymbols": false, diff --git a/tests/data/ruleset-test.json b/tests/data/ruleset-test.json index 2d8eab242..dd67f755c 100755 --- a/tests/data/ruleset-test.json +++ b/tests/data/ruleset-test.json @@ -2,6 +2,9 @@ "rules": { "iam-password-policy-reuse-enabled.json": [ { + "args": [ + "1" + ], "enabled": true, "level": "danger" } From 69eeb3aeea0c8d7f4748bd94c7a1aa0bdc67d1fe Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Thu, 19 Dec 2024 18:13:59 -0800 Subject: [PATCH 24/33] Added rule references to the ADA-CP-1.0 ruleset. --- .../providers/aws/rules/rulesets/ada-cp-1.0.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json index 6aedc058c..9103e6fac 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -3,48 +3,56 @@ "rules": { "awslambda-runtime-deprecated.json": [ { + "comment":"ADA-CP 1.2.1 Ensure that all AWS Lambda functions are configured to use a current (not deprecated) runtime", "enabled": true, "level": "danger" } ], "iam-no-support-role.json": [ { + "comment": "ADA-CP 2.2.1 Ensure a support role has been created to manage incidents with AWS Support", "enabled": true, "level": "danger" } ], "account-contact-details-missing.json": [ { + "comment": "ADA-CP 2.3.1 Maintain current contact details", "enabled": true, "level": "danger" } ], "account-security-contact-details-missing.json": [ { + "comment": "ADA-CP 2.3.2 Ensure security contact information is registered", "enabled": true, "level": "danger" } ], "iam-root-account-with-access-keys.json": [ { + "comment": "ADA-CP 2.7.1 Ensure no 'root' user account access key exists", "enabled": true, "level": "danger" } ], "iam-user-password-key-assigned-at-creation.json": [ { + "comment": "ADA-CP 2.7.2 Do not setup access keys during initial user setup for all IAM users that have a console password", "enabled": true, "level": "danger" } ], "iam-admin-policy-attached.json": [ { + "comment": "ADA-CP 2.7.3 Ensure IAM policies that allow full ':' administrative privileges are not attached", "enabled": true, "level": "danger" } ], "iam-password-policy-minimum-length.json": [ { + "comment": "ADA-CP 2.8.2 Ensure IAM password policy requires minimum length of 14 or greater", "args": [ "14" ], @@ -54,12 +62,14 @@ ], "iam-user-with-multiple-access-keys.json": [ { + "comment": "ADA-CP 2.8.3 Ensure there is only one active access key available for any single IAM user", "enabled": true, "level": "danger" } ], "iam-user-no-key-rotation.json": [ { + "comment": "ADA-CP 2.8.4 Ensure access keys are rotated every 90 days or less", "args": [ "Active", "90" @@ -70,6 +80,7 @@ ], "iam-password-policy-reuse-enabled.json": [ { + "comment": "ADA-CP 2.9.1 Ensure IAM password policy prevents password reuse", "args": [ "24" ], From 365ce5e85cb6076471d6342b5c63dc75cda3786b Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Fri, 20 Dec 2024 12:48:43 -0800 Subject: [PATCH 25/33] Reverting changes to this rule that is no longer used for ADA-CP. --- .../rules/findings/iam-root-account-with-active-keys.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json index 56cac39a4..51d533d66 100755 --- a/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-active-keys.json @@ -17,11 +17,6 @@ "name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "1.12" - }, - { - "name": "CIS Amazon Web Services Foundations", - "version": "2.0.0", - "reference": "1.4" } ], "references": [ From 64da479f81e83da7910cb0ab5dc07eec227401c5 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Fri, 20 Dec 2024 12:49:56 -0800 Subject: [PATCH 26/33] iam-admin-policy-attached: better way to detect aws-managed policies The new method should work reliably in all partitions. --- .../aws/rules/findings/iam-admin-policy-attached.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json b/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json index b284513a5..aa62b0174 100644 --- a/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json @@ -21,9 +21,9 @@ "conditions": [ "and", [ - "iam.policies.id.arn", - "notMatch", - "^arn:aws:iam::aws:policy/.*$" + "iam.policies.id.management", + "equal", + "Customer" ], [ "iam.policies.id.attached_to", From 590a77c916dc2d967b3a5d407dda3ae5ed6b4319 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Fri, 20 Dec 2024 14:26:17 -0800 Subject: [PATCH 27/33] Added unit tests for all checks in ADA-CP-AWS that lack them. With this change, all rules currently in `ada-cp-1.0.json` for AWS have at least a minimal test case in `tests/test_rules_processingengine.py`, which passes. `tests/test_rules_ruleset.py` continues to fail, but I did not introduce that bug. Some of the other tests also throw warnings. --- .../account-contact-details-missing.json | 27 +++ ...ount-security-contact-details-missing.json | 21 ++ .../iam-admin-policy-attached.json | 210 ++++++++++++++++++ .../iam-root-account-with-access-keys.json | 43 ++++ .../iam-user-no-key-rotation.json | 54 +++++ .../iam-user-with-multiple-access-keys.json | 73 ++++++ .../account-contact-details-missing.json | 3 + ...ount-security-contact-details-missing.json | 3 + .../iam-admin-policy-attached.json | 4 + .../iam-root-account-with-access-keys.json | 3 + .../iam-user-no-key-rotation.json | 3 + .../iam-user-with-multiple-access-keys.json | 3 + tests/data/ruleset-test.json | 29 ++- 13 files changed, 469 insertions(+), 7 deletions(-) create mode 100644 tests/data/rule-configs/account-contact-details-missing.json create mode 100644 tests/data/rule-configs/account-security-contact-details-missing.json create mode 100644 tests/data/rule-configs/iam-admin-policy-attached.json create mode 100644 tests/data/rule-configs/iam-root-account-with-access-keys.json create mode 100644 tests/data/rule-configs/iam-user-no-key-rotation.json create mode 100644 tests/data/rule-configs/iam-user-with-multiple-access-keys.json create mode 100644 tests/data/rule-results/account-contact-details-missing.json create mode 100644 tests/data/rule-results/account-security-contact-details-missing.json create mode 100644 tests/data/rule-results/iam-admin-policy-attached.json create mode 100644 tests/data/rule-results/iam-root-account-with-access-keys.json create mode 100644 tests/data/rule-results/iam-user-no-key-rotation.json create mode 100644 tests/data/rule-results/iam-user-with-multiple-access-keys.json diff --git a/tests/data/rule-configs/account-contact-details-missing.json b/tests/data/rule-configs/account-contact-details-missing.json new file mode 100644 index 000000000..898030db3 --- /dev/null +++ b/tests/data/rule-configs/account-contact-details-missing.json @@ -0,0 +1,27 @@ +{ + "account_id": "123456789012", + "services": { + "account": { + "contacts": { + "0": { + "contact_information": { + "AddressLine1": "123 Some St", + "City": "", + "CountryCode": "US", + "FullName": "Nobody", + "PhoneNumber": "+1-555-555-1234", + "PostalCode": "111111", + "StateOrRegion": "Noplace" + }, + "security_contact": { + "AlternateContactType": "SECURITY", + "EmailAddress": "somebody@invalid.zz", + "Name": "Nobody", + "PhoneNumber": "+1-555-555-1234", + "Title": "Nothing" + } + } + } + } + } +} diff --git a/tests/data/rule-configs/account-security-contact-details-missing.json b/tests/data/rule-configs/account-security-contact-details-missing.json new file mode 100644 index 000000000..84e078fed --- /dev/null +++ b/tests/data/rule-configs/account-security-contact-details-missing.json @@ -0,0 +1,21 @@ +{ + "account_id": "123456789012", + "services": { + "account": { + "contacts": { + "0": { + "contact_information": { + "AddressLine1": "123 Some St", + "City": "Nowhere", + "CountryCode": "US", + "FullName": "Nobody", + "PhoneNumber": "+1-555-555-1234", + "PostalCode": "111111", + "StateOrRegion": "Noplace" + }, + "security_contact": null + } + } + } + } +} diff --git a/tests/data/rule-configs/iam-admin-policy-attached.json b/tests/data/rule-configs/iam-admin-policy-attached.json new file mode 100644 index 000000000..0c062aa38 --- /dev/null +++ b/tests/data/rule-configs/iam-admin-policy-attached.json @@ -0,0 +1,210 @@ +{ + "account_id": "123456789012", + "services": { + "iam": { + "policies": { + "ANPAAAAAAAAAAAAAAAAAA": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "arn": "arn:aws:iam::aws:policy/AdministratorAccess", + "attached_to": { + "roles": [ + { + "id": "AROAXXXXXXXXXXXXXXXX", + "name": "Admin" + } + ], + "users": [ + { + "id": "AROAXXXXXXXXXXXXXXXX", + "name": "admin" + } + ] + }, + "id": "ANPAAAAAAAAAAAAAAAAAA", + "management": "AWS", + "name": "AdministratorAccess" + }, + "ANPABBBBBBBBBBBBBBBBB": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ], + "Sid": "Statement1" + } + ], + "Version": "2012-10-17" + }, + "arn": "arn:aws:iam::111111111111:policy/admin", + "attached_to": { + "users": [ + { + "id": "AROAXXXXXXXXXXXXXXXX", + "name": "test2" + } + ] + }, + "id": "ANPABBBBBBBBBBBBBBBBB", + "management": "Customer", + "name": "admin" + }, + "ANPACCCCCCCCCCCCCCCCC": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3::foo/bar" + ], + "Sid": "Statement1" + }, + { + "Action": [ + "s3:PutObject", + "*", + "s3:GetObjectVersion" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3::foo/*", + "*", + "arn:aws:s3::foo/bar" + ], + "Sid": "Statement1" + } + ], + "Version": "2012-10-17" + }, + "arn": "arn:aws:iam::111111111111:policy/admin-obfuscated", + "attached_to": { + "groups": [ + { + "id": "AROAXXXXXXXXXXXXXXXX", + "name": "admins" + } + ] + }, + "id": "ANPACCCCCCCCCCCCCCCCC", + "management": "Customer", + "name": "admin-obfuscated" + }, + "ANPADDDDDDDDDDDDDDDDD": { + "PolicyDocument": {}, + "arn": "arn:aws:iam::111111111111:policy/admin-not-attached", + "attached_to": {}, + "id": "ANPADDDDDDDDDDDDDDDDD", + "management": "Customer", + "name": "admin-not-attached" + }, + "ANPAEEEEEEEEEEEEEEEEE": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "*" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3::foo" + ], + "Sid": "Statement1" + } + ], + "Version": "2012-10-17" + }, + "arn": "arn:aws:iam::111111111111:policy/admin-s3", + "attached_to": { + "users": [ + { + "id": "AROAXXXXXXXXXXXXXXXX", + "name": "test2" + } + ] + }, + "id": "ANPAEEEEEEEEEEEEEEEEE", + "management": "Customer", + "name": "admin-s3" + }, + "ANPAEEEEEEEEEEEEEEEEE": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "Get*" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3::foo" + ], + "Sid": "Statement1" + } + ], + "Version": "2012-10-17" + }, + "arn": "arn:aws:iam::111111111111:policy/read-all", + "attached_to": { + "users": [ + { + "id": "AROAXXXXXXXXXXXXXXXX", + "name": "test2" + } + ] + }, + "id": "ANPAEEEEEEEEEEEEEEEEE", + "management": "Customer", + "name": "read-all" + }, + "ANPAEEEEEEEEEEEEEEEEE": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "*" + ], + "Effect": "Deny", + "Resource": [ + "*" + ], + "Sid": "Statement1" + } + ], + "Version": "2012-10-17" + }, + "arn": "arn:aws:iam::111111111111:policy/anti-admin", + "attached_to": { + "users": [ + { + "id": "AROAXXXXXXXXXXXXXXXX", + "name": "test2" + } + ] + }, + "id": "ANPAEEEEEEEEEEEEEEEEE", + "management": "Customer", + "name": "anti-admin" + } + } + } + } +} diff --git a/tests/data/rule-configs/iam-root-account-with-access-keys.json b/tests/data/rule-configs/iam-root-account-with-access-keys.json new file mode 100644 index 000000000..4d0d93554 --- /dev/null +++ b/tests/data/rule-configs/iam-root-account-with-access-keys.json @@ -0,0 +1,43 @@ +{ + "account_id": "123456789012", + "services": { + "iam": { + "account_summary": { + "AccessKeysPerUserQuota": 2, + "AccountAccessKeysPresent": 1, + "AccountMFAEnabled": 1, + "AccountPasswordPresent": 1, + "AccountSigningCertificatesPresent": 0, + "AssumeRolePolicySizeQuota": 2048, + "AttachedPoliciesPerGroupQuota": 10, + "AttachedPoliciesPerRoleQuota": 10, + "AttachedPoliciesPerUserQuota": 10, + "GlobalEndpointTokenVersion": 1, + "GroupPolicySizeQuota": 5120, + "Groups": 0, + "GroupsPerUserQuota": 10, + "GroupsQuota": 300, + "InstanceProfiles": 0, + "InstanceProfilesQuota": 1000, + "MFADevices": 1, + "MFADevicesInUse": 1, + "Policies": 1, + "PoliciesQuota": 1500, + "PolicySizeQuota": 6144, + "PolicyVersionsInUse": 4, + "PolicyVersionsInUseQuota": 10000, + "Providers": 0, + "RolePolicySizeQuota": 10240, + "Roles": 2, + "RolesQuota": 1000, + "ServerCertificates": 0, + "ServerCertificatesQuota": 20, + "SigningCertificatesPerUserQuota": 2, + "UserPolicySizeQuota": 2048, + "Users": 15, + "UsersQuota": 5000, + "VersionsPerPolicyQuota": 5 + } + } + } +} diff --git a/tests/data/rule-configs/iam-user-no-key-rotation.json b/tests/data/rule-configs/iam-user-no-key-rotation.json new file mode 100644 index 000000000..908357fd4 --- /dev/null +++ b/tests/data/rule-configs/iam-user-no-key-rotation.json @@ -0,0 +1,54 @@ +{ + "account_id": "123456789012", + "services": { + "iam": { + "users": { + "AIDAXXXXXXXXAAAAAAAAA": { + "AccessKeys": [ + { + "AccessKeyId": "AKIAXXXXXXXXAAAAAAAA", + "CreateDate": "2024-06-14 22:51:27+00:00", + "Status": "Inactive", + "UserName": "test" + } + ], + "arn": "arn:aws:iam::123456789012:user/test", + "id": "AIDAXXXXXXXXAAAAAAAAA", + "name": "test" + }, + "AIDAXXXXXXXXBBBBBBBBB": { + "AccessKeys": [ + { + "AccessKeyId": "AKIAXXXXXXXXCCCCCCCC", + "CreateDate": "2021-02-15 22:56:29+00:00", + "Status": "Active", + "UserName": "admin" + } + ], + "arn": "arn:aws:iam::123456789012:user/admin", + "id": "AIDAXXXXXXXXBBBBBBBBB", + "name": "admin" + }, + "AIDAXXXXXXXXCCCCCCCCC": { + "AccessKeys": [ + { + "AccessKeyId": "AKIAXXXXXXXXDDDDDDDD", + "CreateDate": "2024-12-20 21:48:01+00:00", + "Status": "Active", + "UserName": "test2" + } + ], + "arn": "arn:aws:iam::123456789012:user/test2", + "id": "AIDAXXXXXXXXCCCCCCCCC", + "name": "test2" + }, + "AIDAXXXXXXXXDDDDDDDDD": { + "AccessKeys": [], + "arn": "arn:aws:iam::123456789012:user/no-access", + "id": "AIDAXXXXXXXXDDDDDDDDD", + "name": "no-access" + } + } + } + } +} \ No newline at end of file diff --git a/tests/data/rule-configs/iam-user-with-multiple-access-keys.json b/tests/data/rule-configs/iam-user-with-multiple-access-keys.json new file mode 100644 index 000000000..5b25b5e89 --- /dev/null +++ b/tests/data/rule-configs/iam-user-with-multiple-access-keys.json @@ -0,0 +1,73 @@ +{ + "account_id": "123456789012", + "services": { + "iam": { + "users": { + "AIDAXXXXXXXXAAAAAAAAA": { + "AccessKeys": [ + { + "AccessKeyId": "AKIAXXXXXXXXAAAAAAAA", + "CreateDate": "2024-06-14 22:51:27+00:00", + "Status": "Inactive", + "UserName": "test" + }, + { + "AccessKeyId": "AKIAXXXXXXXXBBBBBBBB", + "CreateDate": "2024-06-14 22:51:00+00:00", + "Status": "Inactive", + "UserName": "test" + } + ], + "arn": "arn:aws:iam::123456789012:user/test", + "id": "AIDAXXXXXXXXAAAAAAAAA", + "name": "test" + }, + "AIDAXXXXXXXXBBBBBBBBB": { + "AccessKeys": [ + { + "AccessKeyId": "AKIAXXXXXXXXCCCCCCCC", + "CreateDate": "2021-02-15 22:56:29+00:00", + "Status": "Active", + "UserName": "admin" + } + ], + "arn": "arn:aws:iam::123456789012:user/admin", + "id": "AIDAXXXXXXXXBBBBBBBBB", + "name": "admin" + }, + "AIDAXXXXXXXXCCCCCCCCC": { + "AccessKeys": [ + { + "AccessKeyId": "AKIAXXXXXXXXDDDDDDDD", + "CreateDate": "2024-12-20 21:48:01+00:00", + "Status": "Active", + "UserName": "test2" + }, + { + "AccessKeyId": "AKIAXXXXXXXXEEEEEEEE", + "CreateDate": "2024-12-20 21:47:47+00:00", + "Status": "Active", + "UserName": "test2" + } + ], + "arn": "arn:aws:iam::123456789012:user/test2", + "id": "AIDAXXXXXXXXCCCCCCCCC", + "name": "test2" + }, + "AIDAXXXXXXXXDDDDDDDDD": { + "AccessKeys": [ + { + "AccessKeyId": "AKIAXXXXXXXXFFFFFFFF", + "CreateDate": "2022-02-21 18:03:37+00:00", + "Status": "Inactive", + "UserName": "no-access" + } + ], + "arn": "arn:aws:iam::123456789012:user/no-access", + "id": "AIDAXXXXXXXXDDDDDDDDD", + "name": "no-access" + } + } + } + } +} \ No newline at end of file diff --git a/tests/data/rule-results/account-contact-details-missing.json b/tests/data/rule-results/account-contact-details-missing.json new file mode 100644 index 000000000..d44116e56 --- /dev/null +++ b/tests/data/rule-results/account-contact-details-missing.json @@ -0,0 +1,3 @@ +[ + "account.contacts.0.contact_information" +] \ No newline at end of file diff --git a/tests/data/rule-results/account-security-contact-details-missing.json b/tests/data/rule-results/account-security-contact-details-missing.json new file mode 100644 index 000000000..cee250e62 --- /dev/null +++ b/tests/data/rule-results/account-security-contact-details-missing.json @@ -0,0 +1,3 @@ +[ + "account.contacts.0.security_contact" +] \ No newline at end of file diff --git a/tests/data/rule-results/iam-admin-policy-attached.json b/tests/data/rule-results/iam-admin-policy-attached.json new file mode 100644 index 000000000..7ea10db84 --- /dev/null +++ b/tests/data/rule-results/iam-admin-policy-attached.json @@ -0,0 +1,4 @@ +[ + "iam.policies.ANPABBBBBBBBBBBBBBBBB.PolicyDocument.Statement.0", + "iam.policies.ANPACCCCCCCCCCCCCCCCC.PolicyDocument.Statement.1" +] \ No newline at end of file diff --git a/tests/data/rule-results/iam-root-account-with-access-keys.json b/tests/data/rule-results/iam-root-account-with-access-keys.json new file mode 100644 index 000000000..427176f69 --- /dev/null +++ b/tests/data/rule-results/iam-root-account-with-access-keys.json @@ -0,0 +1,3 @@ +[ + "iam.account_summary.AccountAccessKeysPresent" +] \ No newline at end of file diff --git a/tests/data/rule-results/iam-user-no-key-rotation.json b/tests/data/rule-results/iam-user-no-key-rotation.json new file mode 100644 index 000000000..c8607e04b --- /dev/null +++ b/tests/data/rule-results/iam-user-no-key-rotation.json @@ -0,0 +1,3 @@ +[ + "iam.users.AIDAXXXXXXXXBBBBBBBBB.AccessKeys.0" +] \ No newline at end of file diff --git a/tests/data/rule-results/iam-user-with-multiple-access-keys.json b/tests/data/rule-results/iam-user-with-multiple-access-keys.json new file mode 100644 index 000000000..68e021ca7 --- /dev/null +++ b/tests/data/rule-results/iam-user-with-multiple-access-keys.json @@ -0,0 +1,3 @@ +[ + "iam.users.AIDAXXXXXXXXCCCCCCCCC.multiple_api_keys" +] \ No newline at end of file diff --git a/tests/data/ruleset-test.json b/tests/data/ruleset-test.json index dd67f755c..3051a0f33 100755 --- a/tests/data/ruleset-test.json +++ b/tests/data/ruleset-test.json @@ -1,21 +1,30 @@ { "rules": { - "iam-password-policy-reuse-enabled.json": [ + "awslambda-runtime-deprecated.json": [ + { + "enabled": true, + "level": "warning" + } + ], + "iam-no-support-role.json": [ { - "args": [ - "1" - ], "enabled": true, "level": "danger" } ], - "awslambda-runtime-deprecated.json": [ + "account-contact-details-missing.json": [ { "enabled": true, - "level": "warning" + "level": "danger" } ], - "iam-no-support-role.json": [ + "account-security-contact-details-missing.json": [ + { + "enabled": true, + "level": "danger" + } + ], + "iam-root-account-with-access-keys.json": [ { "enabled": true, "level": "danger" @@ -26,6 +35,12 @@ "enabled": true, "level": "danger" } + ], + "iam-admin-policy-attached.json": [ + { + "enabled": true, + "level": "danger" + } ] }, "about": "extra test rules" From 6568f10af4e45e8d78563a35ee58764415341321 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 30 Dec 2024 13:06:14 -0800 Subject: [PATCH 28/33] Fixed syntax error in dev-requirements.txt --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 4bb7f8a49..1d9f47711 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -3,6 +3,6 @@ flake8 codecov coveralls autopep8 -pytest>=5.* +pytest>=5.0 pytest-cov mypy From da105df388e5e9cbe61cda21b940bbb4fcd426c7 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 30 Dec 2024 14:10:57 -0800 Subject: [PATCH 29/33] Fixed time for unit tests. Used the "freezegun" module to fix the date at 2024-01-01 for all unit tests. Modified the times for the one existing, applicable test case to suit this date. --- dev-requirements.txt | 1 + tests/data/rule-configs/iam-user-no-key-rotation.json | 6 +++--- tests/test_rules_processingengine.py | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 1d9f47711..b70a31e60 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -6,3 +6,4 @@ autopep8 pytest>=5.0 pytest-cov mypy +freezegun diff --git a/tests/data/rule-configs/iam-user-no-key-rotation.json b/tests/data/rule-configs/iam-user-no-key-rotation.json index 908357fd4..67949fd88 100644 --- a/tests/data/rule-configs/iam-user-no-key-rotation.json +++ b/tests/data/rule-configs/iam-user-no-key-rotation.json @@ -7,7 +7,7 @@ "AccessKeys": [ { "AccessKeyId": "AKIAXXXXXXXXAAAAAAAA", - "CreateDate": "2024-06-14 22:51:27+00:00", + "CreateDate": "2023-06-01 00:00:00+00:00", "Status": "Inactive", "UserName": "test" } @@ -20,7 +20,7 @@ "AccessKeys": [ { "AccessKeyId": "AKIAXXXXXXXXCCCCCCCC", - "CreateDate": "2021-02-15 22:56:29+00:00", + "CreateDate": "2023-10-01 00:00:00+00:00", "Status": "Active", "UserName": "admin" } @@ -33,7 +33,7 @@ "AccessKeys": [ { "AccessKeyId": "AKIAXXXXXXXXDDDDDDDD", - "CreateDate": "2024-12-20 21:48:01+00:00", + "CreateDate": "2023-10-05 00:00:00+00:00", "Status": "Active", "UserName": "test2" } diff --git a/tests/test_rules_processingengine.py b/tests/test_rules_processingengine.py index c944675f4..9704fd67c 100755 --- a/tests/test_rules_processingengine.py +++ b/tests/test_rules_processingengine.py @@ -2,6 +2,7 @@ import os import tempfile import unittest +import freezegun from ScoutSuite.core.console import set_logger_configuration, print_error from ScoutSuite.core.processingengine import ProcessingEngine @@ -22,6 +23,7 @@ def setUp(self): # TODO # Check that one testcase per finding rule exists (should be within default ruleset) + @freezegun.freeze_time("2024-01-01") def test_all_finding_rules(self): # Test everything in the "default" ruleset # FIXME this is only for AWS From 98a801ee1a981d59ead05383fac80c361701abf3 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 30 Dec 2024 15:07:46 -0800 Subject: [PATCH 30/33] Rule for ADA-CP 2.10.1 "Ensure credentials unused for 45 days or greater are disabled" This rule is closely related to "iam-unused-credentials-not-disabled". However, that rule applies only to credentials that have been used; both ADA-CP and CIS 2.0 also apply to credentials that have *not* been used. I haven't checked earlier versions of CIS, so maybe they are more limited in scope; alternately, "iam-unused-credentials-not-disabled" may be just wrong and should be replaced with this new rule. --- ...d-credentials-not-disabled-or-rotated.json | 155 +++++++++++ .../aws/rules/rulesets/ada-cp-1.0.json | 10 + ...d-credentials-not-disabled-or-rotated.json | 261 ++++++++++++++++++ ...d-credentials-not-disabled-or-rotated.json | 8 + tests/data/ruleset-test.json | 9 + 5 files changed, 443 insertions(+) create mode 100644 ScoutSuite/providers/aws/rules/findings/iam-unused-credentials-not-disabled-or-rotated.json create mode 100644 tests/data/rule-configs/iam-unused-credentials-not-disabled-or-rotated.json create mode 100644 tests/data/rule-results/iam-unused-credentials-not-disabled-or-rotated.json diff --git a/ScoutSuite/providers/aws/rules/findings/iam-unused-credentials-not-disabled-or-rotated.json b/ScoutSuite/providers/aws/rules/findings/iam-unused-credentials-not-disabled-or-rotated.json new file mode 100644 index 000000000..6f7f9cd22 --- /dev/null +++ b/ScoutSuite/providers/aws/rules/findings/iam-unused-credentials-not-disabled-or-rotated.json @@ -0,0 +1,155 @@ +{ + "comment": "This rule is very similar to iam-unused-credentials-not-disabled.json, the difference being that it allows unused active credentials to be less than _ARG_0_ days old rather than only applying to credentials that have been used. CIS 2.0 also requires this, but I haven't checked earlier versions of CIS.", + "description": "Credentials Unused for _ARG_0_ Days or Greater Are Not Disabled", + "rationale": "Disabling or removing unnecessary credentials will reduce the window of opportunity for compromised accounts to be used.", + "remediation": "Ensure that all credentials (including passwords and access keys) have been used or changed in the last _ARG_0_ days", + "compliance": [ + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.10.1" + } + ], + "references": [ + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#2101-ensure-credentials-unused-for-45-days-or-greater-are-disabled", + "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html" + ], + "dashboard_name": "Users", + "path": "iam.credential_reports.id", + "conditions": [ + "or", + [ + "and", + [ + "iam.credential_reports.id.password_enabled", + "true", + "" + ], + [ + "or", + [ + "and", + [ + "iam.credential_reports.id.password_last_used", + "notNull", + "" + ], + [ + "iam.credential_reports.id.password_last_used", + "olderThan", + [ + "_ARG_0_", + "days" + ] + ] + ], + [ + "and", + [ + "iam.credential_reports.id.password_last_used", + "null", + "" + ], + [ + "iam.credential_reports.id.password_last_changed", + "olderThan", + [ + "_ARG_0_", + "days" + ] + ] + ] + ] + ], + [ + "and", + [ + "iam.credential_reports.id.access_key_1_active", + "true", + "" + ], + [ + "or", + [ + "and", + [ + "iam.credential_reports.id.access_key_1_last_used_date", + "notNull", + "" + ], + [ + "iam.credential_reports.id.access_key_1_last_used_date", + "olderThan", + [ + "_ARG_0_", + "days" + ] + ] + ], + [ + "and", + [ + "iam.credential_reports.id.access_key_1_last_used_date", + "null", + "" + ], + [ + "iam.credential_reports.id.access_key_1_last_rotated", + "olderThan", + [ + "_ARG_0_", + "days" + ] + ] + ] + ] + ], + [ + "and", + [ + "iam.credential_reports.id.access_key_2_active", + "true", + "" + ], + [ + "or", + [ + "and", + [ + "iam.credential_reports.id.access_key_2_last_used_date", + "notNull", + "" + ], + [ + "iam.credential_reports.id.access_key_2_last_used_date", + "olderThan", + [ + "_ARG_0_", + "days" + ] + ] + ], + [ + "and", + [ + "iam.credential_reports.id.access_key_2_last_used_date", + "null", + "" + ], + [ + "iam.credential_reports.id.access_key_2_last_rotated", + "olderThan", + [ + "_ARG_0_", + "days" + ] + ] + ] + ] + ] + ], + "arg_names": [ + "Period in days" + ], + "class_suffix": "unused_credentials" +} \ No newline at end of file diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json index 9103e6fac..20d08dce2 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -87,6 +87,16 @@ "enabled": true, "level": "danger" } + ], + "iam-unused-credentials-not-disabled-or-rotated.json": [ + { + "comment": "ADA-CP 2.10.1 Ensure credentials unused for 45 days or greater are disabled", + "args": [ + "45" + ], + "enabled": true, + "level": "danger" + } ] } } \ No newline at end of file diff --git a/tests/data/rule-configs/iam-unused-credentials-not-disabled-or-rotated.json b/tests/data/rule-configs/iam-unused-credentials-not-disabled-or-rotated.json new file mode 100644 index 000000000..e46f3683f --- /dev/null +++ b/tests/data/rule-configs/iam-unused-credentials-not-disabled-or-rotated.json @@ -0,0 +1,261 @@ +{ + "account_id": "123456789012", + "services": { + "iam": { + "credential_reports": { + "scoutid-0000": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/password-none", + "id": "scoutid-0000", + "name": "password-none", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0001": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/password-unused-new", + "id": "scoutid-0001", + "name": "password-unused-new", + "password_enabled": "true", + "password_last_changed": "2023-11-17T00:00:00Z", + "password_last_used": null + }, + "scoutid-0002": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/password-recent", + "id": "scoutid-0002", + "name": "password-recent", + "password_enabled": "true", + "password_last_changed": "2023-11-01T00:00:00Z", + "password_last_used": "2023-11-17T00:00:00Z" + }, + "scoutid-0003": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/password-unused-old", + "id": "scoutid-0003", + "name": "password-unused-old", + "password_enabled": "true", + "password_last_changed": "2023-11-15T00:00:00Z", + "password_last_used": null + }, + "scoutid-0004": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/password-old", + "id": "scoutid-0004", + "name": "password-old", + "password_enabled": "true", + "password_last_changed": "2023-11-15T00:00:00Z", + "password_last_used": "2023-11-15T00:00:00Z" + }, + "scoutid-0100": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/keys-none", + "id": "scoutid-0100", + "name": "keys-none", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0101": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-11-17T00:00:00Z", + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/key1-unused-new", + "id": "scoutid-0101", + "name": "key1-unused-new", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0102": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-11-01T00:00:00Z", + "access_key_1_last_used_date": "2023-11-17T00:00:00Z", + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/key1-recent", + "id": "scoutid-0102", + "name": "key1-recent", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0103": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-11-15T00:00:00Z", + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/key1-unused-old", + "id": "scoutid-0103", + "name": "key1-unused-old", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0104": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-11-01T00:00:00Z", + "access_key_1_last_used_date": "2023-11-15T00:00:00Z", + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/key1-old", + "id": "scoutid-0104", + "name": "key1-old", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0201": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-12-31T00:00:00Z", + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "true", + "access_key_2_last_rotated": "2023-11-17T00:00:00Z", + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/key2-unused-new", + "id": "scoutid-0101", + "name": "key2-unused-new", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0202": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-12-31T00:00:00Z", + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "true", + "access_key_2_last_rotated": "2023-11-01T00:00:00Z", + "access_key_2_last_used_date": "2023-11-17T00:00:00Z", + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/key2-recent", + "id": "scoutid-0102", + "name": "key2-recent", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0203": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-12-31T00:00:00Z", + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "true", + "access_key_2_last_rotated": "2023-11-15T00:00:00Z", + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/key2-unused-old", + "id": "scoutid-0203", + "name": "key2-unused-old", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0204": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-12-31T00:00:00Z", + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "true", + "access_key_2_last_rotated": "2023-11-01T00:00:00Z", + "access_key_2_last_used_date": "2023-11-15T00:00:00Z", + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:user/key2-old", + "id": "scoutid-0204", + "name": "key2-old", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + } + } + } + } +} diff --git a/tests/data/rule-results/iam-unused-credentials-not-disabled-or-rotated.json b/tests/data/rule-results/iam-unused-credentials-not-disabled-or-rotated.json new file mode 100644 index 000000000..e1927c868 --- /dev/null +++ b/tests/data/rule-results/iam-unused-credentials-not-disabled-or-rotated.json @@ -0,0 +1,8 @@ +[ + "iam.credential_reports.scoutid-0003.unused_credentials", + "iam.credential_reports.scoutid-0004.unused_credentials", + "iam.credential_reports.scoutid-0103.unused_credentials", + "iam.credential_reports.scoutid-0104.unused_credentials", + "iam.credential_reports.scoutid-0203.unused_credentials", + "iam.credential_reports.scoutid-0204.unused_credentials" +] \ No newline at end of file diff --git a/tests/data/ruleset-test.json b/tests/data/ruleset-test.json index 3051a0f33..fbd3424b6 100755 --- a/tests/data/ruleset-test.json +++ b/tests/data/ruleset-test.json @@ -41,6 +41,15 @@ "enabled": true, "level": "danger" } + ], + "iam-unused-credentials-not-disabled-or-rotated.json": [ + { + "args": [ + "45" + ], + "enabled": true, + "level": "danger" + } ] }, "about": "extra test rules" From 8c054765dcbf6ed3a0554f5d7dc373c9e2c10466 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 30 Dec 2024 16:50:15 -0800 Subject: [PATCH 31/33] iam-unused-credentials-not-disabled-or-rotated: added CIS 2.0 compliance --- .../iam-unused-credentials-not-disabled-or-rotated.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ScoutSuite/providers/aws/rules/findings/iam-unused-credentials-not-disabled-or-rotated.json b/ScoutSuite/providers/aws/rules/findings/iam-unused-credentials-not-disabled-or-rotated.json index 6f7f9cd22..8fad904ce 100644 --- a/ScoutSuite/providers/aws/rules/findings/iam-unused-credentials-not-disabled-or-rotated.json +++ b/ScoutSuite/providers/aws/rules/findings/iam-unused-credentials-not-disabled-or-rotated.json @@ -4,6 +4,11 @@ "rationale": "Disabling or removing unnecessary credentials will reduce the window of opportunity for compromised accounts to be used.", "remediation": "Ensure that all credentials (including passwords and access keys) have been used or changed in the last _ARG_0_ days", "compliance": [ + { + "name": "CIS Amazon Web Services Foundations", + "version": "2.0.0", + "reference": "1.12" + }, { "name": "ADA Cloud Profile", "version": "1.0", From 21e8645817c653f499fd3e1a99aa41279a3b3d69 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 30 Dec 2024 16:51:41 -0800 Subject: [PATCH 32/33] Rule for ADA-CP 2.11.1 Eliminate use of the 'root' user for administrative and daily tasks This rule is closely related to "iam-root-account-used-recently.json". However, that rule only applies to passwords; both ADA-CP 1.0 and CIS 2.0 also apply to API keys. I haven't checked earlier versions of CIS, so maybe they are more limited in scope; alternately, "iam-root-account-used-recently" may just be wrong and should be replaced with this new rule. --- .../iam-root-credentials-used-recently.json | 87 ++++++++ .../aws/rules/rulesets/ada-cp-1.0.json | 10 + .../iam-root-credentials-used-recently.json | 207 ++++++++++++++++++ .../iam-root-credentials-used-recently.json | 5 + tests/data/ruleset-test.json | 9 + 5 files changed, 318 insertions(+) create mode 100644 ScoutSuite/providers/aws/rules/findings/iam-root-credentials-used-recently.json create mode 100644 tests/data/rule-configs/iam-root-credentials-used-recently.json create mode 100644 tests/data/rule-results/iam-root-credentials-used-recently.json diff --git a/ScoutSuite/providers/aws/rules/findings/iam-root-credentials-used-recently.json b/ScoutSuite/providers/aws/rules/findings/iam-root-credentials-used-recently.json new file mode 100644 index 000000000..46e4c9c6b --- /dev/null +++ b/ScoutSuite/providers/aws/rules/findings/iam-root-credentials-used-recently.json @@ -0,0 +1,87 @@ +{ + "comment": "This rule is very similar to 'iam-root-account-used-recently.json', the difference being that this rule also applies to API keys as required by CIS 2.0 1.7 and ADA-CP 2.11.1. Neither ADA nor CIS give a fixed amount of time over which root creds have been used; both take the perspective of 'make sure that they haven't been used recently without a good reason'. This rule uses an arbitrary cutoff and expects the investigator to review.", + "description": "Root Account Used Recently", + "rationale": "The root account is the most privileged user in an account. As a best practice, the root account should only be used when required for root-only tasks.", + "remediation": "Migrate all tasks performed by the root user to other principals.", + "compliance": [ + { + "name": "CIS Amazon Web Services Foundations", + "version": "2.0", + "reference": "1.7" + }, + { + "name": "ADA Cloud Profile", + "version": "1.0", + "reference": "2.11.1" + } ], + "references": [ + "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#2111-eliminate-use-of-the-root-user-for-administrative-and-daily-tasks", + "https://docs.aws.amazon.com/general/latest/gr/aws_tasks-that-require-root.html", + "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-use", + "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_review-access", + "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_document-processes", + "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_monitor-access" + ], + "dashboard_name": "Root account", + "path": "iam.credential_reports.id", + "conditions": [ + "and", + [ + "iam.credential_reports.id.name", + "equal", + "" + ], + [ + "or", + [ + "and", + [ + "iam.credential_reports.id.password_last_used", + "notNull", + "" + ], + [ + "iam.credential_reports.id.password_last_used", + "newerThan", + [ + "_ARG_0_", + "days" + ] + ] + ], + [ + "and", + [ + "iam.credential_reports.id.access_key_1_last_used_date", + "notNull", + "" + ], + [ + "iam.credential_reports.id.access_key_1_last_used_date", + "newerThan", + [ + "_ARG_0_", + "days" + ] + ] + ], + [ + "and", + [ + "iam.credential_reports.id.access_key_2_last_used_date", + "notNull", + "" + ], + [ + "iam.credential_reports.id.access_key_2_last_used_date", + "newerThan", + [ + "_ARG_0_", + "days" + ] + ] + ] + ] + ], + "id_suffix": "password_last_used" +} \ No newline at end of file diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json index 20d08dce2..c641bfa9b 100644 --- a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json +++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json @@ -97,6 +97,16 @@ "enabled": true, "level": "danger" } + ], + "iam-root-credentials-used-recently.json": [ + { + "comment": "ADA-CP 1.11.1 Eliminate use of the 'root' user for administrative and daily tasks", + "args": [ + "180" + ], + "enabled": true, + "level": "danger" + } ] } } \ No newline at end of file diff --git a/tests/data/rule-configs/iam-root-credentials-used-recently.json b/tests/data/rule-configs/iam-root-credentials-used-recently.json new file mode 100644 index 000000000..64940532e --- /dev/null +++ b/tests/data/rule-configs/iam-root-credentials-used-recently.json @@ -0,0 +1,207 @@ +{ + "account_id": "123456789012", + "services": { + "iam": { + "credential_reports": { + "scoutid-0000": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:root", + "id": "scoutid-0000", + "name": "", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0001": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:root", + "id": "scoutid-0001", + "name": "", + "password_enabled": "true", + "password_last_changed": "2023-01-01T00:00:00Z", + "password_last_used": null + }, + "scoutid-0002": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:root", + "id": "scoutid-0002", + "name": "", + "password_enabled": "true", + "password_last_changed": "2023-01-01T00:00:00Z", + "password_last_used": "2023-07-13T00:00:00Z" + }, + "scoutid-0003": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:root", + "id": "scoutid-0003", + "name": "", + "password_enabled": "true", + "password_last_changed": "2023-01-01T00:00:00Z", + "password_last_used": "2023-07-01T00:00:00Z" + }, + "scoutid-0100": { + "access_key_1_active": "false", + "access_key_1_last_rotated": null, + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:root", + "id": "scoutid-0100", + "name": "", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0101": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-01-01T00:00:00Z", + "access_key_1_last_used_date": null, + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:root", + "id": "scoutid-0101", + "name": "", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0102": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-01-01T00:00:00Z", + "access_key_1_last_used_date": "2023-07-15T00:00:00Z", + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:root", + "id": "scoutid-0102", + "name": "", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0103": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-01-01T00:00:00Z", + "access_key_1_last_used_date": "2023-07-01T00:00:00Z", + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "false", + "access_key_2_last_rotated": null, + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:root", + "id": "scoutid-0103", + "name": "", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0201": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-01-01T00:00:00Z", + "access_key_1_last_used_date": "2023-01-01T00:00:00Z", + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "true", + "access_key_2_last_rotated": "2023-01-01T00:00:00Z", + "access_key_2_last_used_date": null, + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:root", + "id": "scoutid-0201", + "name": "", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0202": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-01-01T00:00:00Z", + "access_key_1_last_used_date": "2023-01-01T00:00:00Z", + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "true", + "access_key_2_last_rotated": "2023-01-01T00:00:00Z", + "access_key_2_last_used_date": "2023-07-15T00:00:00Z", + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:root", + "id": "scoutid-0202", + "name": "", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + }, + "scoutid-0203": { + "access_key_1_active": "true", + "access_key_1_last_rotated": "2023-01-01T00:00:00Z", + "access_key_1_last_used_date": "2023-01-01T00:00:00Z", + "access_key_1_last_used_region": "N/A", + "access_key_1_last_used_service": "N/A", + "access_key_2_active": "true", + "access_key_2_last_rotated": "2023-01-01T00:00:00Z", + "access_key_2_last_used_date": "2023-07-01T00:00:00Z", + "access_key_2_last_used_region": "N/A", + "access_key_2_last_used_service": "N/A", + "arn": "arn:aws:iam::123456789012:root", + "id": "scoutid-0203", + "name": "", + "password_enabled": "false", + "password_last_changed": null, + "password_last_used": null + } + } + } + } +} diff --git a/tests/data/rule-results/iam-root-credentials-used-recently.json b/tests/data/rule-results/iam-root-credentials-used-recently.json new file mode 100644 index 000000000..94fe0fd3a --- /dev/null +++ b/tests/data/rule-results/iam-root-credentials-used-recently.json @@ -0,0 +1,5 @@ +[ + "iam.credential_reports.scoutid-0002.password_last_used", + "iam.credential_reports.scoutid-0102.password_last_used", + "iam.credential_reports.scoutid-0202.password_last_used" +] \ No newline at end of file diff --git a/tests/data/ruleset-test.json b/tests/data/ruleset-test.json index fbd3424b6..7376b3180 100755 --- a/tests/data/ruleset-test.json +++ b/tests/data/ruleset-test.json @@ -50,6 +50,15 @@ "enabled": true, "level": "danger" } + ], + "iam-root-credentials-used-recently.json": [ + { + "args": [ + "180" + ], + "enabled": true, + "level": "danger" + } ] }, "about": "extra test rules" From cd3fe2c1e105a22ec22cf12217be881eaf7dbd74 Mon Sep 17 00:00:00 2001 From: Rennie deGraaf Date: Mon, 30 Dec 2024 17:38:12 -0800 Subject: [PATCH 33/33] Corrected the identifier for the Lambda runtime "nodejs18.x" --- ScoutSuite/providers/aws/resources/awslambda/functions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ScoutSuite/providers/aws/resources/awslambda/functions.py b/ScoutSuite/providers/aws/resources/awslambda/functions.py index 18f2283d7..9b1d67c49 100755 --- a/ScoutSuite/providers/aws/resources/awslambda/functions.py +++ b/ScoutSuite/providers/aws/resources/awslambda/functions.py @@ -79,7 +79,7 @@ async def _add_env_variables(self, function_dict): function_dict["env_variable_values"] = [] def _get_deprecation_date(self, runtime): - # As of July 2024, the Lambda API does not have a way to determine whether a Lambda + # As of December 2024, the Lambda API does not have a way to determine whether a Lambda # runtime is deprecated; that information is only available in AWS documentation. # Consequently, the table here will need to be updated from time to time. # Upcoming deprecation dates: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtimes-supported @@ -88,8 +88,9 @@ def _get_deprecation_date(self, runtime): # Table of runtime identifier : deprecation date # If a particular runtime identifier does not appear in the table, then no deprecation # date for the runtime has been announced. - last_updated = datetime.date(2024, 7, 15) + last_updated = datetime.date(2024, 12, 30) deprecations = { + 'nodejs18.x': datetime.date(2025, 7, 31), # Jul 31 2025 'dotnet6': datetime.date(2024, 12, 20), # Dec 20, 2024 'python3.8': datetime.date(2024, 10, 14), # Oct 14, 2024 'nodejs16.x': datetime.date(2024, 6, 12), # Jun 12, 2024