Skip to content

Commit

Permalink
Merge branch 'develop' into data-model-unit-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
arielkr256 authored Dec 9, 2024
2 parents 4537bd2 + 9ef96a8 commit da914d9
Show file tree
Hide file tree
Showing 12 changed files with 1,388 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packs/aws.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,11 @@ PackDefinition:
- AWS.CloudFormation.Stack.UsesIAMServiceRole
- AWS.CloudTrail.CodebuildProjectMadePublic
- AWS.CloudTrail.Created
- AWS.CloudTrail.DNSLogsDeleted
- AWS.CloudTrail.Enabled
- AWS.CloudTrail.EventSelectorsDisabled
- AWS.CloudTrail.SecurityConfigurationChange
- AWS.CloudTrail.ShortLifecycle
- AWS.CloudTrail.Stopped
- AWS.CloudTrail.UnauthorizedAPICall
- AWS.CloudWatchLogs.DataRetention1Year
Expand Down Expand Up @@ -131,6 +134,7 @@ PackDefinition:
- AWS.S3.ServerAccess.Error
- AWS.SecurityHub.Finding.Evasion
- AWS.VPC.FlowLogs
- AWS.VPCFlow.LogsDeleted
- AWS.WAF.Disassociation
- AWS.WAF.HasXSSPredicate
- AWS.WAF.LoggingConfigured
Expand Down
1 change: 1 addition & 0 deletions packs/crowdstrike_event_streams.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ PackDefinition:
- Crowdstrike.AllowlistRemoved
- Crowdstrike.API.Key.Created
- Crowdstrike.API.Key.Deleted
- Crowdstrike.EppDetectionSummary
- Crowdstrike.EphemeralUserAccount
- Crowdstrike.IpAllowlistChanged
- Crowdstrike.NewAdminUserCreated
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context, lookup_aws_account_name

# API calls that are indicative of CloudTrail changes
CLOUDTRAIL_EDIT_SELECTORS = {"PutEventSelectors"}


def rule(event):
if not (aws_cloudtrail_success(event) and event.get("eventName") in CLOUDTRAIL_EDIT_SELECTORS):
return False

# Check if management events are included for each selector.
# deep_walk only returns a list if there's more than 1 entry in the nested array, so we must
# enforce it to be a list.
includes = event.deep_walk("requestParameters", "eventSelectors", "includeManagementEvents")
if not isinstance(includes, list):
includes = [includes]

# Return False all the management events are included, else return True and raise alert
return not all(includes)


def dedup(event):
# Merge on the CloudTrail ARN
return event.deep_get("requestParameters", "trailName", default="<UNKNOWN_NAME>")


def title(event):
return (
f"Management events have been exluded from CloudTrail [{dedup(event)}] in account "
f"[{lookup_aws_account_name(event.get('recipientAccountId'))}]"
)


