diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2078e8513..1a7f8ddf9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: fetch-depth: 0 token: ${{ env.GITHUB_TOKEN }} - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 with: role-to-assume: ${{ secrets.AWS_ROLE }} aws-region: ${{ secrets.AWS_REGION }} diff --git a/Pipfile b/Pipfile index 7fc86308c..95e4f9a9f 100644 --- a/Pipfile +++ b/Pipfile @@ -19,7 +19,7 @@ wrapt = "~=1.15" [packages] policyuniverse = "==1.5.1.20230817" requests = "==2.31.0" -panther-analysis-tool = "~=0.39" +panther-analysis-tool = "~=0.40" panther-detection-helpers = "==0.2.0" [requires] diff --git a/Pipfile.lock b/Pipfile.lock index f22df0f43..4204c9c87 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d2d43cb0e38e6667b7f9fc5f22dc91cfe095a2e0c7825c2e9105a18dbfeae942" + "sha256": "ef3121c751224143c7f27939ddd036b7a8b5a454d45de632892d1a4369805548" }, "pipfile-spec": 6, "requires": { @@ -147,19 +147,19 @@ }, "boto3": { "hashes": [ - "sha256:7c70c6ceb2706c7fad6466a5de174fe6d0d6f5f8f1e052bfaad9cbe4e53b64cd", - "sha256:e74fbad79bc921a74a9a276ef9f38e1e31153f76690fe9bc5ec790007de36572" + "sha256:9fd66f22a4cdd63165a7a19186fff9b6e3d742e83498ea3f3231bab6ae4bf0f3", + "sha256:ae1a974c728c076a49392a695102124f650f45361c654bb7c0bef1bb644c52d5" ], "markers": "python_version >= '3.8'", - "version": "==1.34.38" + "version": "==1.34.41" }, "botocore": { "hashes": [ - "sha256:773e49f5bf596191e796b2a15096ff381e61778cbe7c982b381bb9f6bfe5fef3", - "sha256:da9754a8e1798706427ede9c9c0a55263bd8e57f217c021807b2946eb4a0c2d8" + "sha256:3a6943c75a0d292ab6e008bce58ee6503776969479f991f5ad03a5d877af29ae", + "sha256:9b5827332da766da487e5a5b14b36e02528be9f2e899f909577afb7001eb441d" ], "markers": "python_version >= '3.8'", - "version": "==1.34.38" + "version": "==1.34.41" }, "certifi": { "hashes": [ @@ -668,10 +668,10 @@ }, "panther-analysis-tool": { "hashes": [ - "sha256:70bea9cbadd820ae2e77361e966320e058d5d16ebbc8d730298b0606844e934c" + "sha256:54534d08ef27e35186a7e5cfbc2eb7970132d615367372906d2065e030164718" ], "index": "pypi", - "version": "==0.39.0" + "version": "==0.40.0" }, "panther-core": { "hashes": [ @@ -715,6 +715,7 @@ "sha256:7920896195af163230635f1a5cee0958f56003ef8c421f805ec81f134f80a57c" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==1.5.1.20230817" }, "pygments": { @@ -738,7 +739,7 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.2" }, "pyyaml": { @@ -911,6 +912,7 @@ "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==2.31.0" }, "rpds-py": { @@ -1079,7 +1081,7 @@ "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875", "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412" ], - "markers": "platform_python_implementation == 'CPython' and python_version < '3.13'", + "markers": "python_version < '3.13' and platform_python_implementation == 'CPython'", "version": "==0.2.8" }, "s3transfer": { @@ -1110,7 +1112,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "sniffio": { @@ -1155,11 +1157,11 @@ }, "tqdm": { "hashes": [ - "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386", - "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7" + "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9", + "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531" ], "markers": "python_version >= '3.7'", - "version": "==4.66.1" + "version": "==4.66.2" }, "typing-extensions": { "hashes": [ @@ -1289,6 +1291,7 @@ "sha256:527906bec6088cb499aae31bc962864b4e77569e9d529ee51df3a93b4b8ab28a" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==1.7.7" }, "black": { @@ -1307,23 +1310,24 @@ "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==22.12.0" }, "boto3": { "hashes": [ - "sha256:7c70c6ceb2706c7fad6466a5de174fe6d0d6f5f8f1e052bfaad9cbe4e53b64cd", - "sha256:e74fbad79bc921a74a9a276ef9f38e1e31153f76690fe9bc5ec790007de36572" + "sha256:9fd66f22a4cdd63165a7a19186fff9b6e3d742e83498ea3f3231bab6ae4bf0f3", + "sha256:ae1a974c728c076a49392a695102124f650f45361c654bb7c0bef1bb644c52d5" ], "markers": "python_version >= '3.8'", - "version": "==1.34.38" + "version": "==1.34.41" }, "botocore": { "hashes": [ - "sha256:773e49f5bf596191e796b2a15096ff381e61778cbe7c982b381bb9f6bfe5fef3", - "sha256:da9754a8e1798706427ede9c9c0a55263bd8e57f217c021807b2946eb4a0c2d8" + "sha256:3a6943c75a0d292ab6e008bce58ee6503776969479f991f5ad03a5d877af29ae", + "sha256:9b5827332da766da487e5a5b14b36e02528be9f2e899f909577afb7001eb441d" ], "markers": "python_version >= '3.8'", - "version": "==1.34.38" + "version": "==1.34.41" }, "certifi": { "hashes": [ @@ -1539,6 +1543,7 @@ "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" ], "index": "pypi", + "markers": "python_version >= '3.5'", "version": "==5.1.1" }, "dill": { @@ -1547,6 +1552,7 @@ "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==0.3.8" }, "idna": { @@ -1563,6 +1569,7 @@ "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6" ], "index": "pypi", + "markers": "python_full_version >= '3.8.0'", "version": "==5.13.2" }, "jinja2": { @@ -1720,6 +1727,7 @@ "sha256:94e3b07a403cc8078ffee94bf404ef677112d036a57ddb5e0f19c5fcf48987f5" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==5.0.1" }, "mypy": { @@ -1753,6 +1761,7 @@ "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==1.8.0" }, "mypy-extensions": { @@ -1808,6 +1817,7 @@ "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad" ], "index": "pypi", + "markers": "python_full_version >= '3.7.2'", "version": "==2.17.7" }, "pylint-print": { @@ -1816,6 +1826,7 @@ "sha256:a2b2599e7887b93e551db2624c523c1e6e9e58c3be8416cd98d41e4427e2669b" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==1.0.1" }, "python-dateutil": { @@ -1823,7 +1834,7 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.2" }, "pyyaml": { @@ -1889,15 +1900,16 @@ "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==2.31.0" }, "responses": { "hashes": [ - "sha256:a2b43f4c08bfb9c9bd242568328c65a34b318741d3fab884ac843c5ceeb543f9", - "sha256:b127c6ca3f8df0eb9cc82fd93109a3007a86acb24871834c47b77765152ecf8c" + "sha256:01ae6a02b4f34e39bffceb0fc6786b67a25eae919c6368d05eabc8d9576c2a66", + "sha256:2f0b9c2b6437db4b528619a77e5d565e4ec2a9532162ac1a131a83529db7be1a" ], "markers": "python_version >= '3.8'", - "version": "==0.24.1" + "version": "==0.25.0" }, "rich": { "hashes": [ @@ -1920,7 +1932,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "stevedore": { @@ -2045,6 +2057,7 @@ "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==1.16.0" }, "xmltodict": { diff --git a/data_models/github_data_model.py b/data_models/github_data_model.py index b6f337089..5ab24efb8 100644 --- a/data_models/github_data_model.py +++ b/data_models/github_data_model.py @@ -6,14 +6,25 @@ "team.promote_maintainer", } +CONDITIONAL_ADMIN_EVENTS = { + "team.add_repository", +} + def get_admin_role(event): action = event.get("action", "") + permission = event.get("permission", "") + if action in CONDITIONAL_ADMIN_EVENTS and permission == "admin": + return action return action if action in ADMIN_EVENTS else "" def get_event_type(event): - if event.get("action", "") in ADMIN_EVENTS: + action = event.get("action", "") + permission = event.get("permission", "") + if action in ADMIN_EVENTS: + return event_type.ADMIN_ROLE_ASSIGNED + if action in CONDITIONAL_ADMIN_EVENTS and permission == "admin": return event_type.ADMIN_ROLE_ASSIGNED if event.get("action", "") == "org.disable_two_factor_requirement": return event_type.MFA_DISABLED diff --git a/lookup_tables/tor/tor_exit_nodes.yml b/lookup_tables/tor/tor_exit_nodes.yml index 672fd65f3..705b7f5e2 100644 --- a/lookup_tables/tor/tor_exit_nodes.yml +++ b/lookup_tables/tor/tor_exit_nodes.yml @@ -211,6 +211,7 @@ LogTypeMap: - "$.protoPayload.requestMetadata.callerIP" - "$.httpRequest.remoteIP" - "$.httpRequest.serverIP" + - "$.requestMetadata.callerIP" - LogType: GCP.HTTPLoadBalancer Selectors: - "$.jsonPayload.removeIp" diff --git a/packs/auth0.yml b/packs/auth0.yml index 820572892..5ffb82d9d 100644 --- a/packs/auth0.yml +++ b/packs/auth0.yml @@ -5,6 +5,7 @@ PackDefinition: IDs: - Auth0.Custom.Role.Created - Auth0.Integration.Installed + - Auth0.MFA.Factor.Setting.Enabled - Auth0.MFA.Policy.Disabled - Auth0.MFA.Policy.Enabled - Auth0.MFA.Risk.Assessment.Disabled diff --git a/packs/aws.yml b/packs/aws.yml index 4731a9f09..240f46368 100644 --- a/packs/aws.yml +++ b/packs/aws.yml @@ -49,6 +49,7 @@ PackDefinition: # Root Activity - AWS.CloudTrail.RootAccessKeyCreated - AWS.CloudTrail.RootPasswordChanged + - AWS.Console.RootLogin - AWS.Console.RootLoginFailed - AWS.EC2.Instance.DetailedMonitoring - AWS.Root.Activity @@ -110,6 +111,7 @@ PackDefinition: - AWS.GuardDuty.MediumSeverityFinding - AWS.IAM.Policy.AdministrativePrivileges - AWS.RDS.InstanceHighAvailability + - AWS.RDS.ManualSnapshotCreated - AWS.RDS.MasterPasswordUpdated - AWS.RDS.PublicRestore - AWS.RDS.SnapshotShared diff --git a/packs/carbonblack.yml b/packs/carbonblack.yml index dbdba1d36..7a815c6ad 100644 --- a/packs/carbonblack.yml +++ b/packs/carbonblack.yml @@ -3,6 +3,7 @@ PackID: PantherManaged.CarbonBlack Description: Group of all Carbon Black detections PackDefinition: IDs: + - CarbonBlack.AlertV2.Passthrough - CarbonBlack.Audit.Admin.Grant - CarbonBlack.Audit.API.Key.Created.Retrieved - CarbonBlack.Audit.Data.Forwarder.Stopped diff --git a/packs/cloudflare.yml b/packs/cloudflare.yml index 003d30f21..332d00808 100644 --- a/packs/cloudflare.yml +++ b/packs/cloudflare.yml @@ -6,6 +6,8 @@ PackDefinition: IDs: - Cloudflare.Firewall.L7DDoS - Cloudflare.Firewall.SuspiciousEventGreyNoise + - Cloudflare.HttpRequest.BotHighVolume + - Cloudflare.HttpRequest.BotHighVolumeGreyNoise # Globals used in these rules/policies - panther_base_helpers - panther_cloudflare_helpers diff --git a/packs/gcp_audit.yml b/packs/gcp_audit.yml index bbd68c422..f06f7b71f 100644 --- a/packs/gcp_audit.yml +++ b/packs/gcp_audit.yml @@ -7,9 +7,9 @@ PackDefinition: - GCP.Access.Attempts.Violating.VPC.Service.Controls - GCP.BigQuery.Large.Scan - GCP.Cloud.Storage.Buckets.Modified.Or.Deleted - - GCP.CloudBuild.Potential.Privilege.Escalation - - GCP.Cloudfunctions.Functions.Create - - GCP.Cloudfunctions.Functions.Update + - GCP.CloudBuild.Potential.Privilege.Escalation.Simple + - GCP.Cloudfunctions.Functions.Create.Simple + - GCP.Cloudfunctions.Functions.Update.Simple - GCP.Destructive.Queries - GCP.DNS.Zone.Modified.or.Deleted - GCP.Firewall.Rule.Created @@ -17,21 +17,24 @@ PackDefinition: - GCP.Firewall.Rule.Modified - GCP.GCS.IAMChanges - GCP.GCS.Public + - GCP.GKE.Kubernetes.Cron.Job.Created.Or.Modified.Simple - GCP.IAM.CorporateEmail - GCP.IAM.CustomRoleChanges - GCP.IAM.OrgFolderIAMChanges - - GCP.iam.roles.update.Privilege.Escalation - - GCP.iam.serviceAccountKeys.create + - GCP.iam.roles.update.Privilege.Escalation.Simple + - GCP.iam.serviceAccountKeys.create.Simple - GCP.Inbound.SSO.Profile.Created + - GCP.K8s.New.Daemonset.Deployed.Simple - GCP.Log.Bucket.Or.Sink.Deleted - GCP.Logging.Settings.Modified - GCP.Logging.Sink.Modified - GCP.Permissions.Granted.to.Create.or.Manage.Service.Account.Key - - GCP.Privilege.Escalation.By.Deployments.Create + - GCP.Privilege.Escalation.By.Deployments.Create.Simple - GCP.Service.Account.Access.Denied - GCP.Service.Account.or.Keys.Created - - GCP.serviceusage.apiKeys.create.Privilege.Escalation + - GCP.serviceusage.apiKeys.create.Privilege.Escalation.Simple - GCP.SQL.ConfigChanges + - GCP.Storage.Hmac.Keys.Create - GCP.User.Added.to.IAP.Protected.Service - GCP.VPC.Flow.Logs.Disabled - GCP.Workforce.Pool.Created.or.Updated @@ -39,11 +42,11 @@ PackDefinition: # Data model - Standard.GCP.AuditLog # Globals used in these rules/policies - - panther_base_helpers - - panther_event_type_helpers - gcp_base_helpers - gcp_environment + - panther_base_helpers - panther_config - panther_config_defaults - panther_config_overrides + - panther_event_type_helpers DisplayName: "Panther GCP Audit Pack" diff --git a/packs/netskope.yml b/packs/netskope.yml index bb4754f02..e2454b057 100644 --- a/packs/netskope.yml +++ b/packs/netskope.yml @@ -3,7 +3,7 @@ PackID: PantherManaged.Netskope Description: Group of all Netskope detections PackDefinition: IDs: - - Netskope.AdminLoggedOutLoginFailures - - Netskope.AdminUserChange - - Netskope.NetskopePersonnelActivity + - Netskope.AdminLoggedOutLoginFailures.Simple + - Netskope.AdminUserChange.Simple + - Netskope.NetskopePersonnelActivity.Simple DisplayName: "Panther Netskope Pack" diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_unsuccessful_mfa_attempt.yml b/rules/aws_cloudtrail_rules/aws_cloudtrail_unsuccessful_mfa_attempt.yml index 138703915..a7f7004e1 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_unsuccessful_mfa_attempt.yml +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_unsuccessful_mfa_attempt.yml @@ -4,6 +4,8 @@ DisplayName: "AWS Unsuccessful MFA attempt" Enabled: false Filename: aws_cloudtrail_unsuccessful_mfa_attempt.py Reference: https://attack.mitre.org/techniques/T1621/ +Tags: + - Configuration Required # configure threshold for multiple MFA failures Reports: MITRE ATT&CK: - TA0006:T1621 diff --git a/rules/aws_cloudtrail_rules/aws_console_root_login.yml b/rules/aws_cloudtrail_rules/aws_console_root_login.yml index e5096871f..1fef21db3 100644 --- a/rules/aws_cloudtrail_rules/aws_console_root_login.yml +++ b/rules/aws_cloudtrail_rules/aws_console_root_login.yml @@ -2,7 +2,7 @@ AnalysisType: rule Filename: aws_console_root_login.py RuleID: "AWS.Console.RootLogin" DisplayName: "Root Console Login" -Enabled: false +Enabled: true DedupPeriodMinutes: 15 LogTypes: - AWS.CloudTrail diff --git a/rules/aws_cloudtrail_rules/aws_lambda_crud.yml b/rules/aws_cloudtrail_rules/aws_lambda_crud.yml index 8ca1cb70c..879053ce1 100644 --- a/rules/aws_cloudtrail_rules/aws_lambda_crud.yml +++ b/rules/aws_cloudtrail_rules/aws_lambda_crud.yml @@ -8,6 +8,7 @@ LogTypes: Tags: - AWS - Security Control + - Configuration Required Reports: CIS: - 3.12 diff --git a/rules/aws_cloudtrail_rules/aws_rds_manual_snapshot_created.yml b/rules/aws_cloudtrail_rules/aws_rds_manual_snapshot_created.yml index 54fb358aa..8fc26baff 100644 --- a/rules/aws_cloudtrail_rules/aws_rds_manual_snapshot_created.yml +++ b/rules/aws_cloudtrail_rules/aws_rds_manual_snapshot_created.yml @@ -2,7 +2,7 @@ AnalysisType: rule Filename: aws_rds_manual_snapshot_created.py RuleID: "AWS.RDS.ManualSnapshotCreated" DisplayName: "AWS RDS Manual/Public Snapshot Created" -Enabled: false +Enabled: true LogTypes: - AWS.CloudTrail Tags: diff --git a/rules/aws_cloudtrail_rules/aws_software_discovery.yml b/rules/aws_cloudtrail_rules/aws_software_discovery.yml index a2493852f..199471ef4 100644 --- a/rules/aws_cloudtrail_rules/aws_software_discovery.yml +++ b/rules/aws_cloudtrail_rules/aws_software_discovery.yml @@ -4,6 +4,8 @@ DisplayName: "AWS Software Discovery" Enabled: false Filename: aws_software_discovery.py Reference: https://attack.mitre.org/techniques/T1518/001/ +Tags: + - Configuration Required Reports: MITRE ATT&CK: - TA0007:T1518 diff --git a/rules/aws_cloudtrail_rules/aws_unused_region.yml b/rules/aws_cloudtrail_rules/aws_unused_region.yml index e3b0d873f..a38b3b996 100644 --- a/rules/aws_cloudtrail_rules/aws_unused_region.yml +++ b/rules/aws_cloudtrail_rules/aws_unused_region.yml @@ -8,6 +8,7 @@ LogTypes: Tags: - AWS - Defense Evasion:Unused/Unsupported Cloud Regions + - Configuration Required Reports: MITRE ATT&CK: - TA0005:T1535 diff --git a/rules/carbonblack_rules/cb_passthrough.yml b/rules/carbonblack_rules/cb_passthrough.yml index ec1a28024..67e239668 100644 --- a/rules/carbonblack_rules/cb_passthrough.yml +++ b/rules/carbonblack_rules/cb_passthrough.yml @@ -4,7 +4,7 @@ Description: This rule enriches and contextualizes security alerts generated by DisplayName: Carbon Black Passthrough Rule Runbook: Review the Carbon Black alert details to determine what malicious behavior was detected, and whether or not it was blocked. Use the Reference link to view the alert in the Carbon Black console and take remediating actions if necessary. Reference: https://docs.vmware.com/en/VMware-Carbon-Black-Cloud/services/carbon-black-cloud-user-guide/GUID-0B68199D-6411-45D1-AE0D-2AB9B7A28513.html -Enabled: false +Enabled: true Filename: cb_passthrough.py LogTypes: - CarbonBlack.AlertV2 diff --git a/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.py b/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.py new file mode 100644 index 000000000..beb37b249 --- /dev/null +++ b/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.py @@ -0,0 +1,62 @@ +from gcp_base_helpers import gcp_alert_context +from panther_base_helpers import deep_get + +REQUIRED_PERMISSIONS = [ + "compute.disks.create", + "compute.instances.create", + "compute.instances.setMetadata", + "compute.instances.setServiceAccount", + "compute.subnetworks.use", + "compute.subnetworks.useExternalIp", +] + + +def rule(event): + if deep_get(event, "protoPayload", "response", "error"): + return False + + method = deep_get(event, "protoPayload", "methodName") + if not method.endswith("compute.instances.insert"): + return False + + authorization_info = deep_get(event, "protoPayload", "authorizationInfo") + if not authorization_info: + return False + + granted_permissions = {} + for auth in authorization_info: + granted_permissions[auth["permission"]] = auth["granted"] + for permission in REQUIRED_PERMISSIONS: + if not granted_permissions.get(permission): + return False + + return True + + +def title(event): + actor = deep_get( + event, "protoPayload", "authenticationInfo", "principalEmail", default="" + ) + + service_accounts = deep_get(event, "protoPayload", "request", "serviceAccounts") + if not service_accounts: + service_account_emails = "" + else: + service_account_emails = [service_acc["email"] for service_acc in service_accounts] + + project = deep_get(event, "resource", "labels", "project_id", default="") + return ( + f"[GCP]: [{actor}] created a new Compute Engine instance with [{service_account_emails}] " + f"Service Account on project [{project}]" + ) + + +def alert_context(event): + context = gcp_alert_context(event) + service_accounts = deep_get(event, "protoPayload", "request", "serviceAccounts") + if not service_accounts: + service_account_emails = "" + else: + service_account_emails = [service_acc["email"] for service_acc in service_accounts] + context["serviceAccount"] = service_account_emails + return context diff --git a/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.yml b/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.yml new file mode 100644 index 000000000..a0b4cd0e0 --- /dev/null +++ b/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.yml @@ -0,0 +1,346 @@ +AnalysisType: rule +LogTypes: + - GCP.AuditLog +Description: Detects compute.instances.create method for privilege escalation in GCP. +DisplayName: "GCP compute.instances.create Privilege Escalation" +RuleID: "GCP.compute.instances.create.Privilege.Escalation" +Enabled: true +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 +Severity: High +Filename: gcp_computeinstances_create_privilege_escalation.py +DedupPeriodMinutes: 60 +Threshold: 1 +Tests: + - + Name: GCP compute.instances - Potential Privilege Escalation + ExpectedResult: true + Log: + { + "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": "compute.instances.create", + "resource": "projects/some-project/zones/us-central1-f/instances/abc", + "resourceAttributes": { + "name": "projects/some-project/zones/us-central1-f/instances/abc", + "service": "compute", + "type": "compute.instances" + } + }, + { + "granted": true, + "permission": "compute.disks.create", + "resource": "projects/some-project/zones/us-central1-f/disks/abc", + "resourceAttributes": { + "name": "projects/some-project/zones/us-central1-f/disks/abc", + "service": "compute", + "type": "compute.disks" + } + }, + { + "granted": true, + "permission": "compute.subnetworks.use", + "resource": "projects/some-project/regions/us-central1/subnetworks/default", + "resourceAttributes": { + "name": "projects/some-project/regions/us-central1/subnetworks/default", + "service": "compute", + "type": "compute.subnetworks" + } + }, + { + "granted": true, + "permission": "compute.subnetworks.useExternalIp", + "resource": "projects/some-project/regions/us-central1/subnetworks/default", + "resourceAttributes": { + "name": "projects/some-project/regions/us-central1/subnetworks/default", + "service": "compute", + "type": "compute.subnetworks" + } + }, + { + "granted": true, + "permission": "compute.instances.setMetadata", + "resource": "projects/some-project/zones/us-central1-f/instances/abc", + "resourceAttributes": { + "name": "projects/some-project/zones/us-central1-f/instances/abc", + "service": "compute", + "type": "compute.instances" + } + }, + { + "granted": true, + "permission": "compute.instances.setServiceAccount", + "resource": "projects/some-project/zones/us-central1-f/instances/abc", + "resourceAttributes": { + "name": "projects/some-project/zones/us-central1-f/instances/abc", + "service": "compute", + "type": "compute.instances" + } + } + ], + "methodName": "v1.compute.instances.insert", + "request": { + "@type": "type.googleapis.com/compute.instances.insert", + "disks": ..., + "machineType": ..., + "name": ..., + "networkInterfaces": ..., + "serviceAccounts": [ + { + "email": "abcmail@some-project.iam.gserviceaccount.com", + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/iam" + ] + } + ] + }, + "requestMetadata": { + "callerIP": "1.2.3.4", + "callerSuppliedUserAgent": "(gzip),gzip(gfe)", + "destinationAttributes": { }, + "requestAttributes": { + "auth": { }, + "time": "2024-01-30T12:52:36.003867Z" + } + }, + "resourceLocation": ..., + "resourceName": "projects/some-project/zones/us-central1-f/instances/abc", + "response": { + "@type": "type.googleapis.com/operation", + "id": "8758546889396539388", + "insertTime": "2024-01-30T04:52:35.886-08:00", + "name": "operation-1706619154623-610293c7a6a25-934f1c35-1efebb12", + "operationType": "insert", + "progress": "0", + "selfLink": "https://www.googleapis.com/compute/v1/projects/some-project/zones/us-central1-f/operations/operation-1706619154623-610293c7a6a25-934f1c35-1efebb12", + "selfLinkWithId": "https://www.googleapis.com/compute/v1/projects/some-project/zones/us-central1-f/operations/8758546889396539388", + "startTime": "2024-01-30T04:52:35.887-08:00", + "status": "RUNNING", + "targetId": "1454427709413609468", + "targetLink": "https://www.googleapis.com/compute/v1/projects/some-project/zones/us-central1-f/instances/abc", + "user": "some.user@company.com", + "zone": "https://www.googleapis.com/compute/v1/projects/some-project/zones/us-central1-f" + }, + "serviceName": "compute.googleapis.com" + }, + "receiveTimestamp": "2024-01-30 12:52:36.642422049", + "resource": { + "labels": { + "instance_id": "1454427709413609468", + "project_id": "some-project", + "zone": "us-central1-f" + }, + "type": "gce_instance" + }, + "severity": "NOTICE", + "timestamp": "2024-01-30 12:52:34.676384000" + } + - + Name: GCP compute.instances - Error + ExpectedResult: false + Log: + { + "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": "compute.instances.create", + "resource": "projects/some-project/zones/us-central1-f/instances/abc", + "resourceAttributes": { + "name": "projects/some-project/zones/us-central1-f/instances/abc", + "service": "compute", + "type": "compute.instances" + } + }, + { + "granted": true, + "permission": "compute.disks.create", + "resource": "projects/some-project/zones/us-central1-f/disks/abc", + "resourceAttributes": { + "name": "projects/some-project/zones/us-central1-f/disks/abc", + "service": "compute", + "type": "compute.disks" + } + }, + { + "granted": true, + "permission": "compute.subnetworks.use", + "resource": "projects/some-project/regions/us-central1/subnetworks/default", + "resourceAttributes": { + "name": "projects/some-project/regions/us-central1/subnetworks/default", + "service": "compute", + "type": "compute.subnetworks" + } + }, + { + "granted": true, + "permission": "compute.subnetworks.useExternalIp", + "resource": "projects/some-project/regions/us-central1/subnetworks/default", + "resourceAttributes": { + "name": "projects/some-project/regions/us-central1/subnetworks/default", + "service": "compute", + "type": "compute.subnetworks" + } + }, + { + "granted": true, + "permission": "compute.instances.setMetadata", + "resource": "projects/some-project/zones/us-central1-f/instances/abc", + "resourceAttributes": { + "name": "projects/some-project/zones/us-central1-f/instances/abc", + "service": "compute", + "type": "compute.instances" + } + }, + { + "granted": true, + "permission": "compute.instances.setServiceAccount", + "resource": "projects/some-project/zones/us-central1-f/instances/abc", + "resourceAttributes": { + "name": "projects/some-project/zones/us-central1-f/instances/abc", + "service": "compute", + "type": "compute.instances" + } + } + ], + "methodName": "v1.compute.instances.insert", + "request": { + "@type": ..., + "disks": ..., + "machineType": ..., + "name": ..., + "networkInterfaces": ..., + "serviceAccounts": [ + { + "email": "abcmail@some-project.iam.gserviceaccount.com", + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/iam" + ] + } + ] + }, + "requestMetadata": ..., + "resourceLocation": ..., + "resourceName": "projects/some-project/zones/us-central1-f/instances/abc", + "response": { + "@type": "type.googleapis.com/error", + "error": { + "code": 404, + "errors": [ + { + "domain": "global", + "message": "The resource 'abc' was not found", + "reason": "notFound" + } + ], + "message": "The resource 'abc' was not found" + } + }, + "serviceName": "compute.googleapis.com", + "status": { + "code": 5, + "message": "The resource 'abc' was not found" + } + }, + "receiveTimestamp": "2024-01-30 11:03:56.719662927", + "resource": { + "labels": { + "instance_id": "", + "project_id": "some-project", + "zone": "us-central1-f" + }, + "type": "gce_instance" + }, + "severity": "ERROR", + "timestamp": "2024-01-30 11:03:55.700872000" + } + - Name: GCP compute.instances - Not All Permissions + ExpectedResult: false + Log: + { + "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": "compute.instances.create", + "resource": "projects/some-project/zones/us-central1-f/instances/abc", + "resourceAttributes": { + "name": "projects/some-project/zones/us-central1-f/instances/abc", + "service": "compute", + "type": "compute.instances" + } + }, + { + "granted": true, + "permission": "compute.disks.create", + "resource": "projects/some-project/zones/us-central1-f/disks/abc", + "resourceAttributes": { + "name": "projects/some-project/zones/us-central1-f/disks/abc", + "service": "compute", + "type": "compute.disks" + } + }, + { + "granted": true, + "permission": "compute.subnetworks.use", + "resource": "projects/some-project/regions/us-central1/subnetworks/default", + "resourceAttributes": { + "name": "projects/some-project/regions/us-central1/subnetworks/default", + "service": "compute", + "type": "compute.subnetworks" + } + }, + { + "granted": true, + "permission": "compute.subnetworks.useExternalIp", + "resource": "projects/some-project/regions/us-central1/subnetworks/default", + "resourceAttributes": { + "name": "projects/some-project/regions/us-central1/subnetworks/default", + "service": "compute", + "type": "compute.subnetworks" + } + }, + { + "granted": true, + "permission": "compute.instances.setMetadata", + "resource": "projects/some-project/zones/us-central1-f/instances/abc", + "resourceAttributes": { + "name": "projects/some-project/zones/us-central1-f/instances/abc", + "service": "compute", + "type": "compute.instances" + } + }, + ], + "methodName": "v1.compute.instances.insert", + "request": ..., + "requestMetadata": ..., + "resourceLocation": ..., + "resourceName": ..., + "response": ..., + "serviceName": ... + }, + "receiveTimestamp": ..., + "resource": ... + } diff --git a/rules/gcp_audit_rules/gcp_gcs_public.py b/rules/gcp_audit_rules/gcp_gcs_public.py index 02dabe399..28743607d 100644 --- a/rules/gcp_audit_rules/gcp_gcs_public.py +++ b/rules/gcp_audit_rules/gcp_gcs_public.py @@ -12,7 +12,7 @@ def rule(event): if not service_data: return False - # Reference: bit.ly/2WsJdZS + # Reference: https://cloud.google.com/iam/docs/policies binding_deltas = deep_get(service_data, "policyDelta", "bindingDeltas") if not binding_deltas: return False diff --git a/rules/gcp_audit_rules/gcp_iam_admin_role_assigned.yml b/rules/gcp_audit_rules/gcp_iam_admin_role_assigned.yml index 7cfcb7e17..84c0020fa 100644 --- a/rules/gcp_audit_rules/gcp_iam_admin_role_assigned.yml +++ b/rules/gcp_audit_rules/gcp_iam_admin_role_assigned.yml @@ -9,6 +9,8 @@ Tags: - GCP - Identity & Access Management - Privilege Escalation:Valid Accounts + - Configuration Required + - Deprecated Reports: MITRE ATT&CK: - TA0004:T1078 diff --git a/rules/gcp_audit_rules/gcp_storage_hmac_keys_create.yml b/rules/gcp_audit_rules/gcp_storage_hmac_keys_create.yml new file mode 100644 index 000000000..e2da538a9 --- /dev/null +++ b/rules/gcp_audit_rules/gcp_storage_hmac_keys_create.yml @@ -0,0 +1,57 @@ +AnalysisType: rule +RuleID: "GCP.Storage.Hmac.Keys.Create" +DisplayName: "GCP storage hmac keys create" +Description: "There is a feature of Cloud Storage, “interoperability”, that provides a way for Cloud Storage to interact with storage offerings from other cloud providers, like AWS S3. As part of that, there are HMAC keys that can be created for both Service Accounts and regular users. We can escalate Cloud Storage permissions by creating an HMAC key for a higher-privileged Service Account." +Enabled: true +LogTypes: + - GCP.AuditLog +Severity: High +DedupPeriodMinutes: 60 +Threshold: 1 +Reference: https://rhinosecuritylabs.com/cloud-security/privilege-escalation-google-cloud-platform-part-2/ +Reports: + MITRE ATT&CK: + - TA0004:T1548 +Detection: + - All: + - KeyPath: protoPayload.authorizationInfo[*].granted + Condition: Contains + Value: true + - KeyPath: protoPayload.authorizationInfo[*].permission + Condition: Contains + Value: storage.hmacKeys.create +Tests: + - Name: privilege-escalation + ExpectedResult: true + Log: + protoPayload: + authorizationInfo: + - granted: true + permission: storage.hmacKeys.create + methodName: v2.deploymentmanager.deployments.insert + serviceName: deploymentmanager.googleapis.com + receiveTimestamp: "2024-01-19 13:47:19.465856238" + resource: + labels: + name: test-vm-deployment + project_id: panther-threat-research + type: deployment + severity: NOTICE + timestamp: "2024-01-19 13:47:18.279921000" + - Name: fail + ExpectedResult: false + Log: + protoPayload: + authorizationInfo: + - granted: false + permission: storage.hmacKeys.create + methodName: v2.deploymentmanager.deployments.insert + serviceName: deploymentmanager.googleapis.com + receiveTimestamp: "2024-01-19 13:47:19.465856238" + resource: + labels: + name: test-vm-deployment + project_id: panther-threat-research + type: deployment + severity: NOTICE + timestamp: "2024-01-19 13:47:18.279921000" diff --git a/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.py b/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.py new file mode 100644 index 000000000..e64e8ef7a --- /dev/null +++ b/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.py @@ -0,0 +1,71 @@ +from gcp_base_helpers import gcp_alert_context +from panther_base_helpers import deep_get, deep_walk + + +SUSPICIOUS_PATHS = [ + "/var/run/docker.sock", + "/var/run/crio/crio.sock", + "/var/lib/kubelet", + "/var/lib/kubelet/pki", + "/var/lib/docker/overlay2", + "/etc/kubernetes", + "/etc/kubernetes/manifests", + "/etc/kubernetes/pki", + "/home/admin", +] + + +def rule(event): + if deep_get(event, "protoPayload", "response", "status") == "Failure": + return False + + if deep_get(event, "protoPayload", "methodName") not in ( + "io.k8s.core.v1.pods.create", + "io.k8s.core.v1.pods.update", + "io.k8s.core.v1.pods.patch", + ): + return False + + volume_mount_path = deep_walk( + event, "protoPayload", "request", "spec", "volumes", "hostPath", "path" + ) + if volume_mount_path not in SUSPICIOUS_PATHS and not any( + path in SUSPICIOUS_PATHS for path in volume_mount_path + ): + return False + + authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + for auth in authorization_info: + if ( + auth.get("permission") + in ( + "io.k8s.core.v1.pods.create", + "io.k8s.core.v1.pods.update", + "io.k8s.core.v1.pods.patch", + ) + and auth.get("granted") is True + ): + return True + return False + + +def title(event): + actor = deep_get( + event, "protoPayload", "authenticationInfo", "principalEmail", default="" + ) + pod_name = deep_get(event, "protoPayload", "resourceName", default="") + project_id = deep_get(event, "resource", "labels", "project_id", default="") + + return ( + f"[GCP]: [{actor}] created k8s pod [{pod_name}] with a hostPath volume mount " + f"in project [{project_id}]" + ) + + +def alert_context(event): + context = gcp_alert_context(event) + volume_mount_path = deep_walk( + event, "protoPayload", "request", "spec", "volumes", "hostPath", "path" + ) + context["volume_mount_path"] = volume_mount_path + return context diff --git a/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.yml b/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.yml new file mode 100644 index 000000000..b59e46a7d --- /dev/null +++ b/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.yml @@ -0,0 +1,287 @@ +AnalysisType: rule +RuleID: "GCP.K8S.Pot.Create.Or.Modify.Host.Path.Volume.Mount" +DisplayName: "GCP K8S Pot Create Or Modify Host Path Volume Mount" +Enabled: true +LogTypes: + - GCP.AuditLog +Severity: High +Description: > + This detection monitors for pod creation with a hostPath volume mount. The attachment to a node's volume can allow + for privilege escalation through underlying vulnerabilities or it can open up possibilities for data exfiltration + or unauthorized file access. It is very rare to see this being a pod requirement. +Runbook: > + Investigate the reason of adding hostPath volume mount. Advise that it is discouraged practice. + Create ticket if appropriate. +Reference: https://linuxhint.com/kubernetes-hostpath-volumes/ +Reports: + MITRE ATT&CK: + - TA0001 # Initial Access + - TA0002 # Execution +Filename: gcp_k8s_pod_create_or_modify_host_path_vol_mount.py +Tests: + - + Name: Pod With Suspicious Volume Mount Created + ExpectedResult: true + Log: + { + "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" + }, + "authorizationInfo": [ + { + "granted": true, + "permission": "io.k8s.core.v1.pods.create", + "resource": "core/v1/namespaces/default/pods/test" + } + ], + "methodName": "io.k8s.core.v1.pods.create", + "request": { + "@type": "core.k8s.io/v1.Pod", + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test", + "namespace": "default" + }, + "spec": { + "containers": [ + { + "image": "nginx", + "imagePullPolicy": "Always", + "name": "test", + "volumeMounts": [ + { + "mountPath": "/test", + "name": "test-volume" + } + ] + } + ], + "volumes": [ + { + "hostPath": { + "path": "/var/lib/kubelet", + "type": "DirectoryOrCreate" + }, + "name": "test-volume" + } + ] + }, + }, + "requestMetadata": { + "callerIP": "1.2.3.4", + "callerSuppliedUserAgent": "kubectl/v1.28.2 (darwin/amd64) kubernetes/89a4ea3" + }, + "resourceName": "core/v1/namespaces/default/pods/test", + "response": { + "spec": { + "containers": [ + { + "image": "nginx", + "imagePullPolicy": "Always", + "name": "test", + "volumeMounts": [ + { + "mountPath": "/test", + "name": "test-volume" + }, + ] + } + ], + "volumes": [ + { + "hostPath": { + "path": "/var/lib/kubelet", + "type": "DirectoryOrCreate" + }, + "name": "test-volume" + }, + ] + }, + "status": { + "phase": "Pending", + "qosClass": "BestEffort" + } + }, + }, + "receiveTimestamp": "2024-02-16 11:48:43.531373988", + "resource": { + "labels": { + "cluster_name": "some-project-cluster", + "location": "us-west1", + "project_id": "some-project" + }, + "type": "k8s_cluster" + }, + "timestamp": "2024-02-16 11:48:22.742154000" + } + - Name: Pod With Non-Suspicious Volume Mount Created + ExpectedResult: false + Log: + { + "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" + }, + "authorizationInfo": [ + { + "granted": true, + "permission": "io.k8s.core.v1.pods.create", + "resource": "core/v1/namespaces/default/pods/test" + } + ], + "methodName": "io.k8s.core.v1.pods.create", + "request": { + "@type": "core.k8s.io/v1.Pod", + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test", + "namespace": "default" + }, + "spec": { + "containers": [ + { + "image": "nginx", + "imagePullPolicy": "Always", + "name": "test", + "volumeMounts": [ + { + "mountPath": "/test", + "name": "test-volume" + } + ] + } + ], + "volumes": [ + { + "hostPath": { + "path": "/data", + "type": "DirectoryOrCreate" + }, + "name": "test-volume" + } + ] + }, + }, + "requestMetadata": { + "callerIP": "1.2.3.4", + "callerSuppliedUserAgent": "kubectl/v1.28.2 (darwin/amd64) kubernetes/89a4ea3" + }, + "resourceName": "core/v1/namespaces/default/pods/test", + "response": { + "spec": { + "containers": [ + { + "image": "nginx", + "imagePullPolicy": "Always", + "name": "test", + "volumeMounts": [ + { + "mountPath": "/test", + "name": "test-volume" + }, + ] + } + ], + "volumes": [ + { + "hostPath": { + "path": "/data", + "type": "DirectoryOrCreate" + }, + "name": "test-volume" + }, + ] + }, + "status": { + "phase": "Pending", + "qosClass": "BestEffort" + } + }, + }, + "receiveTimestamp": "2024-02-16 11:48:43.531373988", + "resource": { + "labels": { + "cluster_name": "some-project-cluster", + "location": "us-west1", + "project_id": "some-project" + }, + "type": "k8s_cluster" + }, + "timestamp": "2024-02-16 11:48:22.742154000" + } + - + Name: Pod Not Created + ExpectedResult: False + Log: + { + "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" + }, + "authorizationInfo": [ + { + "granted": true, + "permission": "io.k8s.core.v1.pods.create", + "resource": "core/v1/namespaces/default/pods/test" + } + ], + "methodName": "io.k8s.core.v1.pods.create", + "request": { + "@type": "core.k8s.io/v1.Pod", + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test", + "namespace": "default" + }, + "spec": { + "containers": [ + { + "image": "nginx", + "imagePullPolicy": "Always", + "name": "test", + "volumeMounts": [ + { + "mountPath": "/test", + "name": "test-volume" + } + ] + } + ], + "volumes": [ + { + "hostPath": { + "path": "/var/lib/kubelet", + "type": "DirectoryOrCreate" + }, + "name": "test-volume" + } + ] + }, + "status": { } + }, + "resourceName": "core/v1/namespaces/default/pods/test", + "response": { + "status": "Failure" + }, + }, + "receiveTimestamp": "2024-02-16 12:55:17.003485190", + "resource": { + "labels": { + "cluster_name": "some-project-cluster", + "location": "us-west1", + "project_id": "some-project" + }, + "type": "k8s_cluster" + }, + "timestamp": "2024-02-16 12:55:00.510160000" + } diff --git a/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.py b/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.py new file mode 100644 index 000000000..283ca7b42 --- /dev/null +++ b/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.py @@ -0,0 +1,40 @@ +from gcp_base_helpers import gcp_alert_context +from panther_base_helpers import deep_get, deep_walk + + +def rule(event): + if deep_get(event, "protoPayload", "response", "status") == "Failure": + return False + + if deep_get(event, "protoPayload", "methodName") != "io.k8s.core.v1.pods.create": + return False + + authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + containers_info = deep_walk(event, "protoPayload", "response", "spec", "containers") + for auth in authorization_info: + if auth.get("permission") == "io.k8s.core.v1.pods.create" and auth.get("granted") is True: + for security_context in containers_info: + if ( + deep_get(security_context, "securityContext", "privileged") is True + or deep_get(security_context, "securityContext", "runAsNonRoot") is False + ): + return True + + return False + + +def title(event): + actor = deep_get( + event, "protoPayload", "authenticationInfo", "principalEmail", default="" + ) + pod_name = deep_get(event, "protoPayload", "resourceName", default="") + project_id = deep_get(event, "resource", "labels", "project_id", default="") + + return f"[GCP]: [{actor}] created a privileged pod [{pod_name}] in project [{project_id}]" + + +def alert_context(event): + context = gcp_alert_context(event) + containers_info = deep_walk(event, "protoPayload", "response", "spec", "containers") + context["pod_security_context"] = [i.get("securityContext") for i in containers_info] + return context diff --git a/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.yml b/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.yml new file mode 100644 index 000000000..9d85349a6 --- /dev/null +++ b/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.yml @@ -0,0 +1,344 @@ +AnalysisType: rule +RuleID: "GCP.K8S.Privileged.Pod.Created" +DisplayName: "GCP K8S Privileged Pod Created" +Enabled: true +LogTypes: + - GCP.AuditLog +Severity: High +Filename: gcp_k8s_privileged_pod_created.py +Description: > + Alerts when a user creates privileged pod. These particular pods have full access to the host’s namespace and + devices, have the ability to exploit the kernel, have dangerous linux capabilities, and can be a powerful launching + point for further attacks. In the event of a successful container escape where a user is operating with root + privileges, the attacker retains this role on the node. +Runbook: > + Investigate the reason of creating privileged pod. Advise that it is discouraged practice. + Create ticket if appropriate. +Reference: https://www.golinuxcloud.com/kubernetes-privileged-pod-examples/ +Reports: + MITRE ATT&CK: + - TA0004:T1548 # Abuse Elevation Control Mechanism +Tests: + - + Name: Privileged Pod Created + ExpectedResult: true + Log: + { + "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity", + "operation": {}, + "protoPayload": { + "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog", + "authenticationInfo": { + "principalEmail": "john.doe@company.com" + }, + "authorizationInfo": [ + { + "granted": true, + "permission": "io.k8s.core.v1.pods.create", + "resource": "core/v1/namespaces/default/pods/test-privileged-pod" + } + ], + "methodName": "io.k8s.core.v1.pods.create", + "request": { + "@type": "core.k8s.io/v1.Pod", + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test-privileged-pod", + "namespace": "default" + }, + "spec": { + "containers": [ + { + "image": "nginx", + "imagePullPolicy": "Always", + "name": "nginx", + "resources": { }, + "securityContext": { + "privileged": true + }, + } + ], + "securityContext": { }, + }, + "status": { } + }, + "requestMetadata": { + "callerIP": "1.2.3.4", + }, + "resourceName": "core/v1/namespaces/default/pods/test-privileged-pod", + "response": { + "@type": "core.k8s.io/v1.Pod", + "apiVersion": "v1", + "kind": "Pod", + "metadata": {}, + "spec": { + "containers": [ + { + "image": "nginx", + "imagePullPolicy": "Always", + "name": "nginx", + "resources": { }, + "securityContext": { + "privileged": true + }, + } + ], + "securityContext": { }, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + }, + "status": {} + }, + "serviceName": "k8s.io", + "status": {} + }, + "receiveTimestamp": "2024-02-13 12:45:20.058795785", + "resource": { + "labels": { + "cluster_name": "some-project-cluster", + "location": "us-west1", + "project_id": "some-project" + }, + "type": "k8s_cluster" + }, + "timestamp": "2024-02-13 12:45:06.073905000" + } + - + Name: Run-As-Root Pod Created + ExpectedResult: true + Log: + { + "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity", + "operation": {}, + "protoPayload": { + "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog", + "authenticationInfo": { + "principalEmail": "john.doe@company.com" + }, + "authorizationInfo": [ + { + "granted": true, + "permission": "io.k8s.core.v1.pods.create", + "resource": "core/v1/namespaces/default/pods/test-runasroot-pod" + } + ], + "methodName": "io.k8s.core.v1.pods.create", + "request": { + "@type": "core.k8s.io/v1.Pod", + "apiVersion": "v1", + "kind": "Pod", + "metadata": {}, + "spec": { + "containers": [ + { + "image": "nginx", + "imagePullPolicy": "Always", + "name": "nginx", + "resources": { }, + "securityContext": { + "runAsNonRoot": false + }, + } + ], + }, + "status": { } + }, + "requestMetadata": { + "callerIP": "1.2.3.4", + }, + "resourceName": "core/v1/namespaces/default/pods/test-runasroot-pod", + "response": { + "@type": "core.k8s.io/v1.Pod", + "apiVersion": "v1", + "kind": "Pod", + "metadata": {}, + "spec": { + "containers": [ + { + "image": "nginx", + "imagePullPolicy": "Always", + "name": "nginx", + "resources": { }, + "securityContext": { + "runAsNonRoot": false + }, + } + ], + }, + "status": { + "phase": "Pending", + "qosClass": "BestEffort" + } + }, + "serviceName": "k8s.io", + "status": { } + }, + "receiveTimestamp": "2024-02-13 13:13:53.113465457", + "resource": { + "labels": { + "cluster_name": "some-project-cluster", + "location": "us-west1", + "project_id": "some-project" + }, + "type": "k8s_cluster" + }, + "timestamp": "2024-02-13 13:13:45.363388000" + } + - + Name: Non-Privileged Pod Created + ExpectedResult: false + Log: + { + "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity", + "operation": { + "first": true, + "id": "7f8c5bec-01ff-4079-97e3-065ac34e10e8", + "last": true, + "producer": "k8s.io" + }, + "protoPayload": { + "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog", + "authenticationInfo": { + "principalEmail": "john.doe@company.com" + }, + "authorizationInfo": [ + { + "granted": true, + "permission": "io.k8s.core.v1.pods.create", + "resource": "core/v1/namespaces/default/pods/test-non-privileged-pod" + } + ], + "methodName": "io.k8s.core.v1.pods.create", + "request": { + "@type": "core.k8s.io/v1.Pod", + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test-non-privileged-pod", + "namespace": "default" + }, + "spec": { + "containers": [ + { + "image": "nginx", + "imagePullPolicy": "Always", + "name": "nginx", + "resources": { }, + } + ], + }, + "status": { } + }, + "requestMetadata": { + "callerIP": "1.2.3.4", + }, + "resourceName": "core/v1/namespaces/default/pods/test-non-privileged-pod", + "response": { + "@type": "core.k8s.io/v1.Pod", + "apiVersion": "v1", + "kind": "Pod", + "metadata": {}, + "spec": { + "containers": [ + { + "image": "nginx", + "imagePullPolicy": "Always", + "name": "nginx", + "resources": { }, + } + ], + }, + "status": {} + }, + "serviceName": "k8s.io", + "status": { } + }, + "receiveTimestamp": "2024-02-13 13:07:54.642331675", + "resource": { + "labels": { + "cluster_name": "some-project-cluster", + "location": "us-west1", + "project_id": "some-project" + }, + "type": "k8s_cluster" + }, + "timestamp": "2024-02-13 13:07:29.505948000" + } + - Name: Error Creating Pod + ExpectedResult: false + Log: + { + "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity", + "protoPayload": { + "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog", + "authenticationInfo": { + "principalEmail": "john.doe@company.com" + }, + "authorizationInfo": [ + { + "granted": true, + "permission": "io.k8s.core.v1.pods.create", + "resource": "core/v1/namespaces/default/pods/test-privileged-pod" + } + ], + "methodName": "io.k8s.core.v1.pods.create", + "request": { + "@type": "core.k8s.io/v1.Pod", + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test-privileged-pod", + "namespace": "default" + }, + "spec": { + "containers": [ + { + "image": "nginx", + "imagePullPolicy": "Always", + "name": "nginx", + "resources": { }, + "securityContext": { + "runAsNonRoot": false + }, + } + ], + }, + "status": { } + }, + "requestMetadata": { + "callerIP": "1.2.3.4", + }, + "resourceName": "core/v1/namespaces/default/pods/test-privileged-pod", + "response": { + "@type": "core.k8s.io/v1.Status", + "apiVersion": "v1", + "code": 409, + "details": { + "kind": "pods", + "name": "test-privileged-pod" + }, + "kind": "Status", + "message": "pods \"test-privileged-pod\" already exists", + "metadata": { }, + "reason": "AlreadyExists", + "status": "Failure" + }, + "serviceName": "k8s.io", + "status": { + "code": 10, + "message": "pods \"test-privileged-pod\" already exists" + } + }, + "receiveTimestamp": "2024-02-13 13:13:33.486605432", + "resource": { + "labels": { + "cluster_name": "some-project-cluster", + "location": "us-west1", + "project_id": "some-project" + }, + "type": "k8s_cluster" + }, + "timestamp": "2024-02-13 13:13:24.079140000" + } diff --git a/rules/gsuite_activityevent_rules/gsuite_permissions_delegated.yml b/rules/gsuite_activityevent_rules/gsuite_permissions_delegated.yml index 22337695e..3738d16da 100644 --- a/rules/gsuite_activityevent_rules/gsuite_permissions_delegated.yml +++ b/rules/gsuite_activityevent_rules/gsuite_permissions_delegated.yml @@ -7,6 +7,8 @@ LogTypes: - GSuite.ActivityEvent Tags: - GSuite + - Configuration Required + - Deprecated Severity: Low Description: > A GSuite user was granted new administrator privileges. diff --git a/rules/notion_rules/notion_login_from_blocked_ip.yml b/rules/notion_rules/notion_login_from_blocked_ip.yml index af4e2134b..c47541ce1 100644 --- a/rules/notion_rules/notion_login_from_blocked_ip.yml +++ b/rules/notion_rules/notion_login_from_blocked_ip.yml @@ -9,6 +9,7 @@ Tags: - Notion - Network Security Monitoring - Malicious Connections + - Configuration Required Severity: Medium Description: "A user attempted to access Notion from a blocked IP address. Note: before deployinh, make sure to add Rule Filters checking if event.ip_address is in a certain CIDR range(s)." DedupPeriodMinutes: 60 diff --git a/rules/okta_rules/okta_idp_signin.yaml b/rules/okta_rules/okta_idp_signin.yaml index feade31b6..6409a618b 100644 --- a/rules/okta_rules/okta_idp_signin.yaml +++ b/rules/okta_rules/okta_idp_signin.yaml @@ -5,6 +5,8 @@ DisplayName: "Okta Identity Provider Sign-in" Enabled: false LogTypes: - Okta.SystemLog +Tags: + - Configuration Required Reports: MITRE ATT&CK: - TA0001:T1199 # Trusted Relationship diff --git a/rules/standard_rules/impossible_travel_login.py b/rules/standard_rules/impossible_travel_login.py index 9d718c741..8e4db4450 100644 --- a/rules/standard_rules/impossible_travel_login.py +++ b/rules/standard_rules/impossible_travel_login.py @@ -72,7 +72,7 @@ def rule(event): IS_PRIVATE_RELAY = all( [ deep_get(ipinfo_privacy, "relay", default=False), - deep_get(ipinfo_privacy, "service", default="") != "", + deep_get(ipinfo_privacy, "service", default="") == "Apple Private Relay", ] ) # We've found that some places, like WeWork locations, diff --git a/rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation.yml b/simple_rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation_simple.yml similarity index 97% rename from rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation.yml rename to simple_rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation_simple.yml index f7436ebf6..21313403f 100644 --- a/rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation.yml +++ b/simple_rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation_simple.yml @@ -5,7 +5,7 @@ Description: Detects privilege escalation attacks designed to gain access to the A user with permissions to start a new build with Cloud Build can gain access to the Cloud Build Service Account and abuse it for more access to the environment. DisplayName: "GCP CloudBuild Potential Privilege Escalation" -RuleID: "GCP.CloudBuild.Potential.Privilege.Escalation" +RuleID: "GCP.CloudBuild.Potential.Privilege.Escalation.Simple" Enabled: true Reference: https://rhinosecuritylabs.com/gcp/iam-privilege-escalation-gcp-cloudbuild/ Runbook: Confirm this was authorized and necessary behavior. To defend against this privilege escalation attack, @@ -29,8 +29,7 @@ Detection: - KeyPath: protoPayload.methodName Condition: EndsWith Value: CloudBuild.CreateBuild -AlertTitle: "[GCP]: [{protoPayload.authenticationInfo.principalEmail}] performed [{protoPayload.methodName}] on -project [{resource.labels.project_id}]" +AlertTitle: "[GCP]: [{protoPayload.authenticationInfo.principalEmail}] performed [{protoPayload.methodName}] on project [{resource.labels.project_id}]" AlertContext: - KeyName: project KeyValue: diff --git a/rules/gcp_audit_rules/gcp_cloudfunctions_functions_create.yml b/simple_rules/gcp_audit_rules/gcp_cloudfunctions_functions_create_simple.yml similarity index 95% rename from rules/gcp_audit_rules/gcp_cloudfunctions_functions_create.yml rename to simple_rules/gcp_audit_rules/gcp_cloudfunctions_functions_create_simple.yml index e4f438f5c..44412d504 100644 --- a/rules/gcp_audit_rules/gcp_cloudfunctions_functions_create.yml +++ b/simple_rules/gcp_audit_rules/gcp_cloudfunctions_functions_create_simple.yml @@ -1,5 +1,5 @@ AnalysisType: rule -RuleID: "GCP.Cloudfunctions.Functions.Create" +RuleID: "GCP.Cloudfunctions.Functions.Create.Simple" DisplayName: "GCP cloudfunctions functions create" Description: "The Identity and Access Management (IAM) service manages authorization and authentication for a GCP environment. This means that there are very likely multiple privilege escalation methods that use the IAM service and/or its permissions." Enabled: true @@ -54,4 +54,4 @@ Tests: project_id: panther-threat-research type: deployment severity: NOTICE - timestamp: "2024-01-19 13:47:18.279921000" \ No newline at end of file + timestamp: "2024-01-19 13:47:18.279921000" diff --git a/rules/gcp_audit_rules/gcp_cloudfunctions_functions_update.yml b/simple_rules/gcp_audit_rules/gcp_cloudfunctions_functions_update_simple.yml similarity index 95% rename from rules/gcp_audit_rules/gcp_cloudfunctions_functions_update.yml rename to simple_rules/gcp_audit_rules/gcp_cloudfunctions_functions_update_simple.yml index 1a83fe396..8b951cd09 100644 --- a/rules/gcp_audit_rules/gcp_cloudfunctions_functions_update.yml +++ b/simple_rules/gcp_audit_rules/gcp_cloudfunctions_functions_update_simple.yml @@ -1,5 +1,5 @@ AnalysisType: rule -RuleID: "GCP.Cloudfunctions.Functions.Update" +RuleID: "GCP.Cloudfunctions.Functions.Update.Simple" DisplayName: "GCP cloudfunctions functions update" Description: "The Identity and Access Management (IAM) service manages authorization and authentication for a GCP environment. This means that there are very likely multiple privilege escalation methods that use the IAM service and/or its permissions." Enabled: true @@ -54,4 +54,4 @@ Tests: project_id: panther-threat-research type: deployment severity: NOTICE - timestamp: "2024-01-19 13:47:18.279921000" \ No newline at end of file + timestamp: "2024-01-19 13:47:18.279921000" diff --git a/simple_rules/gcp_audit_rules/gcp_gke_kubernetes_cron_job_created_or_modified_simple.yml b/simple_rules/gcp_audit_rules/gcp_gke_kubernetes_cron_job_created_or_modified_simple.yml new file mode 100644 index 000000000..1311279c3 --- /dev/null +++ b/simple_rules/gcp_audit_rules/gcp_gke_kubernetes_cron_job_created_or_modified_simple.yml @@ -0,0 +1,71 @@ +AnalysisType: rule +RuleID: "GCP.GKE.Kubernetes.Cron.Job.Created.Or.Modified.Simple" +DisplayName: "GCP GKE Kubernetes Cron Job Created Or Modified" +Description: "This detection monitor for any modifications or creations of a cron job in GKE. Attackers may create or modify an existing scheduled job in order to achieve cluster persistence." +Enabled: true +LogTypes: + - GCP.AuditLog +Severity: Medium +DedupPeriodMinutes: 60 +Threshold: 1 +Reference: https://medium.com/snowflake/from-logs-to-detection-using-snowflake-and-panther-to-detect-k8s-threats-d72f70a504d7 +Detection: + - Any: + - KeyPath: protoPayload.authorizationInfo[*].permission + Condition: Contains + Value: io.k8s.batch.v1.cronjobs.create + - KeyPath: protoPayload.authorizationInfo[*].permission + Condition: Contains + Value: io.k8s.batch.v1.cronjobs.update +Tests: + - Name: create + ExpectedResult: true + Log: + protoPayload: + authorizationInfo: + - granted: true + permission: io.k8s.batch.v1.cronjobs.create + methodName: v2.deploymentmanager.deployments.insert + serviceName: deploymentmanager.googleapis.com + receiveTimestamp: "2024-01-19 13:47:19.465856238" + resource: + labels: + name: test-vm-deployment + project_id: panther-threat-research + type: deployment + severity: NOTICE + timestamp: "2024-01-19 13:47:18.279921000" + - Name: update + ExpectedResult: true + Log: + protoPayload: + authorizationInfo: + - granted: true + permission: io.k8s.batch.v1.cronjobs.update + methodName: v2.deploymentmanager.deployments.insert + serviceName: deploymentmanager.googleapis.com + receiveTimestamp: "2024-01-19 13:47:19.465856238" + resource: + labels: + name: test-vm-deployment + project_id: panther-threat-research + type: deployment + severity: NOTICE + timestamp: "2024-01-19 13:47:18.279921000" + - Name: fail + ExpectedResult: false + Log: + protoPayload: + authorizationInfo: + - granted: false + permission: cloudfunctions.functions.upsert + methodName: v2.deploymentmanager.deployments.insert + serviceName: deploymentmanager.googleapis.com + receiveTimestamp: "2024-01-19 13:47:19.465856238" + resource: + labels: + name: test-vm-deployment + project_id: panther-threat-research + type: deployment + severity: NOTICE + timestamp: "2024-01-19 13:47:18.279921000" diff --git a/rules/gcp_audit_rules/gcp_iam_roles_update_prielege_escalation.yml b/simple_rules/gcp_audit_rules/gcp_iam_roles_update_privilege_escalation_simple.yml similarity index 96% rename from rules/gcp_audit_rules/gcp_iam_roles_update_prielege_escalation.yml rename to simple_rules/gcp_audit_rules/gcp_iam_roles_update_privilege_escalation_simple.yml index 026a803d8..27c389a25 100644 --- a/rules/gcp_audit_rules/gcp_iam_roles_update_prielege_escalation.yml +++ b/simple_rules/gcp_audit_rules/gcp_iam_roles_update_privilege_escalation_simple.yml @@ -1,5 +1,5 @@ AnalysisType: rule -RuleID: "GCP.iam.roles.update.Privilege.Escalation" +RuleID: "GCP.iam.roles.update.Privilege.Escalation.Simple" DisplayName: "GCP iam.roles.update Privilege Escalation" Description: "If your user is assigned a custom IAM role, then iam.roles.update will allow you to update the “includedPermissons” on that role. Because it is assigned to you, you will gain the additional privileges, which could be anything you desire." Enabled: true diff --git a/rules/gcp_audit_rules/gcp_iam_service_account_key_create.yml b/simple_rules/gcp_audit_rules/gcp_iam_service_account_key_create_simple.yml similarity index 97% rename from rules/gcp_audit_rules/gcp_iam_service_account_key_create.yml rename to simple_rules/gcp_audit_rules/gcp_iam_service_account_key_create_simple.yml index f82fdca8b..5a1dd1599 100644 --- a/rules/gcp_audit_rules/gcp_iam_service_account_key_create.yml +++ b/simple_rules/gcp_audit_rules/gcp_iam_service_account_key_create_simple.yml @@ -1,5 +1,5 @@ AnalysisType: rule -RuleID: "GCP.iam.serviceAccountKeys.create" +RuleID: "GCP.iam.serviceAccountKeys.create.Simple" DisplayName: "GCP.Iam.ServiceAccountKeys.Create" Description: "If your user is assigned a custom IAM role, then iam.roles.update will allow you to update the “includedPermissons” on that role. Because it is assigned to you, you will gain the additional privileges, which could be anything you desire." Enabled: true diff --git a/rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create.yml b/simple_rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create_simple.yml similarity index 96% rename from rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create.yml rename to simple_rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create_simple.yml index 9e0c36159..1daa546c0 100644 --- a/rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create.yml +++ b/simple_rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create_simple.yml @@ -1,5 +1,5 @@ AnalysisType: rule -RuleID: "GCP.Privilege.Escalation.By.Deployments.Create" +RuleID: "GCP.Privilege.Escalation.By.Deployments.Create.Simple" DisplayName: "GCP.Privilege.Escalation.By.Deployments.Create" Description: "Detects privilege escalation in GCP by taking over the deploymentsmanager.deployments.create permission" Enabled: true diff --git a/rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation.yml b/simple_rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation_simple.yml similarity index 98% rename from rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation.yml rename to simple_rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation_simple.yml index 5ffdff704..b64970c64 100644 --- a/rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation.yml +++ b/simple_rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation_simple.yml @@ -5,7 +5,7 @@ Description: Detects serviceusage.apiKeys.create method for privilege escalation created with no restrictions, which means they have access to the entire GCP project they were created in. We can capitalize on that fact by creating a new API key that may have more privileges than our own user. DisplayName: "GCP serviceusage.apiKeys.create Privilege Escalation" -RuleID: "GCP.serviceusage.apiKeys.create.Privilege.Escalation" +RuleID: "GCP.serviceusage.apiKeys.create.Privilege.Escalation.Simple" Enabled: true Reference: https://rhinosecuritylabs.com/cloud-security/privilege-escalation-google-cloud-platform-part-2/ Runbook: Confirm this was authorized and necessary behavior. This is not a vulnerability in GCP, it is a vulnerability @@ -27,8 +27,7 @@ Detection: - KeyPath: protoPayload.methodName Condition: EndsWith Value: ApiKeys.CreateKey -AlertTitle: "[GCP]: [{protoPayload.authenticationInfo.principalEmail}] created new API Key in -project [{resource.labels.project_id}]" +AlertTitle: "[GCP]: [{protoPayload.authenticationInfo.principalEmail}] created new API Key in project [{resource.labels.project_id}]" AlertContext: - KeyName: project KeyValue: @@ -214,4 +213,4 @@ Tests: "resource": { }, "severity": "NOTICE", "timestamp": "2024-01-25 13:28:18.961519813" - } \ No newline at end of file + } diff --git a/simple_rules/gcp_k8s_rules/gcp_k8s_ioc_activity_simple.yml b/simple_rules/gcp_k8s_rules/gcp_k8s_ioc_activity_simple.yml new file mode 100644 index 000000000..564ff1d3a --- /dev/null +++ b/simple_rules/gcp_k8s_rules/gcp_k8s_ioc_activity_simple.yml @@ -0,0 +1,44 @@ +AnalysisType: rule +RuleID: "GCP.K8s.IOC.Activity.Simple" +DisplayName: "GCP K8s IOCActivity" +Enabled: true +LogTypes: + - GCP.AuditLog +Tags: + - GCP + - Optional +Severity: Medium +Description: This detection monitors for any kuberentes API Request originating from an Indicator of Compromise. +Detection: + - All: + - KeyPath: operation.producer + Condition: Equals + Value: k8s.io + - KeyPath: p_enrichment.tor_exit_nodes + Condition: IsNotNullOrEmpty +Reference: https://medium.com/snowflake/from-logs-to-detection-using-snowflake-and-panther-to-detect-k8s-threats-d72f70a504d7 +Tests: + - + Name: triggers + ExpectedResult: true + Log: + { + "operation": {"producer":"k8s.io"}, + "p_enrichment": { + "tor_exit_nodes": [ + "1.1.1.1" + ] + } + } + - + Name: ignore + ExpectedResult: false + Log: + { + "operation": {"producer":"chrome"}, + "p_enrichment": { + "tor_exit_nodes": [ + "1.1.1.1" + ] + } + } diff --git a/simple_rules/gcp_k8s_rules/gcp_k8s_pod_using_host_pid_namespace_simple.yml b/simple_rules/gcp_k8s_rules/gcp_k8s_pod_using_host_pid_namespace_simple.yml new file mode 100644 index 000000000..dca8ff9a2 --- /dev/null +++ b/simple_rules/gcp_k8s_rules/gcp_k8s_pod_using_host_pid_namespace_simple.yml @@ -0,0 +1,70 @@ +AnalysisType: rule +RuleID: "GCP.K8s.Pod.Using.Host.PID.Namespace.Simple" +DisplayName: "GCP K8s Pod Using Host PID Namespace" +Enabled: true +LogTypes: + - GCP.AuditLog +Tags: + - GCP + - Optional +Severity: Medium +Description: This detection monitors for any pod creation or modification using the host PID namespace. The Host PID namespace enables a pod and its containers to have direct access and share the same view as of the host’s processes. This can offer a powerful escape hatch to the underlying host. +Detection: + - All: + - KeyPath: protoPayload.methodName + Condition: IsIn + Values: + - io.k8s.core.v1.pods.create + - io.k8s.core.v1.pods.update + - io.k8s.core.v1.pods.patch + - Any: + - KeyPath: protoPayload.request.spec.hostPID + Condition: Equals + Value: true + - KeyPath: protoPayload.response.spec.hostPID + Condition: Equals + Value: true +Reference: https://medium.com/snowflake/from-logs-to-detection-using-snowflake-and-panther-to-detect-k8s-threats-d72f70a504d7 +Tests: + - + Name: triggers + ExpectedResult: true + Log: + { + "authorizationInfo": [ + { + "granted": true, + "permission": "io.k8s.core.v1.pods.create", + "resource": "core/v1/namespaces/default/pods/nginx-test" + } + ], + "protoPayload": { + "methodName": "io.k8s.core.v1.pods.create", + "request": { + "spec": { + "hostPID": true, + } + } + } + } + - + Name: ignore + ExpectedResult: false + Log: + { + "authorizationInfo": [ + { + "granted": true, + "permission": "io.k8s.core.v1.pods.create", + "resource": "core/v1/namespaces/default/pods/nginx-test" + } + ], + "protoPayload": { + "methodName": "io.k8s.core.v1.pods.create", + "request": { + "spec": { + "hostPID": false, + } + } + } + } diff --git a/simple_rules/gcp_k8s_rules/gcp_k8s_service_type_node_port_deployed_simple.yml b/simple_rules/gcp_k8s_rules/gcp_k8s_service_type_node_port_deployed_simple.yml new file mode 100644 index 000000000..0ffce2092 --- /dev/null +++ b/simple_rules/gcp_k8s_rules/gcp_k8s_service_type_node_port_deployed_simple.yml @@ -0,0 +1,290 @@ +AnalysisType: rule +RuleID: "GCP.K8S.Service.Type.NodePort.Deployed.Simple" +DisplayName: "GCP K8S Service Type NodePort Deployed" +Enabled: true +LogTypes: + - GCP.AuditLog +Severity: High +Detection: + - All: + - KeyPath: protoPayload.methodName + Condition: Contains + Value: io.k8s.core.v1.services.create + - KeyPath: protoPayload.authorizationInfo[*].granted + Condition: Contains + Value: true + - KeyPath: protoPayload.authorizationInfo[*].permission + Condition: Contains + Value: io.k8s.core.v1.services.create + - KeyPath: protoPayload.response.status + Condition: DoesNotEqual + Value: Failure + - KeyPath: protoPayload.request.spec.type + Condition: Equals + Value: NodePort +AlertTitle: "[GCP]: [{protoPayload.authenticationInfo.principalEmail}] created NodePort service in +project [{resource.labels.project_id}]" +AlertContext: + - KeyName: project + KeyValue: + KeyPath: resource.labels.project_id + - KeyName: principal + KeyValue: + KeyPath: protoPayload.authenticationInfo.principalEmail + - KeyName: caller_ip + KeyValue: + KeyPath: protoPayload.requestMetadata.callerIP + - KeyName: methodName + KeyValue: + KeyPath: protoPayload.methodName + - KeyName: resourceName + KeyValue: + KeyPath: protoPayload.resourceName + - KeyName: serviceName + KeyValue: + KeyPath: protoPayload.serviceName + - KeyName: request_spec + KeyValue: + KeyPath: protoPayload.request.spec +Description: > + This detection monitors for any kubernetes service deployed with type node port. A Node Port service allows + an attacker to expose a set of pods hosting the service to the internet by opening their port and redirecting + traffic here. This can be used to bypass network controls and intercept traffic, creating a direct line to + the outside network. +Runbook: > + Investigate the reason of creating NodePort service. Advise that it is discouraged practice. + Create ticket if appropriate. +Reference: https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/ +Reports: + MITRE ATT&CK: + - T1190 # Exploit Public-Facing Application +Tests: + - + Name: Service Created + ExpectedResult: true + Log: + { + "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" + }, + "authorizationInfo": [ + { + "granted": true, + "permission": "io.k8s.core.v1.services.create", + "resource": "core/v1/namespaces/default/services/test-ns" + } + ], + "methodName": "io.k8s.core.v1.services.create", + "request": { + "@type": "core.k8s.io/v1.Service", + "apiVersion": "v1", + "kind": "Service", + "spec": { + "ports": [ + { + "name": "5678-8080", + "port": 5678, + "protocol": "TCP", + "targetPort": 8080 + } + ], + "type": "NodePort" + }, + }, + "requestMetadata": { + "callerIP": "1.2.3.4", + "callerSuppliedUserAgent": "kubectl/v1.28.2 (darwin/amd64) kubernetes/89a4ea3" + }, + "resourceName": "core/v1/namespaces/default/services/test-ns", + "response": { + "@type": "core.k8s.io/v1.Service", + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "creationTimestamp": "2024-02-19T12:02:21Z", + "name": "test-ns", + "namespace": "default", + "resourceVersion": "15036073", + "uid": "28758fe1-534a-4705-bcc2-12eeac6f11a4" + }, + "spec": { + "clusterIP": "2.3.4.5", + "clusterIPs": [ + "2.3.4.5" + ], + "ports": [ + { + "name": "5678-8080", + "nodePort": 32361, + "port": 5678, + "protocol": "TCP", + "targetPort": 8080 + } + ], + "type": "NodePort" + }, + }, + "serviceName": "k8s.io", + "status": { } + }, + "receiveTimestamp": "2024-02-19 12:02:39.542633547", + "resource": { + "labels": { + "cluster_name": "some-project-cluster", + "location": "us-west1", + "project_id": "some-project" + }, + "type": "k8s_cluster" + }, + "timestamp": "2024-02-19 12:02:22.057586000" + } + - + Name: Error Creating Service + ExpectedResult: false + Log: + { + "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" + }, + "authorizationInfo": [ + { + "granted": true, + "permission": "io.k8s.core.v1.services.create", + "resource": "core/v1/namespaces/default/services/test-ns" + } + ], + "methodName": "io.k8s.core.v1.services.create", + "request": { + "@type": "core.k8s.io/v1.Service", + "apiVersion": "v1", + "kind": "Service", + "spec": { + "ports": [ + { + "name": "5678-8080", + "port": 5678, + "protocol": "TCP", + "targetPort": 8080 + } + ], + "type": "NodePort" + }, + }, + "requestMetadata": { + "callerIP": "1.2.3.4", + "callerSuppliedUserAgent": "kubectl/v1.28.2 (darwin/amd64) kubernetes/89a4ea3" + }, + "resourceName": "core/v1/namespaces/default/services/test-ns", + "response": { + "@type": "core.k8s.io/v1.Status", + "apiVersion": "v1", + "code": 409, + "details": { + "kind": "services", + "name": "test-ns" + }, + "kind": "Status", + "message": "services \"test-ns\" already exists", + "metadata": { }, + "reason": "AlreadyExists", + "status": "Failure" + }, + "serviceName": "k8s.io", + "status": { + "code": 10, + "message": "services \"test-ns\" already exists" + } + }, + "receiveTimestamp": "2024-02-20 13:47:46.955496128", + "resource": { + "labels": { + "cluster_name": "some-project-cluster", + "location": "us-west1", + "project_id": "some-project" + }, + "type": "k8s_cluster" + }, + "timestamp": "2024-02-20 13:47:43.126037000" + } + - + Name: No Permission Granted + ExpectedResult: false + Log: + { + "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" + }, + "authorizationInfo": [ + { + "granted": false, + "permission": "io.k8s.core.v1.services.create", + "resource": "core/v1/namespaces/default/services/test-ns" + } + ], + "methodName": "io.k8s.core.v1.services.create", + "request": { + "@type": "core.k8s.io/v1.Service", + "apiVersion": "v1", + "kind": "Service", + "spec": { + "ports": [ + { + "name": "5678-8080", + "port": 5678, + "protocol": "TCP", + "targetPort": 8080 + } + ], + "type": "NodePort" + }, + }, + "requestMetadata": { + "callerIP": "1.2.3.4", + "callerSuppliedUserAgent": "kubectl/v1.28.2 (darwin/amd64) kubernetes/89a4ea3" + }, + "resourceName": "core/v1/namespaces/default/services/test-ns", + "spec": { + "clusterIP": "2.3.4.5", + "clusterIPs": [ + "2.3.4.5" + ], + "externalTrafficPolicy": "Cluster", + "internalTrafficPolicy": "Cluster", + "ipFamilies": [ + "IPv4" + ], + "ipFamilyPolicy": "SingleStack", + "ports": [ + { + "name": "5678-8080", + "nodePort": 32361, + "port": 5678, + "protocol": "TCP", + "targetPort": 8080 + } + ], + "type": "NodePort" + }, + }, + "serviceName": "k8s.io", + "status": { }, + "receiveTimestamp": "2024-02-19 12:02:39.542633547", + "resource": { + "labels": { + "cluster_name": "some-project-cluster", + "location": "us-west1", + "project_id": "some-project" + }, + "type": "k8s_cluster" + }, + "timestamp": "2024-02-19 12:02:22.057586000" + } diff --git a/simple_rules/gcp_k8s_rules/gcp_kubernetes_new_daemonset_deployed_simple.yml b/simple_rules/gcp_k8s_rules/gcp_kubernetes_new_daemonset_deployed_simple.yml new file mode 100644 index 000000000..b62f61186 --- /dev/null +++ b/simple_rules/gcp_k8s_rules/gcp_kubernetes_new_daemonset_deployed_simple.yml @@ -0,0 +1,54 @@ +AnalysisType: rule +RuleID: "GCP.K8s.New.Daemonset.Deployed.Simple" +DisplayName: "GCP K8s New Daemonset Deployed" +Description: "Detects Daemonset creation in GCP Kubernetes clusters." +Enabled: true +LogTypes: + - GCP.AuditLog +Severity: Medium +DedupPeriodMinutes: 60 +Threshold: 1 +Reference: https://medium.com/snowflake/from-logs-to-detection-using-snowflake-and-panther-to-detect-k8s-threats-d72f70a504d7 +Detection: + - All: + - KeyPath: protoPayload.authorizationInfo[*].granted + Condition: Contains + Value: true + - KeyPath: protoPayload.authorizationInfo[*].permission + Condition: Contains + Value: io.k8s.apps.v1.daemonsets.create +Tests: + - Name: privilege-escalation + ExpectedResult: true + Log: + protoPayload: + authorizationInfo: + - granted: true + permission: io.k8s.apps.v1.daemonsets.create + methodName: v2.deploymentmanager.deployments.insert + serviceName: deploymentmanager.googleapis.com + receiveTimestamp: "2024-01-19 13:47:19.465856238" + resource: + labels: + name: test-vm-deployment + project_id: panther-threat-research + type: deployment + severity: NOTICE + timestamp: "2024-01-19 13:47:18.279921000" + - Name: fail + ExpectedResult: false + Log: + protoPayload: + authorizationInfo: + - granted: false + permission: io.k8s.apps.v1.daemonsets.create + methodName: v2.deploymentmanager.deployments.insert + serviceName: deploymentmanager.googleapis.com + receiveTimestamp: "2024-01-19 13:47:19.465856238" + resource: + labels: + name: test-vm-deployment + project_id: panther-threat-research + type: deployment + severity: NOTICE + timestamp: "2024-01-19 13:47:18.279921000" diff --git a/rules/netskope_rules/netskope_admin_logged_out.yml b/simple_rules/netskope_rules/netskope_admin_logged_out_simple.yml similarity index 97% rename from rules/netskope_rules/netskope_admin_logged_out.yml rename to simple_rules/netskope_rules/netskope_admin_logged_out_simple.yml index b0e6cf9c2..2d5ece611 100644 --- a/rules/netskope_rules/netskope_admin_logged_out.yml +++ b/simple_rules/netskope_rules/netskope_admin_logged_out_simple.yml @@ -1,5 +1,5 @@ AnalysisType: rule -RuleID: "Netskope.AdminLoggedOutLoginFailures" +RuleID: "Netskope.AdminLoggedOutLoginFailures.Simple" DisplayName: "Admin logged out because of successive login failures" AlertTitle: "Admin [{user}] was logged out because of successive login failures" Detection: diff --git a/rules/netskope_rules/netskope_admin_user_change.yml b/simple_rules/netskope_rules/netskope_admin_user_change_simple.yml similarity index 98% rename from rules/netskope_rules/netskope_admin_user_change.yml rename to simple_rules/netskope_rules/netskope_admin_user_change_simple.yml index c04fbad25..65a31ccb5 100644 --- a/rules/netskope_rules/netskope_admin_user_change.yml +++ b/simple_rules/netskope_rules/netskope_admin_user_change_simple.yml @@ -1,5 +1,5 @@ AnalysisType: rule -RuleID: "Netskope.AdminUserChange" +RuleID: "Netskope.AdminUserChange.Simple" DisplayName: "An administrator account was created, deleted, or modified." AlertTitle: "User [{user}] performed [{audit_log_event}]" Detection: @@ -93,4 +93,4 @@ Tests: "type": "admin_audit_logs", "ur_normalized": "service-account", "user": "service-account" - } \ No newline at end of file + } diff --git a/rules/netskope_rules/netskope_many_deletes.yml b/simple_rules/netskope_rules/netskope_many_deletes_simple.yml similarity index 94% rename from rules/netskope_rules/netskope_many_deletes.yml rename to simple_rules/netskope_rules/netskope_many_deletes_simple.yml index c89c54fe6..50fc7ed53 100644 --- a/rules/netskope_rules/netskope_many_deletes.yml +++ b/simple_rules/netskope_rules/netskope_many_deletes_simple.yml @@ -1,5 +1,5 @@ AnalysisType: rule -RuleID: "Netskope.ManyDeletes" +RuleID: "Netskope.ManyDeletes.Simple" DisplayName: "Netskope Many Objects Deleted" AlertTitle: "[{user}] deleted many objects in a short time" Detection: @@ -12,7 +12,7 @@ LogTypes: - Netskope.Audit Tags: - Netskope - - Configuration Required + - Configuration Required # configure threshold for your environment - Data Destruction Reports: MITRE ATT&CK: diff --git a/rules/netskope_rules/netskope_personnel_action.yml b/simple_rules/netskope_rules/netskope_personnel_action_simple.yml similarity index 97% rename from rules/netskope_rules/netskope_personnel_action.yml rename to simple_rules/netskope_rules/netskope_personnel_action_simple.yml index cd3b2f389..c3cc7dda5 100644 --- a/rules/netskope_rules/netskope_personnel_action.yml +++ b/simple_rules/netskope_rules/netskope_personnel_action_simple.yml @@ -1,5 +1,5 @@ AnalysisType: rule -RuleID: "Netskope.NetskopePersonnelActivity" +RuleID: "Netskope.NetskopePersonnelActivity.Simple" DisplayName: "Action Performed by Netskope Personnel" AlertTitle: "Action [{audit_log_event}] performed by Netskope personnel [{user}]" Detection: diff --git a/rules/netskope_rules/netskope_unauthorized_api_calls.yml b/simple_rules/netskope_rules/netskope_unauthorized_api_calls_simple.yml similarity index 95% rename from rules/netskope_rules/netskope_unauthorized_api_calls.yml rename to simple_rules/netskope_rules/netskope_unauthorized_api_calls_simple.yml index 74758ed4f..49667883e 100644 --- a/rules/netskope_rules/netskope_unauthorized_api_calls.yml +++ b/simple_rules/netskope_rules/netskope_unauthorized_api_calls_simple.yml @@ -1,5 +1,5 @@ AnalysisType: rule -RuleID: "Netskope.UnauthorizedAPICalls" +RuleID: "Netskope.UnauthorizedAPICalls.Simple" DisplayName: "Netskope Many Unauthorized API Calls" AlertTitle: "Many unauthorized API calls from user [{user}]" Detection: @@ -12,7 +12,7 @@ LogTypes: - Netskope.Audit Tags: - Netskope - - Configuration Required + - Configuration Required # configure threshold for your environment - Brute Force Reports: MITRE ATT&CK: