Skip to content

Commit

Permalink
Merge branch 'develop' into THREAT-411-ZIA-AdminAuditRules---Password…
Browse files Browse the repository at this point in the history
…,-Log,-Backup
  • Loading branch information
arielkr256 authored Nov 14, 2024
2 parents 80ed8ec + c9724bf commit a68fb00
Show file tree
Hide file tree
Showing 34 changed files with 2,393 additions and 901 deletions.
23 changes: 10 additions & 13 deletions .github/workflows/check-packs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,19 @@ jobs:
run: pip install panther_analysis_tool

- name: Check packs
continue-on-error: true
id: check-packs
run: |
# Get the output for the PR comment body
panther_analysis_tool check-packs 2> errors.txt || true
echo ::set-output name=errors::`cat errors.txt`
# run again to get exit code
panther_analysis_tool check-packs || echo "errors=`cat errors.txt`" >> $GITHUB_OUTPUT
- name: Check packs (Exit Code)
run: |
exit $(panther_analysis_tool check-packs)
- name: Comment PR
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b
if: ${{ steps.check-packs.outputs.errors }}
uses: thollander/actions-comment-pull-request@v3
if: failure()
with:
mode: upsert
message: |
Expand All @@ -56,15 +58,10 @@ jobs:
${{ steps.check-packs.outputs.errors }}
```
comment-tag: check-packs

- name: Delete comment
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b
if: ${{ !steps.check-packs.outputs.errors }}
uses: thollander/actions-comment-pull-request@v3
if: success()
with:
mode: delete
message: |
:scream:
looks like some things could be wrong with the packs
```diff
${{ steps.check-packs.outputs.errors }}
```
comment-tag: check-packs
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ policyuniverse = "==1.5.1.20230817"
requests = "==2.31.0"
panther-analysis-tool = "~=0.54.0"
panther-detection-helpers = "==0.4.0"
pycountry = "==24.6.1"

[requires]
python_version = "3.11"
869 changes: 440 additions & 429 deletions Pipfile.lock

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions data_models/onelogin_data_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@

def get_event_type(event):
# currently, only tracking a handful of event types
if event.get("event_type_id") == 72 and event.get("privilege_name") == "Super user":
event_type_id = str(event.get("event_type_id"))
if event_type_id == "72" and event.get("privilege_name") == "Super user":
return event_type.ADMIN_ROLE_ASSIGNED
if event.get("event_type_id") == 6:
if event_type_id == "6":
return event_type.FAILED_LOGIN
if event.get("event_type_id") == 5:
if event_type_id == "5":
return event_type.SUCCESSFUL_LOGIN
if event.get("event_type_id") == 13:
if event_type_id == "13":
return event_type.USER_ACCOUNT_CREATED
return None
9 changes: 7 additions & 2 deletions global_helpers/panther_azuresignin_helpers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from panther_base_helpers import deep_get


def actor_user(event):
# 'event' could be a PantherEvent or an ImmutableCaseInsensitiveDict, so we have to use the
# imported deep_get method.
category = event.get("category", "")
if category in {"ServicePrincipalSignInLogs"}:
return event.deep_get("properties", "servicePrincipalName")
return deep_get(event, "properties", "servicePrincipalName")
if category in {"SignInLogs", "NonInteractiveUserSignInLogs"}:
return event.deep_get("properties", "userPrincipalName")
return deep_get(event, "properties", "userPrincipalName")
return None


Expand Down
576 changes: 293 additions & 283 deletions lookup_tables/traildiscover/traildiscover_data.jsonl

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packs/aws.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ PackDefinition:
- AWS.S3.Bucket.PublicWrite
- AWS.S3.Bucket.SecureAccess
- AWS.S3.Bucket.Versioning
- AWS.S3.Bucket.PolicyConfusedDeputyProtection
# Encryption Status
- AWS.EC2.EBS.Encryption.Disabled
- AWS.EC2.Volume.Encryption
Expand Down Expand Up @@ -143,7 +144,6 @@ PackDefinition:
- AWS.CMK.KeyRotation
- AWS.DynamoDB.TableTTLEnabled
- AWS.EC2.Vulnerable.XZ.Image.Launched
- Amazon.EKS.AnonymousAPIAccess
- AWS.IAM.Policy.DoesNotGrantAdminAccess
- AWS.IAM.Policy.DoesNotGrantNetworkAdminAccess
- AWS.IAM.Resource.DoesNotHaveInlinePolicy
Expand Down
3 changes: 1 addition & 2 deletions packs/azure_signin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ PackDefinition:
# Globals used in these detections
- global_filter_azuresignin
- panther_azuresignin_helpers


- panther_base_helpers
- panther_event_type_helpers
# Data Models
- Standard.Azure.Audit.SignIn
Expand Down
4 changes: 3 additions & 1 deletion packs/notion.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ PackDefinition:
- Notion.PagePerms.GuestPermsChanged
- Notion.LoginFromNewLocation
- Notion.Many.Pages.Exported
- Notion.Many.Pages.Deleted
- Notion.Many.Pages.Deleted.Sched
- Notion.PagePerms.APIPermsChanged
- Notion.PageSharedToWeb
- Notion.SAML.SSO.Configuration.Changed
Expand All @@ -16,6 +16,8 @@ PackDefinition:
- Notion.Workspace.Public.Page.Added
- Notion.SharingSettingsUpdated
- Notion.TeamspaceOwnerAdded
# Scheduled Queries
- Notion Many Pages Deleted Query
# Correlation Rules
- Notion.Login.FOLLOWED.BY.AccountChange
# Signal Rules
Expand Down
8 changes: 8 additions & 0 deletions packs/snowflake_streaming.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,24 @@ PackDefinition:
- Snowflake.PotentialBruteForceSuccess
# Helpers
- panther_snowflake_helpers
# Queries
- Snowflake Attempted Login With Disabled User
- Snowflake User Daily Query Volume Spike
- Snowflake User Daily Query Volume Spike - Threat Hunting
- Suspicious Snowflake Sessions - Unusual Application
# Rules
- Snowflake.Stream.AccountAdminGranted
- Snowflake.Stream.AttemptedLoginByDisabledUser
- Snowflake.Stream.BruteForceByIp
- Snowflake.Stream.BruteForceByUsername
- Snowflake.Stream.ExternalShares
- Snowflake.Stream.FileDownloaded
- Snowflake.Stream.LoginSuccess
- Snowflake.Stream.LoginWithoutMFA
- Snowflake.Stream.PublicRoleGrant
- Snowflake.Stream.SuspiciousSession.UnusualApp
- Snowflake.Stream.TableCopiedIntoStage
- Snowflake.Stream.TempStageCreated
- Snowflake.Stream.UserCreated
- Snowflake.Stream.UserDailyQueryVolumeSpike
- Snowflake.Stream.UserEnabled
3 changes: 3 additions & 0 deletions packs/standard_ruleset.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ PackDefinition:
- Standard.Asana.Audit
- Standard.Atlassian.Audit
- Standard.AWS.CloudTrail
- Standard.Azure.Audit.SignIn
- Standard.Box.Event
- Standard.GCP.AuditLog
- Standard.Github.Audit
Expand All @@ -18,6 +19,7 @@ PackDefinition:
- Standard.OnePassword.ItemUsage
- Standard.OnePassword.SignInAttempt
- Standard.Zendesk.AuditLog
- Standard.Zoom.Activity
- Standard.Zoom.Operation
# Standard Detections
- Standard.AdminRoleAssigned
Expand All @@ -26,6 +28,7 @@ PackDefinition:
- Standard.MFADisabled
- Standard.NewAWSAccountCreated
- Standard.NewUserAccountCreated
- Standard.SignInFromRogueState
# Global Helpers
- panther_base_helpers
- panther_aws_helpers
Expand Down
30 changes: 30 additions & 0 deletions policies/aws_s3_policies/aws_s3_bucket_policy_confused_deputy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import json

REQUIRED_CONDITIONS = {
"aws:SourceArn",
"aws:SourceAccount",
"aws:SourceOrgID",
"aws:SourceOrgPaths",
}


def policy(resource):
bucket_policy = resource.get("Policy")
if bucket_policy is None:
return True # Pass if there is no bucket policy

policy_statements = json.loads(bucket_policy).get("Statement", [])
for statement in policy_statements:
# Check if the statement includes a service principal and allows access
principal = statement.get("Principal", {})
if "Service" in principal and statement["Effect"] == "Allow":
conditions = statement.get("Condition", {})
# Flatten nested condition keys (e.g., inside "StringEquals")
flat_condition_keys = set()
for condition in conditions.values():
if isinstance(condition, dict):
flat_condition_keys.update(condition.keys())
# Check if any required condition key is present
if not REQUIRED_CONDITIONS.intersection(flat_condition_keys):
return False
return True
39 changes: 39 additions & 0 deletions policies/aws_s3_policies/aws_s3_bucket_policy_confused_deputy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
AnalysisType: policy
Filename: aws_s3_bucket_policy_confused_deputy.py
PolicyID: "AWS.S3.Bucket.PolicyConfusedDeputyProtection"
DisplayName: "S3 Bucket Policy Confused Deputy Protection for Service Principals"
Enabled: true
ResourceTypes:
- AWS.S3.Bucket
Tags:
- AWS
- Security Control
- Best Practices
Severity: High
Description: >
Ensures that S3 bucket policies with service principals include conditions to prevent the confused deputy problem.
Runbook: >
Update the bucket policy to include conditions such as aws:SourceArn, aws:SourceAccount,
aws:SourceOrgID, or aws:SourceOrgPaths when a service principal is specified.
Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html
Tests:
- Name: Compliant Policy with Service Principal and Condition
ExpectedResult: true
Resource:
{
"Policy": '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"cloudtrail.amazonaws.com"},"Action":"s3:PutObject","Resource":"arn:aws:s3:::my-example-bucket/*","Condition":{"StringEquals":{"aws:SourceAccount":"123456789012"}}}]}'
}

- Name: Non-Compliant Policy with Service Principal and No Condition
ExpectedResult: false
Resource:
{
"Policy": '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"cloudtrail.amazonaws.com"},"Action":"s3:PutObject","Resource":"arn:aws:s3:::my-example-bucket/*"}]}'
}

- Name: Policy without Service Principal
ExpectedResult: true
Resource:
{
"Policy": '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"arn:aws:iam::123456789012:root"},"Action":"s3:GetObject","Resource":"arn:aws:s3:::my-example-bucket/*"}]}'
}
29 changes: 29 additions & 0 deletions queries/notion_queries/notion_many_pages_deleted_query.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
AnalysisType: scheduled_query
QueryName: Notion Many Pages Deleted Query
Enabled: false
Tags:
- Notion
- Data Security
- Data Destruction
Description: >
A Notion User deleted multiple pages, which were not created or restored from the trash within the same hour.
Query: |
SELECT
event:actor.person.email AS user
,ARRAY_AGG(event:type) AS actions
,event:details.page_name AS page_name
,event:details.target.page_id AS id
FROM
panther_logs.public.notion_auditlogs
WHERE
p_occurs_since(1 hour)
AND event:type IN ('page.deleted','page.created','page.restored_from_trash')
AND event:details.target.type = 'page_id'
AND page_name != ''
AND event:actor.type = 'person'
GROUP BY id, user, page_name
HAVING
actions = ARRAY_CONSTRUCT('page.deleted')
Schedule:
RateMinutes: 60
TimeoutMinutes: 2
7 changes: 7 additions & 0 deletions queries/notion_queries/notion_many_pages_deleted_sched.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
def rule(_):
return True


def title(event):
user = event.get("user", "<NO_USER_FOUND>")
return f"Notion User [{user}] deleted multiple pages."
29 changes: 29 additions & 0 deletions queries/notion_queries/notion_many_pages_deleted_sched.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
AnalysisType: scheduled_rule
Filename: notion_many_pages_deleted_sched.py
RuleID: "Notion.Many.Pages.Deleted.Sched"
DisplayName: "Notion Many Pages Deleted"
Enabled: true
ScheduledQueries:
- Notion Many Pages Deleted Query
Tags:
- Notion
- Data Security
- Data Destruction
Severity: Medium
Description: A Notion User deleted multiple pages, which were not created or restored from the trash within the same hour.
DedupPeriodMinutes: 60
Threshold: 10 # Number of pages deleted; please change this value to suit your organization's needs.
Runbook: Possible Data Destruction. Follow up with the Notion User to determine if this was done for a valid business reason.
Reference: https://www.notion.so/help/duplicate-delete-and-restore-content
Tests:
- Name: query_result
ExpectedResult: true
Log:
{
"actions": [
"page.deleted"
],
"id": "1360a5bb-da41-8177-bedb-d015d012392a",
"page_name": "Newslette",
"user": "[email protected]"
}
20 changes: 20 additions & 0 deletions queries/okta_queries/okta_52_char_username_threat_hunt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
AnalysisType: saved_query
QueryName: "Okta Username Above 52 Characters Security Advisory"
Description: >
On October 30, 2024, a vulnerability was internally identified in generating the cache key for AD/LDAP DelAuth. The Bcrypt algorithm was used to generate the cache key where we hash a combined string of userId + username + password. Under a specific set of conditions, listed below, this could allow users to authenticate by providing the username with the stored cache key of a previous successful authentication.
Customers meeting the pre-conditions should investigate their Okta System Log for unexpected authentications from usernames greater than 52 characters between the period of July 23rd, 2024 to October 30th, 2024.
https://trust.okta.com/security-advisories/okta-ad-ldap-delegated-authentication-username/
Query: |
SELECT
p_event_time as p_timeline,
*
FROM
panther_logs.public.okta_systemlog
WHERE
p_occurs_between('2024-07-22 00:00:00Z','2024-11-01 00:00:00Z')
AND actor:type = 'User'
AND eventType = 'user.session.start'
AND outcome:result = 'SUCCESS'
AND LEN(actor:alternateId) >= 52
ORDER by p_event_time ASC NULLS LAST
LIMIT 100
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
def rule(_):
return True


def title(event):
source = event.get("p_source_label", "<UNKNOWN SOURCE>")
username = event.get("USER_NAME", "<UNKNOWN USER>")
return f"{source}: Attempted signin by disabled user {username}"


def alert_context(event):
return event.get("user")
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
AnalysisType: scheduled_rule
Filename: snowflake_attempted_login_by_disabled_user.py
RuleID: "Snowflake.Stream.AttemptedLoginByDisabledUser"
Enabled: true
ScheduledQueries:
- Snowflake Attempted Login With Disabled User
Severity: Low
Reports:
MITRE ATT&CK:
- TA0001:T1078.004
Description: >
Detects when a login is attempted by a disabled user account.
Tags:
- Snowflake
- Behavior Analysis
- Initial Access:Valid Accounts:Cloud Accounts
Tests:
- Name: Login by Disabled User
ExpectedResult: true
Log:
{
"p_source_label": "SF-Prod",
"user": {
"CREATED_ON": "2024-10-09 19:43:05.083000000",
"DEFAULT_ROLE": "PANTHER_AUDIT_VIEW_ROLE",
"DISABLED": true,
"DISPLAY_NAME":
"FORMER_ADMIN",
"EXT_AUTHN_DUO": false,
"HAS_MFA": false,
"HAS_PASSWORD": true,
"HAS_RSA_PUBLIC_KEY": false,
"LAST_SUCCESS_LOGIN": "2024-10-09 20:59:00.043000000",
"LOGIN_NAME": "FORMER_ADMIN",
"MUST_CHANGE_PASSWORD": false,
"NAME": "FORMER_ADMIN",
"OWNER": "ACCOUNTADMIN",
"SNOWFLAKE_LOCK": false,
"USER_ID": "51"
},
"USER_NAME": "FORMER_ADMIN"
}
Loading

0 comments on commit a68fb00

Please sign in to comment.