def alert_context(event):
return aws_rule_context(event)
231 changes: 231 additions & 0 deletions rules/aws_cloudtrail_rules/aws_cloudtrail_event_selectors_disabled.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
AnalysisType: rule
Filename: aws_cloudtrail_event_selectors_disabled.py
RuleID: "AWS.CloudTrail.EventSelectorsDisabled"
DisplayName: "CloudTrail Event Delectors Disabled"
Enabled: true
LogTypes:
- AWS.CloudTrail
Tags:
- AWS
- Security Control
- Defense Evasion:Impair Defenses
Reports:
CIS:
- 3.5
MITRE ATT&CK:
- TA0005:T1562
Severity: Medium
Description: >
A CloudTrail Trail was modified to exclude management events for 1 or more resource types.
Runbook: https://docs.runpanther.io/alert-runbooks/built-in-rules/aws-cloudtrail-modified
Reference: https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-update-a-trail-console.html
SummaryAttributes:
- eventName
- userAgent
- sourceIpAddress
- recipientAccountId
- p_any_aws_arns
Tests:
- Name: Event Selector Disabled
ExpectedResult: true
Log: {
"p_event_time": "2024-11-25 17:51:21.000000000",
"p_log_type": "AWS.CloudTrail",
"p_parse_time": "2024-11-25 17:55:54.253083422",
"awsRegion": "us-west-2",
"eventCategory": "Management",
"eventID": "4ca1cb25-7633-496b-8f92-6de876228c3f",
"eventName": "PutEventSelectors",
"eventSource": "cloudtrail.amazonaws.com",
"eventTime": "2024-11-25 17:51:21.000000000",
"eventType": "AwsApiCall",
"eventVersion": "1.11",
"managementEvent": true,
"readOnly": false,
"recipientAccountId": "111122223333",
"requestID": "a8c6184a-89b1-4fc1-a6fa-324748d48b64",
"requestParameters": {
"eventSelectors": [
{
"dataResources": [
{
"type": "AWS::S3::Object",
"values": []
},
{
"type": "AWS::Lambda::Function",
"values": []
}
],
"excludeManagementEventSources": [],
"includeManagementEvents": false,
"readWriteType": "ReadOnly"
}
],
"trailName": "sample-cloudtrail-name"
},
"responseElements": {
"eventSelectors": [
{
"dataResources": [
{
"type": "AWS::S3::Object",
"values": []
},
{
"type": "AWS::Lambda::Function",
"values": []
}
],
"excludeManagementEventSources": [],
"includeManagementEvents": false,
"readWriteType": "ReadOnly"
}
],
"trailARN": "arn:aws:cloudtrail:us-west-2:111122223333:trail/sample-cloudtrail-name"
},
"sourceIPAddress": "1.2.3.4",
"tlsDetails": {
"cipherSuite": "TLS_AES_128_GCM_SHA256",
"clientProvidedHostHeader": "cloudtrail.us-west-2.amazonaws.com",
"tlsVersion": "TLSv1.3"
},
"userAgent": "sample-user-agent",
"userIdentity": {
"accessKeyId": "SAMPLE_ACCESS_KEY_ID",
"accountId": "111122223333",
"arn": "arn:aws:sts::111122223333:assumed-role/SampleRole/leroy.jenkins",
"principalId": "EXAMPLEPRINCIPLEID:leroy.jenkins",
"sessionContext": {
"attributes": {
"creationDate": "2024-11-25T16:53:42Z",
"mfaAuthenticated": "false"
},
"sessionIssuer": {
"accountId": "111122223333",
"arn": "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/us-west-2/SampleRole",
"principalId": "EXAMPLEPRINCIPLEID",
"type": "Role",
"userName": "SampleRole"
}
},
"type": "AssumedRole"
}
}
- Name: Event Selector Enabled
ExpectedResult: false
Log: {
"p_event_time": "2024-11-25 17:51:21.000000000",
"p_log_type": "AWS.CloudTrail",
"p_parse_time": "2024-11-25 17:55:54.253083422",
"awsRegion": "us-west-2",
"eventCategory": "Management",
"eventID": "4ca1cb25-7633-496b-8f92-6de876228c3f",
"eventName": "PutEventSelectors",
"eventSource": "cloudtrail.amazonaws.com",
"eventTime": "2024-11-25 17:51:21.000000000",
"eventType": "AwsApiCall",
"eventVersion": "1.11",
"managementEvent": true,
"readOnly": false,
"recipientAccountId": "111122223333",
"requestID": "a8c6184a-89b1-4fc1-a6fa-324748d48b64",
"requestParameters": {
"eventSelectors": [
{
"dataResources": [],
"excludeManagementEventSources": [],
"includeManagementEvents": true,
"readWriteType": "All"
}
],
"trailName": "sample-cloudtrail-name"
},
"responseElements": {
"eventSelectors": [
{
"dataResources": [],
"excludeManagementEventSources": [],
"includeManagementEvents": true,
"readWriteType": "All"
}
],
"trailARN": "arn:aws:cloudtrail:us-west-2:111122223333:trail/sample-cloudtrail-name"
},
"sourceIPAddress": "1.2.3.4",
"tlsDetails": {
"cipherSuite": "TLS_AES_128_GCM_SHA256",
"clientProvidedHostHeader": "cloudtrail.us-west-2.amazonaws.com",
"tlsVersion": "TLSv1.3"
},
"userAgent": "sample-user-agent",
"userIdentity": {
"accessKeyId": "SAMPLE_ACCESS_KEY_ID",
"accountId": "111122223333",
"arn": "arn:aws:sts::111122223333:assumed-role/SampleRole/leroy.jenkins",
"principalId": "EXAMPLEPRINCIPLEID:leroy.jenkins",
"sessionContext": {
"attributes": {
"creationDate": "2024-11-25T16:53:42Z",
"mfaAuthenticated": "false"
},
"sessionIssuer": {
"accountId": "111122223333",
"arn": "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/us-west-2/SampleRole",
"principalId": "EXAMPLEPRINCIPLEID",
"type": "Role",
"userName": "SampleRole"
}
},
"type": "AssumedRole"
}
}
- Name: Uninteresting Event Type
ExpectedResult: false
Log: {
"p_event_time": "2024-11-25 17:50:24.000000000",
"p_log_type": "AWS.CloudTrail",
"p_parse_time": "2024-11-25 17:55:54.172592534",
"awsRegion": "us-west-2",
"eventCategory": "Management",
"eventID": "63fb143a-c494-4510-8e9e-34172e4872c3",
"eventName": "GetEventSelectors",
"eventSource": "cloudtrail.amazonaws.com",
"eventTime": "2024-11-25 17:50:24.000000000",
"eventType": "AwsApiCall",
"eventVersion": "1.11",
"managementEvent": true,
"readOnly": true,
"recipientAccountId": "111122223333",
"requestID": "cad6aff4-1558-49c5-ae4a-c512058751f1",
"requestParameters": {
"trailName": "sample-cloudtrail-name"
},
"sourceIPAddress": "1.2.3.4",
"tlsDetails": {
"cipherSuite": "TLS_AES_128_GCM_SHA256",
"clientProvidedHostHeader": "cloudtrail.us-west-2.amazonaws.com",
"tlsVersion": "TLSv1.3"
},
"userAgent": "APN/1.0 HashiCorp/1.0 Terraform/1.1.2 (+https://www.terraform.io) terraform-provider-aws/3.76.1 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go/1.44.157 (go1.19.3; darwin; arm64) stratus-red-team_83c9a458-ffab-4d43-8b02-9691311e8c0a HashiCorp-terraform-exec/0.17.3",
"userIdentity": {
"accessKeyId": "SAMPLE_ACCESS_KEY_ID",
"accountId": "111122223333",
"arn": "arn:aws:sts::111122223333:assumed-role/SampleRole/leroy.jenkins",
"principalId": "EXAMPLEPRINCIPLEID:leroy.jenkins",
"sessionContext": {
"attributes": {
"creationDate": "2024-11-25T16:53:42Z",
"mfaAuthenticated": "false"
},
"sessionIssuer": {
"accountId": "111122223333",
"arn": "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/us-west-2/SampleRole",
"principalId": "EXAMPLEPRINCIPLEID",
"type": "Role",
"userName": "SampleRole"
}
},
"type": "AssumedRole"
}
}
87 changes: 87 additions & 0 deletions rules/aws_cloudtrail_rules/aws_cloudtrail_short_lifecycle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context, lookup_aws_account_name
from panther_base_helpers import deep_get

