diff --git a/docs/attack-techniques/GCP/gcp.exfiltration.sql-export-bucket.md b/docs/attack-techniques/GCP/gcp.exfiltration.sql-export-bucket.md new file mode 100644 index 000000000..824a7c4f3 --- /dev/null +++ b/docs/attack-techniques/GCP/gcp.exfiltration.sql-export-bucket.md @@ -0,0 +1,120 @@ +--- +title: Exporting Cloud SQL database to Storage bucket +--- + +# Exporting Cloud SQL database to Storage bucket + +slow + +idempotent + +Platform: GCP + +## MITRE ATT&CK Tactics + + +- Exfiltration + +## Description + + +Exfiltrates data from a Cloud SQL database by exporting to internal storage bucket. + +Warm-up: + +- Create a Cloud SQL instance +- Create a storage bucket and grant objectAdmin to Cloud SQL instance +- Populate the database + +Detonation: + +- Export the database into the storage bucket + +!!! info + + Provisioning the Cloud SQL requires a few minutes. + +Reference: + +- https://cloud.google.com/sdk/gcloud/reference/sql/export/sql +- https://cloud.hacktricks.wiki/en/pentesting-cloud/gcp-security/gcp-post-exploitation/gcp-cloud-sql-post-exploitation.html + + + +## Instructions + +```bash title="Detonate with Stratus Red Team" +stratus detonate gcp.exfiltration.sql-export-bucket +``` + + +## Detection + +Exporting the database is detected as 'cloudsql.instances.export' in Cloud Logging. + +Data Access logging for Cloud SQL instance is disabled by default, thus we need to enable it (if not enabled). + +- Go to "IAM & Admin" -> "Audit Logs" +- Locate "Cloud SQL" +- on "Permission Types", check the "Admin read" + +You can use following query to filter the events: + +``` +resource.type="cloudsql_database" +protoPayload.serviceName="cloudsql.googleapis.com" +protoPayload.methodName="cloudsql.instances.export" +``` + +Sample event (shortened for readability): + +```json +{ + "logName": "projects/my-project-id/logs/cloudaudit.googleapis.com%2Factivity", + "protoPayload": { + "authenticationInfo": { + "principalEmail": "username@service.com", + }, + "methodName": "cloudsql.instances.export", + "request": { + @type: "type.googleapis.com/google.cloud.sql.v1.SqlInstancesExportRequest", + "body": { + "exportContext": { + "databases": [ + "stratus-db" + ], + "fileType": "SQL", + "uri": "gs://my-bucket-id/dump.sql.gz" + } + }, + "instance": "my-cloudsql-instance-id", + } + "resourceName": "projects/my-project-id/instances/my-cloudsql-instance-id", + "serviceName": "cloudsql.googleapis.com", + }, + "resource": { + "type": "cloudsql_database" + }, + "severity": "INFO" +} +``` + +subsequently, detect the 'storage.objects.create' event for creating the object on bucket. + +```json +{ + "logName": "projects/my-project-id/logs/cloudaudit.googleapis.com%2Factivity", + "protoPayload": { + "authenticationInfo": { + "principalEmail": "username@service.com", + }, + "methodName": "storage.objects.create", + "resourceName": "projects/_/buckets/my-bucket-id/objects/dump.sql.gz", + "serviceName": "cloudsql.googleapis.com", + }, + "resource": { + "type": "gcs_bucket" + }, + "severity": "INFO" +} +``` \ No newline at end of file diff --git a/docs/attack-techniques/GCP/index.md b/docs/attack-techniques/GCP/index.md index ed24cbaa2..3a736041f 100755 --- a/docs/attack-techniques/GCP/index.md +++ b/docs/attack-techniques/GCP/index.md @@ -17,6 +17,8 @@ Note that some Stratus attack techniques may correspond to more than a single AT - [Exfiltrate Compute Disk by sharing a snapshot](./gcp.exfiltration.share-compute-snapshot.md) +- [Exporting Cloud SQL database to Storage bucket](./gcp.exfiltration.sql-export-bucket.md) + ## Persistence diff --git a/docs/attack-techniques/list.md b/docs/attack-techniques/list.md index 589adb942..0f2c321e3 100755 --- a/docs/attack-techniques/list.md +++ b/docs/attack-techniques/list.md @@ -67,6 +67,7 @@ This page contains the list of all Stratus Attack Techniques. | [Exfiltrate Compute Disk by sharing it](./GCP/gcp.exfiltration.share-compute-disk.md) | [GCP](./GCP/index.md) | Exfiltration | | [Exfiltrate Compute Image by sharing it](./GCP/gcp.exfiltration.share-compute-image.md) | [GCP](./GCP/index.md) | Exfiltration | | [Exfiltrate Compute Disk by sharing a snapshot](./GCP/gcp.exfiltration.share-compute-snapshot.md) | [GCP](./GCP/index.md) | Exfiltration | +| [Exporting Cloud SQL database to Storage bucket](./GCP/gcp.exfiltration.sql-export-bucket.md) | [GCP](./GCP/index.md) | Exfiltration | | [Backdoor a GCP Service Account through its IAM Policy](./GCP/gcp.persistence.backdoor-service-account-policy.md) | [GCP](./GCP/index.md) | Persistence | | [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 | diff --git a/docs/index.yaml b/docs/index.yaml index 742095b6d..98ad2b0e0 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -415,6 +415,13 @@ GCP: - Exfiltration platform: GCP isIdempotent: true + - id: gcp.exfiltration.sql-export-bucket + name: Exporting Cloud SQL database to Storage bucket + isSlow: true + mitreAttackTactics: + - Exfiltration + platform: GCP + isIdempotent: true Persistence: - id: gcp.persistence.backdoor-service-account-policy name: Backdoor a GCP Service Account through its IAM Policy diff --git a/v2/internal/attacktechniques/gcp/exfiltration/sql-export-bucket/main.go b/v2/internal/attacktechniques/gcp/exfiltration/sql-export-bucket/main.go new file mode 100644 index 000000000..44eb5b00c --- /dev/null +++ b/v2/internal/attacktechniques/gcp/exfiltration/sql-export-bucket/main.go @@ -0,0 +1,172 @@ +package gcp + +import ( + "context" + _ "embed" + "fmt" + "log" + "time" + "google.golang.org/api/sqladmin/v1" + "github.com/datadog/stratus-red-team/v2/pkg/stratus" + "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" +) + +//go:embed main.tf +var tf []byte + +func init() { + const CodeBlock = "```" + const AttackTechniqueId = "gcp.exfiltration.sql-export-bucket" + + stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ + ID: AttackTechniqueId, + FriendlyName: "Exporting Cloud SQL database to Storage bucket", + Description: ` +Exfiltrates data from a Cloud SQL database by exporting to internal storage bucket. + +Warm-up: + +- Create a Cloud SQL instance +- Create a storage bucket and grant objectAdmin to Cloud SQL instance +- Populate the database + +Detonation: + +- Export the database into the storage bucket + +!!! info + + Provisioning the Cloud SQL requires a few minutes. + +Reference: + +- https://cloud.google.com/sdk/gcloud/reference/sql/export/sql +- https://cloud.hacktricks.wiki/en/pentesting-cloud/gcp-security/gcp-post-exploitation/gcp-cloud-sql-post-exploitation.html +`, + Detection: ` +Exporting the database is detected as 'cloudsql.instances.export' in Cloud Logging. + +Data Access logging for Cloud SQL instance is disabled by default, thus we need to enable it (if not enabled). + +- Go to "IAM & Admin" -> "Audit Logs" +- Locate "Cloud SQL" +- on "Permission Types", check the "Admin read" + +You can use following query to filter the events: + +` + CodeBlock + ` +resource.type="cloudsql_database" +protoPayload.serviceName="cloudsql.googleapis.com" +protoPayload.methodName="cloudsql.instances.export" +` + CodeBlock + ` + +Sample event (shortened for readability): + +` + CodeBlock + `json +{ + "logName": "projects/my-project-id/logs/cloudaudit.googleapis.com%2Factivity", + "protoPayload": { + "authenticationInfo": { + "principalEmail": "username@service.com", + }, + "methodName": "cloudsql.instances.export", + "request": { + @type: "type.googleapis.com/google.cloud.sql.v1.SqlInstancesExportRequest", + "body": { + "exportContext": { + "databases": [ + "stratus-db" + ], + "fileType": "SQL", + "uri": "gs://my-bucket-id/dump.sql.gz" + } + }, + "instance": "my-cloudsql-instance-id", + } + "resourceName": "projects/my-project-id/instances/my-cloudsql-instance-id", + "serviceName": "cloudsql.googleapis.com", + }, + "resource": { + "type": "cloudsql_database" + }, + "severity": "INFO" +} +` + CodeBlock + ` + +subsequently, detect the 'storage.objects.create' event for creating the object on bucket. + +` + CodeBlock + `json +{ + "logName": "projects/my-project-id/logs/cloudaudit.googleapis.com%2Factivity", + "protoPayload": { + "authenticationInfo": { + "principalEmail": "username@service.com", + }, + "methodName": "storage.objects.create", + "resourceName": "projects/_/buckets/my-bucket-id/objects/dump.sql.gz", + "serviceName": "cloudsql.googleapis.com", + }, + "resource": { + "type": "gcs_bucket" + }, + "severity": "INFO" +} +` + CodeBlock + ` +`, + Platform: stratus.GCP, + IsIdempotent: true, + MitreAttackTactics: []mitreattack.Tactic{ mitreattack.Exfiltration }, + PrerequisitesTerraformCode: tf, + Detonate: detonate, + }) +} + +func detonate(params map[string]string, providers stratus.CloudProviders) error { + gcp := providers.GCP() + ctx := context.Background() + + projectId := gcp.GetProjectId() + bucketName := params["bucket_name"] + sqlInstance := params["sql_instance"] + + // create service for API communication + service, err := sqladmin.NewService(ctx, gcp.Options()) + if err != nil { + return fmt.Errorf("Failed to create new service: %v", err) + } + + // export the database + req := &sqladmin.InstancesExportRequest { + ExportContext: &sqladmin.ExportContext{ + Databases: []string{"stratus-db"}, + FileType: "SQL", + Uri: fmt.Sprintf("gs://%s/dump.sql.gz", bucketName), + }, + } + + if op, err := service.Instances.Export(projectId, sqlInstance, req).Do(); err != nil { + return fmt.Errorf("Failed to export database: %v", err) + } + + // wait for the export operation to complete + for { + op, err := service.Operations.Get(projectId, op.Name).Do() + if err != nil { + return fmt.Errorf("Failed to get operation status: %v", err) + } + + if op.Status == "DONE" { + if op.Error != nil { + return fmt.Errorf("Export operation failed: %v", op.Error.Errors) + } + break + } + + log.Println("Exporting in progress... waiting") + time.Sleep(10 * time.Second) + } + + log.Println("Database has been exported to the bucket") + + return nil +} \ No newline at end of file diff --git a/v2/internal/attacktechniques/gcp/exfiltration/sql-export-bucket/main.tf b/v2/internal/attacktechniques/gcp/exfiltration/sql-export-bucket/main.tf new file mode 100644 index 000000000..dcc4e6790 --- /dev/null +++ b/v2/internal/attacktechniques/gcp/exfiltration/sql-export-bucket/main.tf @@ -0,0 +1,107 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 6.18.1" + } + httpclient = { + source = "dmachard/http-client" + version = "0.0.3" + } + } +} + +provider "google" { + default_labels = { + stratus-red-team = "true" + } +} + +locals { + resource_prefix = "stratus-red-team-sep" + region = "us-east1" + instance_tier = "db-f1-micro" + default_password = "StratusRedTeam" +} + +resource "random_string" "suffix" { + special = false + length = 16 + min_lower = 16 +} + +data "google_client_config" "current" { } + +resource "google_sql_database_instance" "instance" { + name = "${local.resource_prefix}-sql-${random_string.suffix.result}" + + database_version = "MYSQL_5_7" + region = local.region + + settings { + tier = local.instance_tier + } + + deletion_protection = false +} + +resource "google_sql_database" "database" { + name = "stratus-db" + + instance = google_sql_database_instance.instance.name + charset = "utf8" + collation = "utf8_general_ci" +} + +resource "google_sql_user" "user" { + name = "root" + instance = google_sql_database_instance.instance.name + host = "%" + password = local.default_password +} + +resource "google_storage_bucket" "bucket" { + name = "${local.resource_prefix}-bucket-${random_string.suffix.result}" + + location = local.region + storage_class = "STANDARD" + force_destroy = true + uniform_bucket_level_access = true +} + +resource "google_storage_bucket_iam_member" "importer" { + bucket = google_storage_bucket.bucket.name + role = "roles/storage.objectAdmin" + member = "serviceAccount:${google_sql_database_instance.instance.service_account_email_address}" +} + +resource "google_storage_bucket_object" "sql_file" { + name = "init.sql" + bucket = google_storage_bucket.bucket.id + content = base64decode("Q1JFQVRFIFRBQkxFIElGIE5PVCBFWElTVFMgdXNlcnMgKAogICAgYGlkYCBJTlQoMTApIE5PVCBOVUxMIEFVVE9fSU5DUkVNRU5ULAogICAgYHVzZXJuYW1lYCBWQVJDSEFSKDI1NSkgTk9UIE5VTEwsCiAgICBgcGFzc3dvcmRgIFZBUkNIQVIoMjU1KSBOT1QgTlVMTCwKICAgIFBSSU1BUlkgS0VZIGBpZGAgKGBpZGApCik7CklOU0VSVCBJTlRPIGB1c2Vyc2AoYHVzZXJuYW1lYCxgcGFzc3dvcmRgKSBWQUxVRVMoInNhdHJpYUB0aGlzY29tcGFueS5pZCIsTUQ1KCdQQHNzdzByZCcpKTsKSU5TRVJUIElOVE8gYHVzZXJzYChgdXNlcm5hbWVgLGBwYXNzd29yZGApIFZBTFVFUygiYWR5QHRoaXNjb21wYW55LmlkIixNRDUoJ1BAc3N3MHJkJykpOwpJTlNFUlQgSU5UTyBgdXNlcnNgKGB1c2VybmFtZWAsYHBhc3N3b3JkYCkgVkFMVUVTKCJwcmFkYW5hQHRoaXNjb21wYW55LmlkIixNRDUoJ1BAc3N3MHJkJykpOwo=") + content_type = "text/plain" +} + +data "httpclient_request" "req" { + url = "https://sqladmin.googleapis.com/v1/projects/${data.google_client_config.current.project}/instances/${google_sql_database_instance.instance.name}/import" + request_method = "POST" + request_headers = { + Content-Type: "application/json; charset=utf-8", + Authorization: "Bearer ${data.google_client_config.current.access_token}", + } + request_body = "{'importContext':{'fileType':'SQL','uri':'gs://${google_storage_bucket.bucket.id}/init.sql','database':'stratus-db'}}" + + depends_on = [google_storage_bucket_iam_member.importer] +} + +output "bucket_name" { + value = google_storage_bucket.bucket.name +} + +output "sql_instance" { + value = google_sql_database_instance.instance.name +} + +output "display" { + value = format("Cloud SQL '%s' ready (%s)", google_sql_database_instance.instance.name, data.httpclient_request.req.response_code) +} \ No newline at end of file diff --git a/v2/internal/attacktechniques/main.go b/v2/internal/attacktechniques/main.go index c66c255c9..68bcfe7bb 100644 --- a/v2/internal/attacktechniques/main.go +++ b/v2/internal/attacktechniques/main.go @@ -58,6 +58,7 @@ import ( _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/exfiltration/share-compute-disk" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/exfiltration/share-compute-image" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/exfiltration/share-compute-snapshot" + _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/exfiltration/sql-export-bucket" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/persistence/backdoor-service-account-policy" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/persistence/create-admin-service-account" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/persistence/create-service-account-key"