From 05236db20fd5740f65ca1a74aafafeee8c30adb4 Mon Sep 17 00:00:00 2001 From: Lorenzo Susini Date: Tue, 26 Mar 2024 14:21:14 +0000 Subject: [PATCH 1/6] New attack technique: ses enumeration activities Co-authored-by: Alessandro Brucato Signed-off-by: Lorenzo Susini --- .../aws/discovery/ses-enumerate/main.go | 83 +++++++++++++++++++ .../aws/discovery/ses-enumerate/main.tf | 52 ++++++++++++ v2/internal/attacktechniques/main.go | 1 + 3 files changed, 136 insertions(+) create mode 100644 v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go create mode 100644 v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.tf diff --git a/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go new file mode 100644 index 000000000..1c0992fec --- /dev/null +++ b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go @@ -0,0 +1,83 @@ +package aws + +import ( + "context" + _ "embed" + "log" + + "github.com/datadog/stratus-red-team/v2/internal/utils" + "github.com/datadog/stratus-red-team/v2/pkg/stratus" + "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" + + "github.com/aws/aws-sdk-go-v2/service/ses" +) + +//go:embed main.tf +var tf []byte + +func init() { + stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ + ID: "aws.discovery.ses-enumerate", + FriendlyName: "Enumeration of SES service", + Description: ` +Runs the following discovery commands on SES to enumerate email sending limits and identities, typically used for reconnaissance purposes before launching a phishing campaign: + + - ses:GetSendQuota + - ses:ListIdentities + +See: + +- https://www.invictus-ir.com/news/the-curious-case-of-dangerdev-protonmail-me +- https://docs.aws.amazon.com/ses/latest/APIReference/API_GetSendQuota.html +- https://docs.aws.amazon.com/ses/latest/APIReference/API_ListIdentities.html + +Warm-up: + +- Create an IAM role with the AmazonSESReadOnlyAccess policy attached. The role can be assumed by any user in the AWS account of the caller. + +Detonation: + +- Run ses:GetSendQuota API call +- Run ses:ListIdentities API call +`, + Detection: ` +Through CloudTrail's GetSendQuota and ListIdentities events. +`, + Platform: stratus.AWS, + IsIdempotent: true, + MitreAttackTactics: []mitreattack.Tactic{mitreattack.Discovery}, + PrerequisitesTerraformCode: tf, + Detonate: detonate, + }) +} + +func detonate(params map[string]string, providers stratus.CloudProviders) error { + roleArn := params["role_arn"] + awsConnection := providers.AWS().GetConnection() + if err := utils.WaitForAndAssumeAWSRole(&awsConnection, roleArn); err != nil { + return err + } + sesClient := ses.NewFromConfig(awsConnection) + var maxItems int32 = 10 + listIdentitiesInput := ses.ListIdentitiesInput{ + MaxItems: &maxItems, + NextToken: nil, + } + + identies, err := sesClient.ListIdentities(context.Background(), &listIdentitiesInput) + if err != nil { + return err + } + + log.Println("ListIdentities output: ", identies.Identities) + + quotas, err := sesClient.GetSendQuota(context.Background(), &ses.GetSendQuotaInput{}) + if err != nil { + return err + } + + log.Printf("GetSendQuota output, max24hoursend: %d, maxsendrate: %d, sentlast24hours: %d\n", + int(quotas.Max24HourSend), int(quotas.MaxSendRate), int(quotas.SentLast24Hours)) + + return nil +} diff --git a/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.tf b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.tf new file mode 100644 index 000000000..5f515d953 --- /dev/null +++ b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.tf @@ -0,0 +1,52 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 3.0" + } + } +} + +provider "aws" { + skip_region_validation = true + skip_credentials_validation = true + default_tags { + tags = { + StratusRedTeam = true + } + } +} + +locals { + resource_prefix = "stratus-red-team-ses-enumerate" +} + +data "aws_caller_identity" "current" {} + +resource "aws_iam_role" "ses_enumerate_role" { + name = "${local.resource_prefix}-role" + path = "/" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = ["sts:AssumeRole", "sts:SetSourceIdentity"] + Effect = "Allow" + Sid = "" + Principal = { + AWS = data.aws_caller_identity.current.account_id + } + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "rolepolicy" { + role = aws_iam_role.ses_enumerate_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSESReadOnlyAccess" +} + +output "role_arn" { + value = aws_iam_role.ses_enumerate_role.arn +} diff --git a/v2/internal/attacktechniques/main.go b/v2/internal/attacktechniques/main.go index 0e3a5c21d..ff5a64246 100644 --- a/v2/internal/attacktechniques/main.go +++ b/v2/internal/attacktechniques/main.go @@ -15,6 +15,7 @@ import ( _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/defense-evasion/vpc-remove-flow-logs" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/discovery/ec2-enumerate-from-instance" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/discovery/ec2-get-user-data" + _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/discovery/ses-enumerate" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/execution/ec2-launch-unusual-instances" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/execution/ec2-user-data" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/execution/ssm-send-command" From 9872617e073af0d67fefd4b8b63243b54919dadf Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Thu, 4 Apr 2024 11:18:29 +0200 Subject: [PATCH 2/6] Add missing dependency --- v2/go.mod | 9 +++++---- v2/go.sum | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/v2/go.mod b/v2/go.mod index bedb60949..1474f0b38 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -8,7 +8,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 - github.com/aws/aws-sdk-go-v2 v1.24.1 + github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2/config v1.25.11 github.com/aws/aws-sdk-go-v2/credentials v1.16.9 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.4 @@ -23,9 +23,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/route53resolver v1.25.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.47.2 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.25.2 + github.com/aws/aws-sdk-go-v2/service/ses v1.22.4 github.com/aws/aws-sdk-go-v2/service/ssm v1.44.2 github.com/aws/aws-sdk-go-v2/service/sts v1.26.2 - github.com/aws/smithy-go v1.19.0 + github.com/aws/smithy-go v1.20.2 github.com/cenkalti/backoff/v4 v4.2.1 github.com/fatih/color v1.13.0 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -49,8 +50,8 @@ require ( github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.8 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.3 // indirect diff --git a/v2/go.sum b/v2/go.sum index 5bb9b28f8..c4c62019f 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -34,8 +34,8 @@ github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/ github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3 h1:Zx9+31KyB8wQna6SXFWOewlgoY5uGdDAu6PTOEU3OQI= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3/go.mod h1:zxbEJhRdKTH1nqS2qu6UJ7zGe25xaHxZXaC2CvuQFnA= github.com/aws/aws-sdk-go-v2/config v1.25.11 h1:RWzp7jhPRliIcACefGkKp03L0Yofmd2p8M25kbiyvno= @@ -46,10 +46,10 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.9 h1:FZVFahMyZle6WcogZCOxo6D github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.9/go.mod h1:kjq7REMIkxdtcEC9/4BVXjOsNY5isz6jQbEgk6osRTU= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.4 h1:TUCNKBd4/JEefsZDxo5deRmrRRPZHqGyBYiUAeBKOWU= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.4/go.mod h1:egDkcl+zsgFqS6VO142bKboip5Pe1sNMwN55Xy38QsM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 h1:uR9lXYjdPX0xY+NhvaJ4dD8rpSRz5VY81ccIIoNG+lw= github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.8 h1:abKT+RuM1sdCNZIGIfZpLkvxEX3Rpsto019XG/rkYG8= @@ -84,6 +84,8 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.47.2 h1:DLSAG8zpJV2pYsU+UPkj1IEZghyBn github.com/aws/aws-sdk-go-v2/service/s3 v1.47.2/go.mod h1:thjZng67jGsvMyVZnSxlcqKyLwB0XTG8bHIRZPTJ+Bs= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.25.2 h1:JKbfiLwEqJp8zaOAOn6AVSMS96gdwP3TjBMvZYsbxqE= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.25.2/go.mod h1:pbBOMK8UicdDK11zsPSGbpFh9Xwbd1oD3t7pSxXgNxU= +github.com/aws/aws-sdk-go-v2/service/ses v1.22.4 h1:MNU3UWV47ylAAdlU+VxuyItYfuGGp00MvCBxdVAI3kM= +github.com/aws/aws-sdk-go-v2/service/ses v1.22.4/go.mod h1:M/ZQn5uXL4BP1qolIWrlN2SeoUFngJtU/oCwR4WOfZU= github.com/aws/aws-sdk-go-v2/service/ssm v1.44.2 h1:lmdmYCvG1EJKGLEsUsYDNO6MwZyBZROrRg04Vrb5TwA= github.com/aws/aws-sdk-go-v2/service/ssm v1.44.2/go.mod h1:pHJ1md/3F3WkYfZ4JKOllPfXQi4NiWk7NxbeOD53HQc= github.com/aws/aws-sdk-go-v2/service/sso v1.18.2 h1:xJPydhNm0Hiqct5TVKEuHG7weC0+sOs4MUnd7A5n5F4= @@ -92,8 +94,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.2 h1:8dU9zqA77C5egbU6yd4hFLai github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.2/go.mod h1:7Lt5mjQ8x5rVdKqg+sKKDeuwoszDJIIPmkd8BVsEdS0= github.com/aws/aws-sdk-go-v2/service/sts v1.26.2 h1:fFrLsy08wEbAisqW3KDl/cPHrF43GmV79zXB9EwJiZw= github.com/aws/aws-sdk-go-v2/service/sts v1.26.2/go.mod h1:7Ld9eTqocTvJqqJ5K/orbSDwmGcpRdlDiLjz2DO+SL8= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= From 1f0034b553fc78f5241cfd578dc07bf961c78675 Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Thu, 4 Apr 2024 11:36:12 +0200 Subject: [PATCH 3/6] Cosmetic code enhancements --- .../aws/discovery/ses-enumerate/main.go | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go index 1c0992fec..dfff980ed 100644 --- a/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go +++ b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go @@ -3,7 +3,9 @@ package aws import ( "context" _ "embed" + "fmt" "log" + "strings" "github.com/datadog/stratus-red-team/v2/internal/utils" "github.com/datadog/stratus-red-team/v2/pkg/stratus" @@ -18,27 +20,26 @@ var tf []byte func init() { stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ ID: "aws.discovery.ses-enumerate", - FriendlyName: "Enumeration of SES service", + FriendlyName: "Enumerate SES", Description: ` -Runs the following discovery commands on SES to enumerate email sending limits and identities, typically used for reconnaissance purposes before launching a phishing campaign: - - - ses:GetSendQuota - - ses:ListIdentities - -See: - -- https://www.invictus-ir.com/news/the-curious-case-of-dangerdev-protonmail-me -- https://docs.aws.amazon.com/ses/latest/APIReference/API_GetSendQuota.html -- https://docs.aws.amazon.com/ses/latest/APIReference/API_ListIdentities.html +Simulates an attacker enumerating SES. Attackers frequently use this enumeration technique after having compromised an access key, to use it to launch phishing campaigns or further resell stolen credentials. Warm-up: -- Create an IAM role with the AmazonSESReadOnlyAccess policy attached. The role can be assumed by any user in the AWS account of the caller. +- Create an IAM role with the AmazonSESReadOnlyAccess policy attached. The role can be assumed by any user in the AWS account of the caller. Detonation: -- Run ses:GetSendQuota API call -- Run ses:ListIdentities API call +- Assume the created IAM role +- Perform ses:GetSendQuota to discover the current [email sending quotas](https://docs.aws.amazon.com/ses/latest/APIReference/API_GetSendQuota.html). +- Perform ses:ListIdentities to discover the list of [verified identities](https://docs.aws.amazon.com/ses/latest/APIReference/API_ListIdentities.html) in the account. + +References: +- https://securitylabs.datadoghq.com/articles/following-attackers-trail-in-aws-methodology-findings-in-the-wild/#most-common-enumeration-techniques +- https://www.invictus-ir.com/news/ransomware-in-the-cloud +- https://unit42.paloaltonetworks.com/compromised-cloud-compute-credentials/#post-125981-_kdq0vw6banab +- https://permiso.io/blog/s/aws-ses-pionage-detecting-ses-abuse/ +- https://www.invictus-ir.com/news/the-curious-case-of-dangerdev-protonmail-me `, Detection: ` Through CloudTrail's GetSendQuota and ListIdentities events. @@ -58,26 +59,27 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error return err } sesClient := ses.NewFromConfig(awsConnection) - var maxItems int32 = 10 - listIdentitiesInput := ses.ListIdentitiesInput{ - MaxItems: &maxItems, - NextToken: nil, - } - identies, err := sesClient.ListIdentities(context.Background(), &listIdentitiesInput) + log.Println("Enumerating verified SES identities using ses:ListIdentities") + identities, err := sesClient.ListIdentities(context.Background(), &ses.ListIdentitiesInput{}) if err != nil { - return err + return fmt.Errorf("unable to list SES identities: %w", err) } - log.Println("ListIdentities output: ", identies.Identities) + if len(identities.Identities) == 0 { + log.Println("No verified SES identities found") + } else { + log.Printf("Found %d verified SES identities:", len(identities.Identities)) + log.Println("\n- " + strings.Join(identities.Identities, "\n- ")) + } + log.Println("Enumerating SES quotas") quotas, err := sesClient.GetSendQuota(context.Background(), &ses.GetSendQuotaInput{}) if err != nil { - return err + return fmt.Errorf("unable to get SES quotas: %w", err) } - log.Printf("GetSendQuota output, max24hoursend: %d, maxsendrate: %d, sentlast24hours: %d\n", - int(quotas.Max24HourSend), int(quotas.MaxSendRate), int(quotas.SentLast24Hours)) + log.Printf("Current quotas: max24hoursend: %d, maxsendrate: %d, sentlast24hours: %d\n", int(quotas.Max24HourSend), int(quotas.MaxSendRate), int(quotas.SentLast24Hours)) return nil } From 752e8f75a2ff2f48a8de5cc6f6dd703e355e5880 Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Thu, 4 Apr 2024 11:59:52 +0200 Subject: [PATCH 4/6] Don't use an additional IAM role, and enumerate additional things --- v2/go.mod | 1 + v2/go.sum | 2 + .../aws/discovery/ses-enumerate/main.go | 79 ++++++++++++------- .../aws/discovery/ses-enumerate/main.tf | 52 ------------ 4 files changed, 55 insertions(+), 79 deletions(-) delete mode 100644 v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.tf diff --git a/v2/go.mod b/v2/go.mod index 1474f0b38..e7cd1c76a 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -24,6 +24,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.47.2 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.25.2 github.com/aws/aws-sdk-go-v2/service/ses v1.22.4 + github.com/aws/aws-sdk-go-v2/service/sesv2 v1.27.3 github.com/aws/aws-sdk-go-v2/service/ssm v1.44.2 github.com/aws/aws-sdk-go-v2/service/sts v1.26.2 github.com/aws/smithy-go v1.20.2 diff --git a/v2/go.sum b/v2/go.sum index c4c62019f..815d27622 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -86,6 +86,8 @@ github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.25.2 h1:JKbfiLwEqJp8zaOAO github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.25.2/go.mod h1:pbBOMK8UicdDK11zsPSGbpFh9Xwbd1oD3t7pSxXgNxU= github.com/aws/aws-sdk-go-v2/service/ses v1.22.4 h1:MNU3UWV47ylAAdlU+VxuyItYfuGGp00MvCBxdVAI3kM= github.com/aws/aws-sdk-go-v2/service/ses v1.22.4/go.mod h1:M/ZQn5uXL4BP1qolIWrlN2SeoUFngJtU/oCwR4WOfZU= +github.com/aws/aws-sdk-go-v2/service/sesv2 v1.27.3 h1:8KP71cUPALMQxs8lhGiWcwdtqv1wsogigS7StDHq0IE= +github.com/aws/aws-sdk-go-v2/service/sesv2 v1.27.3/go.mod h1:WIpmp3q5Iw1AEhotd5OL03OFc0kOUoLPcqKFzcAOImU= github.com/aws/aws-sdk-go-v2/service/ssm v1.44.2 h1:lmdmYCvG1EJKGLEsUsYDNO6MwZyBZROrRg04Vrb5TwA= github.com/aws/aws-sdk-go-v2/service/ssm v1.44.2/go.mod h1:pHJ1md/3F3WkYfZ4JKOllPfXQi4NiWk7NxbeOD53HQc= github.com/aws/aws-sdk-go-v2/service/sso v1.18.2 h1:xJPydhNm0Hiqct5TVKEuHG7weC0+sOs4MUnd7A5n5F4= diff --git a/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go index dfff980ed..15fd06efd 100644 --- a/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go +++ b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go @@ -4,19 +4,14 @@ import ( "context" _ "embed" "fmt" - "log" - "strings" - - "github.com/datadog/stratus-red-team/v2/internal/utils" + "github.com/aws/aws-sdk-go-v2/service/sesv2" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" + "log" "github.com/aws/aws-sdk-go-v2/service/ses" ) -//go:embed main.tf -var tf []byte - func init() { stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ ID: "aws.discovery.ses-enumerate", @@ -24,15 +19,13 @@ func init() { Description: ` Simulates an attacker enumerating SES. Attackers frequently use this enumeration technique after having compromised an access key, to use it to launch phishing campaigns or further resell stolen credentials. -Warm-up: - -- Create an IAM role with the AmazonSESReadOnlyAccess policy attached. The role can be assumed by any user in the AWS account of the caller. +Warm-up: None. Detonation: - -- Assume the created IAM role +TODO - Perform ses:GetSendQuota to discover the current [email sending quotas](https://docs.aws.amazon.com/ses/latest/APIReference/API_GetSendQuota.html). -- Perform ses:ListIdentities to discover the list of [verified identities](https://docs.aws.amazon.com/ses/latest/APIReference/API_ListIdentities.html) in the account. +- Perform ses:ListIdentities to discover the list of [identities](https://docs.aws.amazon.com/ses/latest/APIReference/API_ListIdentities.html) in the account. +- When identities are found, use ses:GetIdentityVerificationAttributes to discover the [verification status](https://docs.aws.amazon.com/ses/latest/APIReference/API_GetIdentityVerificationAttributes.html) of each identity. References: - https://securitylabs.datadoghq.com/articles/following-attackers-trail-in-aws-methodology-findings-in-the-wild/#most-common-enumeration-techniques @@ -44,21 +37,45 @@ References: Detection: ` Through CloudTrail's GetSendQuota and ListIdentities events. `, - Platform: stratus.AWS, - IsIdempotent: true, - MitreAttackTactics: []mitreattack.Tactic{mitreattack.Discovery}, - PrerequisitesTerraformCode: tf, - Detonate: detonate, + Platform: stratus.AWS, + IsIdempotent: true, + MitreAttackTactics: []mitreattack.Tactic{mitreattack.Discovery}, + Detonate: detonate, }) } -func detonate(params map[string]string, providers stratus.CloudProviders) error { - roleArn := params["role_arn"] +func detonate(_ map[string]string, providers stratus.CloudProviders) error { awsConnection := providers.AWS().GetConnection() - if err := utils.WaitForAndAssumeAWSRole(&awsConnection, roleArn); err != nil { - return err - } sesClient := ses.NewFromConfig(awsConnection) + sesV2Client := sesv2.NewFromConfig(awsConnection) + + /*log.Println("Checking is SES e-mail sending is enabled in the current region") + result, err := sesClient.GetAccountSendingEnabled(context.Background(), &ses.GetAccountSendingEnabledInput{}) + if err != nil { + return fmt.Errorf("unable to check if SES sending is enabled: %w", err) + } + if result.Enabled { + log.Println("SES e-mail sending is enabled") + } else { + log.Println("SES e-mail sending is disabled") + }*/ + + log.Println("Checking if SES e-mail sending is enabled in the current region, and quotas in place") + account, err := sesV2Client.GetAccount(context.Background(), &sesv2.GetAccountInput{}) + if err != nil { + return fmt.Errorf("unable to get SES account details: %w", err) + } + if account.SendingEnabled { + log.Println("SES e-mail sending is enabled in the current region") + } else { + log.Println("SES e-mail sending is disabled in the current region") + } + if account.ProductionAccessEnabled { + log.Println("SES is in production mode!") + } else { + log.Println("SES is in sandbox mode") + } + log.Printf("SES quotas: max24hoursend: %d, maxsendrate: %d, sentlast24hours: %d\n", int(account.SendQuota.Max24HourSend), int(account.SendQuota.MaxSendRate), int(account.SendQuota.SentLast24Hours)) log.Println("Enumerating verified SES identities using ses:ListIdentities") identities, err := sesClient.ListIdentities(context.Background(), &ses.ListIdentitiesInput{}) @@ -69,17 +86,25 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error if len(identities.Identities) == 0 { log.Println("No verified SES identities found") } else { - log.Printf("Found %d verified SES identities:", len(identities.Identities)) - log.Println("\n- " + strings.Join(identities.Identities, "\n- ")) + log.Printf("Found %d verified SES identities", len(identities.Identities)) + verificationAttributes, err := sesClient.GetIdentityVerificationAttributes(context.Background(), &ses.GetIdentityVerificationAttributesInput{ + Identities: identities.Identities, + }) + if err != nil { + return fmt.Errorf("unable to get identity verification attributes: %w", err) + } + for identity := range verificationAttributes.VerificationAttributes { + log.Printf("- Identity %s has verification status '%s'", identity, verificationAttributes.VerificationAttributes[identity].VerificationStatus) + } } - log.Println("Enumerating SES quotas") + /*log.Println("Enumerating SES quotas") quotas, err := sesClient.GetSendQuota(context.Background(), &ses.GetSendQuotaInput{}) if err != nil { return fmt.Errorf("unable to get SES quotas: %w", err) } - log.Printf("Current quotas: max24hoursend: %d, maxsendrate: %d, sentlast24hours: %d\n", int(quotas.Max24HourSend), int(quotas.MaxSendRate), int(quotas.SentLast24Hours)) + log.Printf("Current quotas: max24hoursend: %d, maxsendrate: %d, sentlast24hours: %d\n", int(quotas.Max24HourSend), int(quotas.MaxSendRate), int(quotas.SentLast24Hours))*/ return nil } diff --git a/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.tf b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.tf deleted file mode 100644 index 5f515d953..000000000 --- a/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.tf +++ /dev/null @@ -1,52 +0,0 @@ -terraform { - required_providers { - aws = { - source = "hashicorp/aws" - version = "~> 3.0" - } - } -} - -provider "aws" { - skip_region_validation = true - skip_credentials_validation = true - default_tags { - tags = { - StratusRedTeam = true - } - } -} - -locals { - resource_prefix = "stratus-red-team-ses-enumerate" -} - -data "aws_caller_identity" "current" {} - -resource "aws_iam_role" "ses_enumerate_role" { - name = "${local.resource_prefix}-role" - path = "/" - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = ["sts:AssumeRole", "sts:SetSourceIdentity"] - Effect = "Allow" - Sid = "" - Principal = { - AWS = data.aws_caller_identity.current.account_id - } - }, - ] - }) -} - -resource "aws_iam_role_policy_attachment" "rolepolicy" { - role = aws_iam_role.ses_enumerate_role.name - policy_arn = "arn:aws:iam::aws:policy/AmazonSESReadOnlyAccess" -} - -output "role_arn" { - value = aws_iam_role.ses_enumerate_role.arn -} From 3808483f8bfa5f76fa88cd8a16e212b86f0f0ef2 Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Thu, 4 Apr 2024 12:18:22 +0200 Subject: [PATCH 5/6] Only use SES (not SESv2) API calls following recommendation from an IR practitioner --- .../aws/discovery/ses-enumerate/main.go | 30 ++++--------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go index 15fd06efd..76314a8c2 100644 --- a/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go +++ b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go @@ -4,7 +4,6 @@ import ( "context" _ "embed" "fmt" - "github.com/aws/aws-sdk-go-v2/service/sesv2" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -22,7 +21,7 @@ Simulates an attacker enumerating SES. Attackers frequently use this enumeration Warm-up: None. Detonation: -TODO +- Perform ses:GetAccountSendingEnabled to check if SES sending is enabled. - Perform ses:GetSendQuota to discover the current [email sending quotas](https://docs.aws.amazon.com/ses/latest/APIReference/API_GetSendQuota.html). - Perform ses:ListIdentities to discover the list of [identities](https://docs.aws.amazon.com/ses/latest/APIReference/API_ListIdentities.html) in the account. - When identities are found, use ses:GetIdentityVerificationAttributes to discover the [verification status](https://docs.aws.amazon.com/ses/latest/APIReference/API_GetIdentityVerificationAttributes.html) of each identity. @@ -35,7 +34,8 @@ References: - https://www.invictus-ir.com/news/the-curious-case-of-dangerdev-protonmail-me `, Detection: ` -Through CloudTrail's GetSendQuota and ListIdentities events. +Through CloudTrail's GetAccountSendingEnabled, GetSendQuota and ListIdentities events. +These can be considered suspicious especially when performed by a long-lived access key, or when the calls span across multiple regions. `, Platform: stratus.AWS, IsIdempotent: true, @@ -47,9 +47,8 @@ Through CloudTrail's GetSendQuota and ListIdentities e func detonate(_ map[string]string, providers stratus.CloudProviders) error { awsConnection := providers.AWS().GetConnection() sesClient := ses.NewFromConfig(awsConnection) - sesV2Client := sesv2.NewFromConfig(awsConnection) - /*log.Println("Checking is SES e-mail sending is enabled in the current region") + log.Println("Checking is SES e-mail sending is enabled in the current region") result, err := sesClient.GetAccountSendingEnabled(context.Background(), &ses.GetAccountSendingEnabledInput{}) if err != nil { return fmt.Errorf("unable to check if SES sending is enabled: %w", err) @@ -58,24 +57,7 @@ func detonate(_ map[string]string, providers stratus.CloudProviders) error { log.Println("SES e-mail sending is enabled") } else { log.Println("SES e-mail sending is disabled") - }*/ - - log.Println("Checking if SES e-mail sending is enabled in the current region, and quotas in place") - account, err := sesV2Client.GetAccount(context.Background(), &sesv2.GetAccountInput{}) - if err != nil { - return fmt.Errorf("unable to get SES account details: %w", err) - } - if account.SendingEnabled { - log.Println("SES e-mail sending is enabled in the current region") - } else { - log.Println("SES e-mail sending is disabled in the current region") - } - if account.ProductionAccessEnabled { - log.Println("SES is in production mode!") - } else { - log.Println("SES is in sandbox mode") } - log.Printf("SES quotas: max24hoursend: %d, maxsendrate: %d, sentlast24hours: %d\n", int(account.SendQuota.Max24HourSend), int(account.SendQuota.MaxSendRate), int(account.SendQuota.SentLast24Hours)) log.Println("Enumerating verified SES identities using ses:ListIdentities") identities, err := sesClient.ListIdentities(context.Background(), &ses.ListIdentitiesInput{}) @@ -98,13 +80,13 @@ func detonate(_ map[string]string, providers stratus.CloudProviders) error { } } - /*log.Println("Enumerating SES quotas") + log.Println("Enumerating SES quotas") quotas, err := sesClient.GetSendQuota(context.Background(), &ses.GetSendQuotaInput{}) if err != nil { return fmt.Errorf("unable to get SES quotas: %w", err) } - log.Printf("Current quotas: max24hoursend: %d, maxsendrate: %d, sentlast24hours: %d\n", int(quotas.Max24HourSend), int(quotas.MaxSendRate), int(quotas.SentLast24Hours))*/ + log.Printf("Current quotas: max24hoursend: %d, maxsendrate: %d, sentlast24hours: %d\n", int(quotas.Max24HourSend), int(quotas.MaxSendRate), int(quotas.SentLast24Hours)) return nil } From 0fa525234238246404db5a34a2921a2a7e9a3298 Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Thu, 4 Apr 2024 12:21:13 +0200 Subject: [PATCH 6/6] Generate docs --- .../AWS/aws.discovery.ses-enumerate.md | 51 +++++++++++++++++++ docs/attack-techniques/AWS/index.md | 2 + docs/attack-techniques/list.md | 3 +- docs/index.yaml | 7 +++ .../aws/discovery/ses-enumerate/main.go | 4 +- 5 files changed, 65 insertions(+), 2 deletions(-) create mode 100755 docs/attack-techniques/AWS/aws.discovery.ses-enumerate.md diff --git a/docs/attack-techniques/AWS/aws.discovery.ses-enumerate.md b/docs/attack-techniques/AWS/aws.discovery.ses-enumerate.md new file mode 100755 index 000000000..40260c98a --- /dev/null +++ b/docs/attack-techniques/AWS/aws.discovery.ses-enumerate.md @@ -0,0 +1,51 @@ +--- +title: Enumerate SES +--- + +# Enumerate SES + + + idempotent + +Platform: AWS + +## MITRE ATT&CK Tactics + + +- Discovery + +## Description + + +Simulates an attacker enumerating SES. Attackers frequently use this enumeration technique after having compromised an access key, to use it to launch phishing campaigns or further resell stolen credentials. + +Warm-up: None. + +Detonation: + +- Perform ses:GetAccountSendingEnabled to check if SES sending is enabled. +- Perform ses:GetSendQuota to discover the current [email sending quotas](https://docs.aws.amazon.com/ses/latest/APIReference/API_GetSendQuota.html). +- Perform ses:ListIdentities to discover the list of [identities](https://docs.aws.amazon.com/ses/latest/APIReference/API_ListIdentities.html) in the account. +- If identities are found, use ses:GetIdentityVerificationAttributes (only once) to discover [verification status](https://docs.aws.amazon.com/ses/latest/APIReference/API_GetIdentityVerificationAttributes.html) of each identity. + +References: + +- https://securitylabs.datadoghq.com/articles/following-attackers-trail-in-aws-methodology-findings-in-the-wild/#most-common-enumeration-techniques +- https://www.invictus-ir.com/news/ransomware-in-the-cloud +- https://unit42.paloaltonetworks.com/compromised-cloud-compute-credentials/#post-125981-_kdq0vw6banab +- https://permiso.io/blog/s/aws-ses-pionage-detecting-ses-abuse/ +- https://www.invictus-ir.com/news/the-curious-case-of-dangerdev-protonmail-me + + +## Instructions + +```bash title="Detonate with Stratus Red Team" +stratus detonate aws.discovery.ses-enumerate +``` +## Detection + + +Through CloudTrail's GetAccountSendingEnabled, GetSendQuota and ListIdentities events. +These can be considered suspicious especially when performed by a long-lived access key, or when the calls span across multiple regions. + + diff --git a/docs/attack-techniques/AWS/index.md b/docs/attack-techniques/AWS/index.md index 47a7741c0..9d964d3cc 100755 --- a/docs/attack-techniques/AWS/index.md +++ b/docs/attack-techniques/AWS/index.md @@ -40,6 +40,8 @@ Note that some Stratus attack techniques may correspond to more than a single AT - [Download EC2 Instance User Data](./aws.discovery.ec2-download-user-data.md) +- [Enumerate SES](./aws.discovery.ses-enumerate.md) + ## Execution diff --git a/docs/attack-techniques/list.md b/docs/attack-techniques/list.md index 8ff5a5ba3..0da70d670 100755 --- a/docs/attack-techniques/list.md +++ b/docs/attack-techniques/list.md @@ -23,6 +23,7 @@ This page contains the list of all Stratus Attack Techniques. | [Remove VPC Flow Logs](./AWS/aws.defense-evasion.vpc-remove-flow-logs.md) | [AWS](./AWS/index.md) | Defense Evasion | | [Execute Discovery Commands on an EC2 Instance](./AWS/aws.discovery.ec2-enumerate-from-instance.md) | [AWS](./AWS/index.md) | Discovery | | [Download EC2 Instance User Data](./AWS/aws.discovery.ec2-download-user-data.md) | [AWS](./AWS/index.md) | Discovery | +| [Enumerate SES](./AWS/aws.discovery.ses-enumerate.md) | [AWS](./AWS/index.md) | Discovery | | [Launch Unusual EC2 instances](./AWS/aws.execution.ec2-launch-unusual-instances.md) | [AWS](./AWS/index.md) | Execution | | [Execute Commands on EC2 Instance via User Data](./AWS/aws.execution.ec2-user-data.md) | [AWS](./AWS/index.md) | Execution, Privilege Escalation | | [Usage of ssm:SendCommand on multiple instances](./AWS/aws.execution.ssm-send-command.md) | [AWS](./AWS/index.md) | Execution | @@ -56,7 +57,6 @@ This page contains the list of all Stratus Attack Techniques. | [Create an Admin GCP Service Account](./GCP/gcp.persistence.create-admin-service-account.md) | [GCP](./GCP/index.md) | Persistence, Privilege Escalation | | [Create a GCP Service Account Key](./GCP/gcp.persistence.create-service-account-key.md) | [GCP](./GCP/index.md) | Persistence, Privilege Escalation | | [Invite an External User to a GCP Project](./GCP/gcp.persistence.invite-external-user.md) | [GCP](./GCP/index.md) | Persistence | -| [Impersonate GCP Service Accounts](./GCP/gcp.privilege-escalation.impersonate-service-accounts.md) | [GCP](./GCP/index.md) | Privilege Escalation | | [Dump All Secrets](./kubernetes/k8s.credential-access.dump-secrets.md) | [Kubernetes](./kubernetes/index.md) | Credential Access | | [Steal Pod Service Account Token](./kubernetes/k8s.credential-access.steal-serviceaccount-token.md) | [Kubernetes](./kubernetes/index.md) | Credential Access | | [Create Admin ClusterRole](./kubernetes/k8s.persistence.create-admin-clusterrole.md) | [Kubernetes](./kubernetes/index.md) | Persistence, Privilege Escalation | @@ -65,3 +65,4 @@ This page contains the list of all Stratus Attack Techniques. | [Container breakout via hostPath volume mount](./kubernetes/k8s.privilege-escalation.hostpath-volume.md) | [Kubernetes](./kubernetes/index.md) | Privilege Escalation | | [Privilege escalation through node/proxy permissions](./kubernetes/k8s.privilege-escalation.nodes-proxy.md) | [Kubernetes](./kubernetes/index.md) | Privilege Escalation | | [Run a Privileged Pod](./kubernetes/k8s.privilege-escalation.privileged-pod.md) | [Kubernetes](./kubernetes/index.md) | Privilege Escalation | +| [Impersonate GCP Service Accounts](./GCP/gcp.privilege-escalation.impersonate-service-accounts.md) | [GCP](./GCP/index.md) | Privilege Escalation | diff --git a/docs/index.yaml b/docs/index.yaml index edde566c0..cdb2d5a34 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -100,6 +100,13 @@ AWS: - Discovery platform: AWS isIdempotent: true + - id: aws.discovery.ses-enumerate + name: Enumerate SES + isSlow: false + mitreAttackTactics: + - Discovery + platform: AWS + isIdempotent: true Execution: - id: aws.execution.ec2-launch-unusual-instances name: Launch Unusual EC2 instances diff --git a/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go index 76314a8c2..ac1ea9242 100644 --- a/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go +++ b/v2/internal/attacktechniques/aws/discovery/ses-enumerate/main.go @@ -21,12 +21,14 @@ Simulates an attacker enumerating SES. Attackers frequently use this enumeration Warm-up: None. Detonation: + - Perform ses:GetAccountSendingEnabled to check if SES sending is enabled. - Perform ses:GetSendQuota to discover the current [email sending quotas](https://docs.aws.amazon.com/ses/latest/APIReference/API_GetSendQuota.html). - Perform ses:ListIdentities to discover the list of [identities](https://docs.aws.amazon.com/ses/latest/APIReference/API_ListIdentities.html) in the account. -- When identities are found, use ses:GetIdentityVerificationAttributes to discover the [verification status](https://docs.aws.amazon.com/ses/latest/APIReference/API_GetIdentityVerificationAttributes.html) of each identity. +- If identities are found, use ses:GetIdentityVerificationAttributes (only once) to discover [verification status](https://docs.aws.amazon.com/ses/latest/APIReference/API_GetIdentityVerificationAttributes.html) of each identity. References: + - https://securitylabs.datadoghq.com/articles/following-attackers-trail-in-aws-methodology-findings-in-the-wild/#most-common-enumeration-techniques - https://www.invictus-ir.com/news/ransomware-in-the-cloud - https://unit42.paloaltonetworks.com/compromised-cloud-compute-credentials/#post-125981-_kdq0vw6banab