# Use this to record the names of your S3 buckets that have cloudtrail logs
# If a bucket name isn't mentioned here, we still make a best guess as to whether or not it
# contains CloudTrail data, but the confidence rating will be lower, and so will the severity
CLOUDTRAIL_BUCKETS = ("example_cloudtrail_bucket_name",)

# This is the minimum length fo time CloudTrail logs should remain in an S3 bucket.
# We set this to 7 initially, since this is the recommended amount of time logs ingested by
# Panther should remain available. You can modify this if you wish.
CLOUDTRAIL_MINIMUM_STORAGE_PERIOD_DAYS = 7


def rule(event):
# Only alert for successful PutBucketLifecycle events
if not (aws_cloudtrail_success(event) and event.get("eventName") == "PutBucketLifecycle"):
return False

# Exit out if the bucket doesn't have cloudtrail logs
# We check this be either comparing the bucket name to a list of buckets the user knows has
# CT logs, or by heuristically looking at the name and guessing whether it likely has CT logs
bucket_name = event.deep_get("requestParameters", "bucketName")
if not bucket_name or (
not is_cloudtrail_bucket(bucket_name) and not guess_is_cloudtrail_bucket(bucket_name)
):
return False

# Don't alert if the Rule status is disabled
lifecycle = event.deep_get("requestParameters", "LifecycleConfiguration", "Rule")
if lifecycle.get("Status") != "Enabled":
return False

# Alert if the lifecycle period is short
duration = deep_get(lifecycle, "Expiration", "Days", default=0)
return duration < CLOUDTRAIL_MINIMUM_STORAGE_PERIOD_DAYS


def title(event):
bucket_name = event.deep_get("requestParameters", "bucketName", default="<UNKNOWN S3 BUCKET>")
lifecycle = event.deep_get("requestParameters", "LifecycleConfiguration", "Rule")
duration = deep_get(lifecycle, "Expiration", "Days", default=0)
rule_id = lifecycle.get("ID", "<UNKNOWN RULE ID>")
account = event.deep_get("userIdentity", "accountId", default="<UNKNOWN_AWS_ACCOUNT>")
return (
f"S3 Bucket {bucket_name} in account {lookup_aws_account_name(account)} "
f"has new rule {rule_id} set to delete CloudTrail logs after "
f"{duration} day{'s' if duration != 1 else ''}"
)


def severity(event):
# Return lower severity if we aren't positive this bucket has cloudtrail logs.
bucket_name = event.deep_get("requestParameters", "bucketName")
if not is_cloudtrail_bucket(bucket_name):
return "LOW"
return "DEFAULT"


def alert_context(event):
context = aws_rule_context(event)

# Add name of S3 bucket, Rule ID, and expiration duration to context
bucket_name = event.deep_get("requestParameters", "bucketName", default="<UNKNOWN S3 BUCKET>")
lifecycle = event.deep_get("requestParameters", "LifecycleConfiguration", "Rule")
duration = deep_get(lifecycle, "Expiration", "Days", default=0)
rule_id = lifecycle.get("ID", "<UNKNOWN RULE ID>")
context.update(
{
"bucketName": bucket_name,
"lifecycleRuleID": rule_id,
"lifecycleRuleDurationDays": duration,
}
)

return context


def is_cloudtrail_bucket(bucket_name: str) -> bool:
"""Returns True if the bucket is known to contain CloudTrail logs."""
return bucket_name in CLOUDTRAIL_BUCKETS


def guess_is_cloudtrail_bucket(bucket_name: str) -> bool:
"""Takes a best guess at whether a bucket contains CloudTrail logs or not."""
# Maybe one day, this check will get more complex
return "trail" in bucket_name.lower()
Loading

0 comments on commit da914d9

Please sign in to comment.