From e68dabf1df76fbd0d069512e3286a26e937330c9 Mon Sep 17 00:00:00 2001 From: Evan Gibler <20933572+egibs@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:10:45 -0500 Subject: [PATCH] Add initial Correlation Rules (#1260) * GitHub advanced security change not followed by repo archived * StopInstance FOLLOWED BY ModifyInstanceAttributes (correlation rule) * GCP run.services.create Privilege Escalation - correlation rule * GCP run.services.create Privilege Escalation - linter fix * Add gcp_cloud_run_service_created.py changes Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> * updated to follow CR style guide/best practices * instance_ids as lists for comparison * added unit tests to correlation rules * updated packs --------- Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> Co-authored-by: akozlovets098 Co-authored-by: Ariel Ropek Co-authored-by: Ben Airey Co-authored-by: Ariel Ropek <79653153+arielkr256@users.noreply.github.com> --- ...e_followed_by_modifyinstanceattributes.yml | 48 ++++ ...vice_create_followed_by_set_iam_policy.yml | 72 ++++++ ...y_change_not_followed_by_repo_archived.yml | 59 +++++ packs/aws.yml | 2 + packs/gcp_audit.yml | 3 + packs/github.yml | 1 + .../aws_ec2_startup_script_change.py | 4 +- .../aws_ec2_stopinstances.py | 29 +++ .../aws_ec2_stopinstances.yml | 238 ++++++++++++++++++ .../gcp_cloud_run_service_created.py | 43 ++++ .../gcp_cloud_run_service_created.yml | 202 +++++++++++++++ .../gcp_cloud_run_set_iam_policy.py | 45 ++++ .../gcp_cloud_run_set_iam_policy.yml | 154 ++++++++++++ .../github_advanced_security_change.yml | 1 + rules/github_rules/github_repo_archived.yml | 62 +++++ 15 files changed, 962 insertions(+), 1 deletion(-) create mode 100644 correlation_rules/aws_cloudtrail_stopinstance_followed_by_modifyinstanceattributes.yml create mode 100644 correlation_rules/gcp_cloud_run_service_create_followed_by_set_iam_policy.yml create mode 100644 correlation_rules/github_advanced_security_change_not_followed_by_repo_archived.yml create mode 100644 rules/aws_cloudtrail_rules/aws_ec2_stopinstances.py create mode 100644 rules/aws_cloudtrail_rules/aws_ec2_stopinstances.yml create mode 100644 rules/gcp_audit_rules/gcp_cloud_run_service_created.py create mode 100644 rules/gcp_audit_rules/gcp_cloud_run_service_created.yml create mode 100644 rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.py create mode 100644 rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.yml create mode 100644 rules/github_rules/github_repo_archived.yml diff --git a/correlation_rules/aws_cloudtrail_stopinstance_followed_by_modifyinstanceattributes.yml b/correlation_rules/aws_cloudtrail_stopinstance_followed_by_modifyinstanceattributes.yml new file mode 100644 index 000000000..c60c8ee75 --- /dev/null +++ b/correlation_rules/aws_cloudtrail_stopinstance_followed_by_modifyinstanceattributes.yml @@ -0,0 +1,48 @@ +AnalysisType: correlation_rule +RuleID: "AWS.EC2.StopInstance.FOLLOWED.BY.ModifyInstanceAttributes" +DisplayName: "StopInstance FOLLOWED BY ModifyInstanceAttributes" +Enabled: true +Severity: High +Description: Identifies when StopInstance and ModifyInstanceAttributes CloudTrail events occur in a short period of time. Since EC2 startup scripts cannot be modified without first stopping the instance, StopInstances should be a signal. +Reference: https://unit42.paloaltonetworks.com/malicious-operations-of-exposed-iam-keys-cryptojacking/ +Reports: + MITRE ATT&CK: + - TA0002:T1059 +Detection: + - Sequence: + - ID: StopInstance + RuleID: AWS.EC2.StopInstances + - ID: StartupScriptChange + RuleID: AWS.EC2.Startup.Script.Change + Transitions: + - ID: StopInstance FOLLOWED BY StartupScriptChange + From: StopInstance + To: StartupScriptChange + Match: + - On: p_alert_context.instance_ids + LookbackWindowMinutes: 90 + Schedule: + RateMinutes: 60 + TimeoutMinutes: 1 +Tests: + - Name: Instance Stopped, Followed By Script Change + ExpectedResult: true + RuleOutputs: + - ID: StopInstance + Matches: + p_alert_context.instance_ids: + 'i-abcdef0123456789a': + - "2024-06-01T10:00:01Z" + - ID: StartupScriptChange + Matches: + p_alert_context.instance_ids: + 'i-abcdef0123456789a': + - "2024-06-01T10:01:01Z" + - Name: Instance Stopped, Not Followed By Script Change + ExpectedResult: false + RuleOutputs: + - ID: StopInstance + Matches: + p_alert_context.instance_ids: + 'i-abcdef0123456789a': + - "2024-06-01T10:00:01Z" diff --git a/correlation_rules/gcp_cloud_run_service_create_followed_by_set_iam_policy.yml b/correlation_rules/gcp_cloud_run_service_create_followed_by_set_iam_policy.yml new file mode 100644 index 000000000..c0643468d --- /dev/null +++ b/correlation_rules/gcp_cloud_run_service_create_followed_by_set_iam_policy.yml @@ -0,0 +1,72 @@ +AnalysisType: correlation_rule +RuleID: "GCP.Cloud.Run.Service.Created.FOLLOWED.BY.Set.IAM.Policy" +DisplayName: "GCP Cloud Run Service Created FOLLOWED BY Set IAM Policy" +Enabled: true +Severity: High +Description: Detects run.services.create method for privilege escalation in GCP. The exploit creates a new Cloud Run + Service that, when invoked, returns the Service Account's access token by accessing the metadata API of the server + it is running on. +Reference: https://rhinosecuritylabs.com/gcp/privilege-escalation-google-cloud-platform-part-1/ +Runbook: Confirm this was authorized and necessary behavior +Reports: + MITRE ATT&CK: + - TA0004:T1548 # Abuse Elevation Control Mechanism +Detection: + - Sequence: + - ID: ServiceCreated + RuleID: GCP.Cloud.Run.Service.Created + - ID: SetIAMPolicy + RuleID: GCP.Cloud.Run.Set.IAM.Policy + Transitions: + - ID: ServiceCreated FOLLOWED BY SetIAMPolicy + From: ServiceCreated + To: SetIAMPolicy + Match: + - On: p_alert_context.caller_ip + LookbackWindowMinutes: 90 + Schedule: + RateMinutes: 60 + TimeoutMinutes: 1 +Tests: + - Name: GCP Service Run, Followed By IAM Policy Change From Same IP + ExpectedResult: true + RuleOutputs: + - ID: ServiceCreated + Matches: + p_alert_context.caller_ip: + 1.1.1.1: + - "2024-06-01T10:00:00Z" + - ID: SetIAMPolicy + Matches: + p_alert_context.caller_ip: + 1.1.1.1: + - "2024-06-01T10:00:01Z" + - Name: GCP Service Run, Not Followed By IAM Policy Change + ExpectedResult: false + RuleOutputs: + - ID: ServiceCreated + Matches: + p_alert_context.caller_ip: + 1.1.1.1: + - "2024-06-01T10:00:00Z" + - Name: IAM Policy Change, Not Preceeded By GCP Service Run + ExpectedResult: false + RuleOutputs: + - ID: SetIAMPolicy + Matches: + p_alert_context.caller_ip: + 1.1.1.1: + - "2024-06-01T10:00:01Z" + - Name: GCP Service Run, Followed By IAM Policy Change From Different IP + ExpectedResult: false + RuleOutputs: + - ID: ServiceCreated + Matches: + p_alert_context.caller_ip: + 1.1.1.1: + - "2024-06-01T10:00:00Z" + - ID: SetIAMPolicy + Matches: + p_alert_context.caller_ip: + 2.2.2.2: + - "2024-06-01T10:00:01Z" diff --git a/correlation_rules/github_advanced_security_change_not_followed_by_repo_archived.yml b/correlation_rules/github_advanced_security_change_not_followed_by_repo_archived.yml new file mode 100644 index 000000000..a581109c3 --- /dev/null +++ b/correlation_rules/github_advanced_security_change_not_followed_by_repo_archived.yml @@ -0,0 +1,59 @@ +AnalysisType: correlation_rule +RuleID: "GitHub.Advanced.Security.Change.NOT.FOLLOWED.BY.Repo.Archived" +DisplayName: "GitHub Advanced Security Change NOT FOLLOWED BY Repo Archived" +Enabled: true +Severity: Critical +Description: Identifies when advances security change was made not to archive a repo. Eliminates false positives in the Advances Security Change Rule when the repo is archived. +Reference: https://docs.github.com/en/code-security/getting-started/auditing-security-alerts +Detection: + - Sequence: + - ID: GHASChange + RuleID: GitHub.Advanced.Security.Change + - ID: RepoArchived + RuleID: Github.Repo.Archived + Absence: true + Transitions: + - ID: GHASChange NOT FOLLOWED BY RepoArchived + From: GHASChange + To: RepoArchived + Match: + - On: p_alert_context.repo + LookbackWindowMinutes: 15 + Schedule: + RateMinutes: 10 + TimeoutMinutes: 1 +Tests: + - Name: Security Change on Repo, Followed By Same Repo Archived + ExpectedResult: false + RuleOutputs: + - ID: GHASChange + Matches: + p_alert_context.repo: + my-org/example-repo: + - "2024-06-01T10:00:00Z" + - ID: RepoArchived + Matches: + p_alert_context.repo: + my-org/example-repo: + - "2024-06-01T10:00:01Z" + - Name: Security Change on Repo, Followed By Different Repo Archived + ExpectedResult: true + RuleOutputs: + - ID: GHASChange + Matches: + p_alert_context.repo: + my-org/example-repo: + - "2024-06-01T10:00:00Z" + - ID: RepoArchived + Matches: + p_alert_context.repo: + my-org/other-repo: + - "2024-06-01T10:00:01Z" + - Name: Security Change on Repo, Not Followed By Repo Archived + ExpectedResult: true + RuleOutputs: + - ID: GHASChange + Matches: + p_alert_context.repo: + my-org/example-repo: + - "2024-06-01T10:00:00Z" \ No newline at end of file diff --git a/packs/aws.yml b/packs/aws.yml index 5fd6dc10a..76b009593 100644 --- a/packs/aws.yml +++ b/packs/aws.yml @@ -103,6 +103,8 @@ PackDefinition: - AWS.DynamoDB.Autoscaling - AWS.EC2.Instance.EBSOptimization - AWS.EC2.Startup.Script.Change + - AWS.EC2.StopInstances + - AWS.EC2.StopInstance.FOLLOWED.BY.ModifyInstanceAttributes - AWS.ELBV2.LoadBalancer.HasSSLPolicy - AWS.ELBv2.SSLPolicy - AWS.GuardDuty.Enabled diff --git a/packs/gcp_audit.yml b/packs/gcp_audit.yml index e99ff6f84..85b7271e5 100644 --- a/packs/gcp_audit.yml +++ b/packs/gcp_audit.yml @@ -10,6 +10,9 @@ PackDefinition: - GCP.CloudBuild.Potential.Privilege.Escalation - GCP.Cloudfunctions.Functions.Create - GCP.Cloudfunctions.Functions.Update + - GCP.Cloud.Run.Service.Created + - GCP.Cloud.Run.Service.Created.FOLLOWED.BY.Set.IAM.Policy + - GCP.Cloud.Run.Set.IAM.Policy - GCP.Destructive.Queries - GCP.DNS.Zone.Modified.or.Deleted - GCP.Firewall.Rule.Created diff --git a/packs/github.yml b/packs/github.yml index bb5de9948..a5d8a650f 100644 --- a/packs/github.yml +++ b/packs/github.yml @@ -5,6 +5,7 @@ DisplayName: "Panther GitHub Audit Pack" PackDefinition: IDs: - GitHub.Advanced.Security.Change + - GitHub.Advanced.Security.Change.NOT.FOLLOWED.BY.Repo.Archived - GitHub.Branch.PolicyOverride - GitHub.Branch.ProtectionDisabled - GitHub.Org.AuthChange diff --git a/rules/aws_cloudtrail_rules/aws_ec2_startup_script_change.py b/rules/aws_cloudtrail_rules/aws_ec2_startup_script_change.py index fe7883d03..b41b045d9 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_startup_script_change.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_startup_script_change.py @@ -23,4 +23,6 @@ def dedup(event): def alert_context(event): - return aws_rule_context(event) + context = aws_rule_context(event) + context["instance_ids"] = [deep_get(event, "requestParameters", "instanceId"), "no_instance_id"] + return context diff --git a/rules/aws_cloudtrail_rules/aws_ec2_stopinstances.py b/rules/aws_cloudtrail_rules/aws_ec2_stopinstances.py new file mode 100644 index 000000000..cdb5c6e35 --- /dev/null +++ b/rules/aws_cloudtrail_rules/aws_ec2_stopinstances.py @@ -0,0 +1,29 @@ +from panther_base_helpers import aws_rule_context + + +def rule(event): + return all( + [ + not event.get("errorCode"), + not event.get("errorMessage"), + event.get("eventName") == "StopInstances", + ] + ) + + +def title(event): + instances = [ + instance["instanceId"] + for instance in event.deep_get("requestParameters", "instancesSet", "items", default=[]) + ] + account = event.get("recipientAccountId") + return f"EC2 instances {instances} stopped in account {account}." + + +def alert_context(event): + context = aws_rule_context(event) + context["instance_ids"] = [ + instance["instanceId"] + for instance in event.deep_get("requestParameters", "instancesSet", "items", default=[]) + ] + return context diff --git a/rules/aws_cloudtrail_rules/aws_ec2_stopinstances.yml b/rules/aws_cloudtrail_rules/aws_ec2_stopinstances.yml new file mode 100644 index 000000000..90332e42c --- /dev/null +++ b/rules/aws_cloudtrail_rules/aws_ec2_stopinstances.yml @@ -0,0 +1,238 @@ +AnalysisType: rule +RuleID: "AWS.EC2.StopInstances" +DisplayName: "CloudTrail EC2 StopInstances" +Enabled: true +CreateAlert: false +Filename: aws_ec2_stopinstances.py +LogTypes: + - AWS.CloudTrail +Tags: + - panther-signal +Severity: Info +Description: > + A CloudTrail instances were stopped. It makes further changes of instances possible +Reference: https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-log-file-examples.html +Tests: + - + Name: CloudTrail Instances Were Stopped + ExpectedResult: true + Log: + { + "eventVersion": "1.08", + "userIdentity": { + "type": "IAMUser", + "principalId": "EXAMPLE6E4XEGITWATV6R", + "arn": "arn:aws:iam::777788889999:user/Nikki", + "accountId": "777788889999", + "accessKeyId": "AKIAI44QH8DHBEXAMPLE", + "userName": "Nikki", + "sessionContext": { + "sessionIssuer": { }, + "webIdFederationData": { }, + "attributes": { + "creationDate": "2023-07-19T21:11:57Z", + "mfaAuthenticated": "false" + } + } + }, + "eventTime": "2023-07-19T21:14:20Z", + "eventSource": "ec2.amazonaws.com", + "eventName": "StopInstances", + "awsRegion": "us-east-1", + "sourceIPAddress": "192.0.2.0", + "userAgent": "aws-cli/2.13.5 Python/3.11.4 Linux/4.14.255-314-253.539.amzn2.x86_64 exec-env/CloudShell exe/x86_64.amzn.2 prompt/off command/ec2.stop-instances", + "requestParameters": { + "instancesSet": { + "items": [ + { + "instanceId": "i-EXAMPLE56126103cb" + }, + { + "instanceId": "i-EXAMPLEaff4840c22" + } + ] + }, + "force": false + }, + "responseElements": { + "requestId": "c308a950-e43e-444e-afc1-EXAMPLE73e49", + "instancesSet": { + "items": [ + { + "instanceId": "i-EXAMPLE56126103cb", + "currentState": { + "code": 64, + "name": "stopping" + }, + "previousState": { + "code": 16, + "name": "running" + } + }, + { + "instanceId": "i-EXAMPLEaff4840c22", + "currentState": { + "code": 64, + "name": "stopping" + }, + "previousState": { + "code": 16, + "name": "running" + } + } + ] + } + }, + "requestID": "c308a950-e43e-444e-afc1-EXAMPLE73e49", + "eventID": "9357a8cc-a0eb-46a1-b67e-EXAMPLE19b14", + "readOnly": false, + "eventType": "AwsApiCall", + "managementEvent": true, + "recipientAccountId": "777788889999", + "eventCategory": "Management", + "tlsDetails": { + "tlsVersion": "TLSv1.2", + "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", + "clientProvidedHostHeader": "ec2.us-east-1.amazonaws.com" + }, + "sessionCredentialFromConsole": "true" + } + - + Name: CloudTrail Instances Were Started + ExpectedResult: false + Log: + { + "eventVersion": "1.08", + "userIdentity": { + "type": "IAMUser", + "principalId": "EXAMPLE6E4XEGITWATV6R", + "arn": "arn:aws:iam::123456789012:user/Mateo", + "accountId": "123456789012", + "accessKeyId": "AKIAIOSFODNN7EXAMPLE", + "userName": "Mateo", + "sessionContext": { + "sessionIssuer": { }, + "webIdFederationData": { }, + "attributes": { + "creationDate": "2023-07-19T21:11:57Z", + "mfaAuthenticated": "false" + } + } + }, + "eventTime": "2023-07-19T21:17:28Z", + "eventSource": "ec2.amazonaws.com", + "eventName": "StartInstances", + "awsRegion": "us-east-1", + "sourceIPAddress": "192.0.2.0", + "userAgent": "aws-cli/2.13.5 Python/3.11.4 Linux/4.14.255-314-253.539.amzn2.x86_64 exec-env/CloudShell exe/x86_64.amzn.2 prompt/off command/ec2.start-instances", + "requestParameters": { + "instancesSet": { + "items": [ + { + "instanceId": "i-EXAMPLE56126103cb" + }, + { + "instanceId": "i-EXAMPLEaff4840c22" + } + ] + } + }, + "responseElements": { + "requestId": "e4336db0-149f-4a6b-844d-EXAMPLEb9d16", + "instancesSet": { + "items": [ + { + "instanceId": "i-EXAMPLEaff4840c22", + "currentState": { + "code": 0, + "name": "pending" + }, + "previousState": { + "code": 80, + "name": "stopped" + } + }, + { + "instanceId": "i-EXAMPLE56126103cb", + "currentState": { + "code": 0, + "name": "pending" + }, + "previousState": { + "code": 80, + "name": "stopped" + } + } + ] + } + }, + "requestID": "e4336db0-149f-4a6b-844d-EXAMPLEb9d16", + "eventID": "e755e09c-42f9-4c5c-9064-EXAMPLE228c7", + "readOnly": false, + "eventType": "AwsApiCall", + "managementEvent": true, + "recipientAccountId": "123456789012", + "eventCategory": "Management", + "tlsDetails": { + "tlsVersion": "TLSv1.2", + "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", + "clientProvidedHostHeader": "ec2.us-east-1.amazonaws.com" + }, + "sessionCredentialFromConsole": "true" + } + - + Name: Error Stopping CloudTrail Instances + ExpectedResult: false + Log: + { + "eventVersion": "1.05", + "errorCode": "SomeErrorCode", + "userIdentity": { + "type": "IAMUser", + "principalId": "EXAMPLE6E4XEGITWATV6R", + "arn": "arn:aws:iam::777788889999:user/Nikki", + "accountId": "777788889999", + "accessKeyId": "AKIAI44QH8DHBEXAMPLE", + "userName": "Nikki", + "sessionContext": { + "sessionIssuer": { }, + "webIdFederationData": { }, + "attributes": { + "creationDate": "2023-07-19T21:11:57Z", + "mfaAuthenticated": "false" + } + } + }, + "eventTime": "2023-07-19T21:14:20Z", + "eventSource": "ec2.amazonaws.com", + "eventName": "StopInstances", + "awsRegion": "us-east-1", + "sourceIPAddress": "192.0.2.0", + "userAgent": "aws-cli/2.13.5 Python/3.11.4 Linux/4.14.255-314-253.539.amzn2.x86_64 exec-env/CloudShell exe/x86_64.amzn.2 prompt/off command/ec2.stop-instances", + "requestParameters": { + "instancesSet": { + "items": [ + { + "instanceId": "i-EXAMPLE56126103cb" + }, + { + "instanceId": "i-EXAMPLEaff4840c22" + } + ] + }, + "force": false + }, + "requestID": "c308a950-e43e-444e-afc1-EXAMPLE73e49", + "eventID": "9357a8cc-a0eb-46a1-b67e-EXAMPLE19b14", + "readOnly": false, + "eventType": "AwsApiCall", + "managementEvent": true, + "recipientAccountId": "777788889999", + "eventCategory": "Management", + "tlsDetails": { + "tlsVersion": "TLSv1.2", + "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", + "clientProvidedHostHeader": "ec2.us-east-1.amazonaws.com" + }, + "sessionCredentialFromConsole": "true" + } \ No newline at end of file diff --git a/rules/gcp_audit_rules/gcp_cloud_run_service_created.py b/rules/gcp_audit_rules/gcp_cloud_run_service_created.py new file mode 100644 index 000000000..6522b0fdd --- /dev/null +++ b/rules/gcp_audit_rules/gcp_cloud_run_service_created.py @@ -0,0 +1,43 @@ +from gcp_base_helpers import gcp_alert_context +from panther_base_helpers import deep_get, deep_walk + + +def rule(event): + if deep_get(event, "severity") == "ERROR": + return False + + if not deep_get(event, "protoPayload", "methodName").endswith("Services.CreateService"): + return False + + authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + if not authorization_info: + return False + + for auth in authorization_info: + if auth.get("permission") == "run.services.create" and auth.get("granted") is True: + return True + return False + + +def title(event): + actor = deep_get( + event, "protoPayload", "authenticationInfo", "principalEmail", default="" + ) + project_id = deep_get(event, "resource", "labels", "project_id", default="") + + return f"[GCP]: [{actor}] created new Run Service in project [{project_id}]" + + +def alert_context(event): + context = gcp_alert_context(event) + context["service_account"] = deep_get( + event, + "protoPayload", + "request", + "service", + "spec", + "template", + "spec", + default="", + ) + return context diff --git a/rules/gcp_audit_rules/gcp_cloud_run_service_created.yml b/rules/gcp_audit_rules/gcp_cloud_run_service_created.yml new file mode 100644 index 000000000..154fa3179 --- /dev/null +++ b/rules/gcp_audit_rules/gcp_cloud_run_service_created.yml @@ -0,0 +1,202 @@ +AnalysisType: rule +LogTypes: + - GCP.AuditLog +Description: + Detects creation of new Cloud Run Service, which, if configured maliciously, may be part of the attack + aimed to invoke the service and retrieve the access token. +DisplayName: "GCP Cloud Run Service Created" +RuleID: "GCP.Cloud.Run.Service.Created" +Filename: gcp_cloud_run_service_created.py +Enabled: true +CreateAlert: false +Reference: https://cloud.google.com/run/docs/quickstarts/deploy-container +Runbook: Confirm this was authorized and necessary behavior +Severity: Low +DedupPeriodMinutes: 60 +Threshold: 1 +Tests: + - Name: GCP Run Service Created + ExpectedResult: true + Log: + { + "insertId": "jzm5rucrn2", + "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity", + "protoPayload": + { + "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog", + "authenticationInfo": + { + "principalEmail": "some.user@company.com", + "principalSubject": "user:some.user@company.com", + }, + "authorizationInfo": + [ + { + "granted": true, + "permission": "run.services.create", + "resource": "namespaces/some-project/services/cloudrun-exfil", + "resourceAttributes": {}, + }, + ], + "methodName": "google.cloud.run.v1.Services.CreateService", + "request": + { + "@type": "type.googleapis.com/google.cloud.run.v1.CreateServiceRequest", + "parent": "namespaces/some-project", + "service": + { + "apiVersion": "serving.knative.dev/v1", + "kind": "Service", + "metadata": + { + "annotations": + { + "client.knative.dev/user-image": "us-west1-docker.pkg.dev/some-project/abc-test/run_services_create_test", + }, + "name": "cloudrun-exfil", + "namespace": "some-project", + }, + "spec": + { + "template": + { + "metadata": + { + "annotations": + { + "client.knative.dev/user-image": "us-west1-docker.pkg.dev/some-project/abc-test/run_services_create_test", + }, + "labels": + { + "cloud.googleapis.com/location": "us-west1", + }, + "name": "cloudrun-exfil-00001-zif", + }, + "spec": + { + "serviceAccountName": "abc-test@some-project.iam.gserviceaccount.com", + }, + }, + }, + }, + }, + "requestMetadata": + { + "callerIP": "1.2.3.4", + "callerSuppliedUserAgent": "(gzip),gzip(gfe)", + "destinationAttributes": {}, + "requestAttributes": + { "auth": {}, "time": "2024-02-02T09:43:54.690161Z" }, + }, + "resourceLocation": { "currentLocations": ["us-west1"] }, + "resourceName": "namespaces/some-project/services/cloudrun-exfil", + "response": + { + "@type": "type.googleapis.com/google.cloud.run.v1.Service", + "apiVersion": "serving.knative.dev/v1", + "kind": "Service", + "metadata": + { + "annotations": + { + "client.knative.dev/user-image": "us-west1-docker.pkg.dev/some-project/abc-test/run_services_create_test", + "run.googleapis.com/ingress": "all", + "run.googleapis.com/operation-id": "6fdf115a-1bdd-4836-b0ca-ae71f8ba6718", + "serving.knative.dev/creator": "some.user@company.com", + "serving.knative.dev/lastModifier": "some.user@company.com", + }, + "creationTimestamp": "2024-02-02T09:43:54.640837Z", + "generation": 1, + "labels": { "cloud.googleapis.com/location": "us-west1" }, + "name": "cloudrun-exfil", + "namespace": "1028347275902", + "resourceVersion": "AAYQYvNHUcU", + "selfLink": "/apis/serving.knative.dev/v1/namespaces/1028347275902/services/cloudrun-exfil", + "uid": "45101e8e-7b91-4c41-81a1-969e876923f4", + }, + "spec": + { + "template": + { + "metadata": + { + "annotations": + { + "autoscaling.knative.dev/maxScale": "100", + "client.knative.dev/user-image": "us-west1-docker.pkg.dev/some-project/abc-test/run_services_create_test", + }, + "labels": + { + "run.googleapis.com/startupProbeType": "Default", + }, + "name": "cloudrun-exfil-00001-zif", + }, + "spec": + { + "containerConcurrency": 80, + "serviceAccountName": "abc-test@some-project.iam.gserviceaccount.com", + "timeoutSeconds": 300, + }, + }, + "traffic": [{ "latestRevision": true, "percent": 100 }], + }, + "status": {}, + }, + "serviceName": "run.googleapis.com", + }, + "receiveTimestamp": "2024-02-02 09:43:54.723840817", + "resource": + { + "labels": + { + "configuration_name": "", + "location": "us-west1", + "project_id": "some-project", + "revision_name": "", + "service_name": "cloudrun-exfil", + }, + "type": "cloud_run_revision", + }, + "severity": "NOTICE", + "timestamp": "2024-02-02 09:43:54.497796000", + } + - Name: GCP Run Service Not Created + ExpectedResult: false + Log: + { + "insertId": "jfca5rd3wqn", + "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity", + "protoPayload": + { + "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog", + "authenticationInfo": + { + "principalEmail": "some.user@company.com", + "principalSubject": "user:some.user@company.com", + }, + "authorizationInfo": + [ + { + "granted": true, + "permission": "run.services.create", + "resource": "namespaces/some-project/services/cloudrun-exfil", + "resourceAttributes": {}, + }, + ], + "methodName": "google.cloud.run.v1.Services.CreateService", + "request": ..., + "requestMetadata": ..., + "resourceLocation": ..., + "resourceName": ..., + "serviceName": "run.googleapis.com", + "status": + { + "code": 6, + "message": "Resource 'cloudrun-exfil' already exists.", + }, + }, + "receiveTimestamp": "2024-02-02 09:36:22.250430490", + "resource": ..., + "severity": "ERROR", + "timestamp": "2024-02-02 09:36:21.212182000", + } diff --git a/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.py b/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.py new file mode 100644 index 000000000..6acdc64f5 --- /dev/null +++ b/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.py @@ -0,0 +1,45 @@ +from gcp_base_helpers import gcp_alert_context +from panther_base_helpers import deep_get, deep_walk + + +def rule(event): + if deep_get(event, "severity") == "ERROR": + return False + + if not deep_get(event, "protoPayload", "methodName").endswith("Services.SetIamPolicy"): + return False + + authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + if not authorization_info: + return False + + for auth in authorization_info: + if auth.get("permission") == "run.services.setIamPolicy" and auth.get("granted") is True: + return True + return False + + +def title(event): + actor = deep_get( + event, "protoPayload", "authenticationInfo", "principalEmail", default="" + ) + resource = deep_get(event, "resource", "resourceName", default="") + assigned_role = deep_walk(event, "protoPayload", "response", "bindings", "role") + project_id = deep_get(event, "resource", "labels", "project_id", default="") + + return ( + f"[GCP]: [{actor}] was granted access to [{resource}] service with " + f"the [{assigned_role}] role in project [{project_id}]" + ) + + +def alert_context(event): + context = gcp_alert_context(event) + context["assigned_role"] = deep_walk( + event, + "protoPayload", + "response", + "bindings", + "role", + ) + return context diff --git a/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.yml b/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.yml new file mode 100644 index 000000000..f8d273e1f --- /dev/null +++ b/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.yml @@ -0,0 +1,154 @@ +AnalysisType: rule +LogTypes: + - GCP.AuditLog +Description: + Detects new roles granted to users to Cloud Run Services. This could potentially allow the user to perform + actions within the project and its resources, which could pose a security risk. +DisplayName: "GCP Cloud Run Set IAM Policy" +RuleID: "GCP.Cloud.Run.Set.IAM.Policy" +Enabled: true +Filename: gcp_cloud_run_set_iam_policy.py +Reference: https://cloud.google.com/run/docs/securing/managing-access +Runbook: Confirm this was authorized and necessary behavior +Severity: High +DedupPeriodMinutes: 60 +Threshold: 1 +Tests: + - Name: GCP Run IAM Policy Set + ExpectedResult: true + Log: + { + "insertId": "l3jvzyd2s2s", + "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity", + "protoPayload": + { + "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog", + "authenticationInfo": + { + "principalEmail": "some.user@company.com", + "principalSubject": "user:some.user@company.com", + }, + "authorizationInfo": + [ + { + "granted": true, + "permission": "run.services.setIamPolicy", + "resource": "projects/some-project/locations/us-west1/services/cloudrun-exfil", + "resourceAttributes": {}, + }, + { + "granted": true, + "permission": "run.services.setIamPolicy", + "resourceAttributes": {}, + }, + ], + "methodName": "google.cloud.run.v1.Services.SetIamPolicy", + "request": + { + "@type": "type.googleapis.com/google.iam.v1.SetIamPolicyRequest", + "policy": + { + "bindings": + [ + { + "members": ["user:some.user@company.com"], + "role": "roles/run.invoker", + }, + ], + }, + "resource": "projects/some-project/locations/us-west1/services/cloudrun-exfil", + }, + "requestMetadata": + { + "callerIP": "1.2.3.4", + "callerSuppliedUserAgent": "(gzip),gzip(gfe)", + "destinationAttributes": {}, + "requestAttributes": + { "auth": {}, "time": "2024-02-02T09:44:26.173186Z" }, + }, + "resourceLocation": { "currentLocations": ["us-west1"] }, + "resourceName": "projects/some-project/locations/us-west1/services/cloudrun-exfil", + "response": + { + "@type": "type.googleapis.com/google.iam.v1.Policy", + "bindings": + [ + { + "members": ["user:some.user@company.com"], + "role": "roles/run.invoker", + }, + ], + "etag": "BwYQYvUoBxs=", + }, + "serviceName": "run.googleapis.com", + }, + "receiveTimestamp": "2024-02-02 09:44:26.653891982", + "resource": + { + "labels": + { + "configuration_name": "", + "location": "us-west1", + "project_id": "some-project", + "revision_name": "", + "service_name": "", + }, + "type": "cloud_run_revision", + }, + "severity": "NOTICE", + "timestamp": "2024-02-02 09:44:26.029835000", + } + - Name: GCP Run IAM Policy Not Set + ExpectedResult: false + Log: + { + "insertId": "l3jvzyd2s2s", + "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity", + "protoPayload": + { + "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog", + "authenticationInfo": + { + "principalEmail": "some.user@company.com", + "principalSubject": "user:some.user@company.com", + }, + "authorizationInfo": + [ + { + "granted": false, + "permission": "run.services.setIamPolicy", + "resource": "projects/some-project/locations/us-west1/services/cloudrun-exfil", + "resourceAttributes": {}, + }, + { + "granted": false, + "permission": "run.services.setIamPolicy", + "resourceAttributes": {}, + }, + ], + "methodName": "google.cloud.run.v1.Services.SetIamPolicy", + "request": + { + "@type": "type.googleapis.com/google.iam.v1.SetIamPolicyRequest", + "policy": + { + "bindings": + [ + { + "members": ["user:some.user@company.com"], + "role": "roles/run.invoker", + }, + ], + }, + "resource": "projects/some-project/locations/us-west1/services/cloudrun-exfil", + }, + "requestMetadata": ..., + "resourceLocation": ..., + "resourceName": "projects/some-project/locations/us-west1/services/cloudrun-exfil", + "serviceName": "run.googleapis.com", + }, + "receiveTimestamp": "2024-02-02 09:44:26.653891982", + "resource": ..., + "severity": "NOTICE", + "timestamp": "2024-02-02 09:44:26.029835000", + } diff --git a/rules/github_rules/github_advanced_security_change.yml b/rules/github_rules/github_advanced_security_change.yml index 152870f09..fce5ad846 100644 --- a/rules/github_rules/github_advanced_security_change.yml +++ b/rules/github_rules/github_advanced_security_change.yml @@ -3,6 +3,7 @@ Filename: github_advanced_security_change.py RuleID: "GitHub.Advanced.Security.Change" DisplayName: "GitHub Security Change, includes GitHub Advanced Security" Enabled: true +CreateAlert: false LogTypes: - GitHub.Audit Tags: diff --git a/rules/github_rules/github_repo_archived.yml b/rules/github_rules/github_repo_archived.yml new file mode 100644 index 000000000..7348a4a87 --- /dev/null +++ b/rules/github_rules/github_repo_archived.yml @@ -0,0 +1,62 @@ +AnalysisType: rule +RuleID: "Github.Repo.Archived" +DisplayName: "GitHub Repository Archived" +Enabled: true +CreateAlert: false +LogTypes: + - GitHub.Audit +Tags: + - GitHub + - panther-signal +Reference: https://docs.github.com/en/repositories/archiving-a-github-repository/about-archiving-content-and-data-on-github +Severity: Info +Description: Detects when a repository is archived. +Detection: + - Key: action + Condition: Equals + Value: repo.archived +AlertTitle: "Repository [{repo}] archived." +AlertContext: + - KeyName: action + KeyValue: + Key: action + - KeyName: actor + KeyValue: + Key: actor + - KeyName: org + KeyValue: + Key: org + - KeyName: repo + KeyValue: + Key: repo + - KeyName: user + KeyValue: + Key: user + - KeyName: actor_location + KeyValue: + KeyPath: actor_location.country_code +Tests: + - + Name: GitHub - Repo Created + ExpectedResult: false + Log: + { + "actor": "cat", + "action": "repo.create", + "created_at": 1621305118553, + "org": "my-org", + "p_log_type": "GitHub.Audit", + "repo": "my-org/my-repo" + } + - + Name: GitHub - Repo Archived + ExpectedResult: true + Log: + { + "actor": "cat", + "action": "repo.archived", + "created_at": 1621305118553, + "org": "my-org", + "p_log_type": "GitHub.Audit", + "repo": "my-org/my-repo" + } \ No newline at end of file