Using Derived Detections to Avoid Merge Conflicts

A workflow that could produce merge conflicts is reimagined using detection derivation

Background

One of the most compelling reasons to use Derived Detections, if you are a team managing your Panther detection content using CLI workflows, is that you can use and customize detections not solely managed and updated by your own team while still avoiding the challenge of merge conflicts, which can arise when the same detections are updated by an external team.

This scenario can present itself when you use the detections produced and distributed by Panther's Threat Research team via the panther-analysis repository, known as Panther-managed detections. Merge conflicts can arise when you attempt to maintain a fork of panther-analysis in which you've customized Panther-managed detections, but also want to keep up to date with content changes from Panther by syncing your fork with the upstream repository.

In this case, derivation is useful, as it allows you to customize Panther-managed detections by editing a file completely separate from the ones Panther modifies—meaning you can later receive content updates from Panther without generating merge conflicts.

Walking through a scenario where derivation is useful

In the scenario below, a user is attempting to modify a Panther-managed detection. See how the update is made without, then with, detection derivation.

Scenario

Let’s say you'd like to use the AWS.Console.LoginWithoutMFA Panther-managed detection (see the Python file here and YAML file here), but you also want to make the following modifications to it:

  • You do not want to alert if the account being logged into is a dev account

    • Correspondingly, you want to change the tests to reflect this logic

  • You want the severity to be Low if the account being logged into is an internal, non-dev account, and High otherwise

  • You want to change the runbook

How to make these modifications without derivation

Without using derivation, you would make the following changes in the CLI workflow:

  • Modify the aws_console_login_without_mfa.py file in the following ways:

    • Alter the rule() function to include something similar to the following logic:

    def rule(event):
    		# Your custom logic that short circuits the detection if it's a dev account
    		dev_accounts = [
    				"123456789001",
    				"123456789002"
    		]
    		if event.get("recipientAccountId") in dev_accounts:
    				return False
    ....
    • Add a severity() function similar to the following:

    def severity(event):
    		internal_non_dev_accounts = [
    				"123456789003",
    				"123456789004"
    		]
    		if event.get("recipientAccountId") in internal_non_dev_accounts:
    				return "LOW"
    		return "HIGH"
  • Modify the aws_console_login_without_mfa.yml file in the following ways:

    • Replace the Tests section with a new series of tests, including one for a log that contains a dev account ID, and one for a log that does not

    • Update the value of Runbook to your custom runbook link

After making these changes, the rule is customized to your organization's needs. However, from this point forward, if Panther makes any changes to either file, you will likely encounter merge conflicts when pulling down updates.

See how to use derivation to avoid merge conflicts while making all of the same customizations, below.

How to make these modifications with derivation

In the derivation workflow, begin by creating a Derived Detection that inherits all aspects of the Base Detection by default:

# aws_console_login_without_mfa_account_filter.yml
AnalysisType: rule
BaseDetection: AWS.Console.LoginWithoutMFA
RuleID: AWS.Console.LoginWithoutMFA.Account.Filter

Then start adding overrides, starting with Runbook:

# aws_console_login_without_mfa_account_filter.yml
...
# Custom runbook
Runbook: <https://runbooks.security.corp/secops/AWS/ConsoleLoginWithoutMFA>

Add InlineFilters to indicate the rule should not run if the AWS account being logged into is a dev account:

# aws_console_login_without_mfa_account_filter.yml
...
# Filter out dev account events 
InlineFilters:
    - All:
        - KeyPath: recipientAccountId
          Condition: IsNotIn
          Values:
            - '123456789002'
            - '123456789001'

Add DynamicSeverities to adjust the severity if the account is a staging account:

# aws_console_login_without_mfa_account_filter.yml
...
# Set severity to Low if staging account
DynamicSeverities:
  - ChangeTo: LOW
    Conditions:
      - KeyPath: recipientAccountId
        Condition: IsIn
        Values:
          - '123456789003'
          - '123456789004'

Finally, add a set of your own Tests that allows you to test the detection and filter you added. Below is the complete Derived Detection after adding all of the aforementioned overrides:

# aws_console_login_without_mfa_account_filter.yml
AnalysisType: rule
BaseDetection: AWS.Console.LoginWithoutMFA
RuleID: AWS.Console.LoginWithoutMFA.Account.Filter

# Custom Runbook
Runbook: <https://runbooks.security.corp/secops/AWS/ConsoleLoginWithoutMFA>

# Filter out dev account events 
InlineFilters:
  - All:
    - KeyPath: recipientAccountId
      Condition: IsNotIn
      Values:
        - '123456789001'
        - '123456789002'

# Set severity to Low if staging account
DynamicSeverities:
  - ChangeTo: LOW
    Conditions:
      - KeyPath: recipientAccountId
        Condition: IsIn
        Values:
          - '123456789003'
          - '123456789004'

# Tests that test the filter and rule
Tests:
  -
      Name: No MFA Login - IAM User
      ExpectedResult: true
      Mocks:
        - objectName: check_account_age
          returnValue: "False"
      Log:
        {
          "eventVersion": "1.05",
          "userIdentity": {
            "type": "IAMUser",
            "principalId": "1111",
            "arn": "arn:aws:iam::123456789012:user/tester",
            "accountId": "123456789012",
            "userName": "tester"
          },
          "eventTime": "2019-01-01T00:00:00Z",
          "eventSource": "signin.amazonaws.com",
          "eventName": "ConsoleLogin",
          "awsRegion": "us-east-1",
          "sourceIPAddress": "111.111.111.111",
          "userAgent": "Mozilla",
          "requestParameters": null,
          "responseElements": {
            "ConsoleLogin": "Success"
          },
          "additionalEventData": {
            "LoginTo": "<https://console.aws.amazon.com/console/>",
            "MobileVersion": "No",
            "MFAUsed": "No"
          },
          "eventID": "1",
          "eventType": "AwsConsoleSignIn",
          "recipientAccountId": "123456789012"
        }
  -
      Name: No MFA Login in Dev Account - IAM User
      ExpectedResult: false
      Mocks:
        - objectName: check_account_age
          returnValue: "False"
      Log:
        {
          "eventVersion": "1.05",
          "userIdentity": {
            "type": "IAMUser",
            "principalId": "1111",
            "arn": "arn:aws:iam::123456789012:user/tester",
            "accountId": "123456789012",
            "userName": "tester"
          },
          "eventTime": "2019-01-01T00:00:00Z",
          "eventSource": "signin.amazonaws.com",
          "eventName": "ConsoleLogin",
          "awsRegion": "us-east-1",
          "sourceIPAddress": "111.111.111.111",
          "userAgent": "Mozilla",
          "requestParameters": null,
          "responseElements": {
            "ConsoleLogin": "Success"
          },
          "additionalEventData": {
            "LoginTo": "<https://console.aws.amazon.com/console/>",
            "MobileVersion": "No",
            "MFAUsed": "No"
          },
          "eventID": "1",
          "eventType": "AwsConsoleSignIn",
          "recipientAccountId": "123456789002"
        }

Now that you've added all of your overrides to your Derived Detection, you can upload it to Panther and use it just like you would any detection.

Whenever Panther updates the Base Detection—i.e. Panther modifies either of the aws_console_login_without_mfa.py or aws_console_login_without_mfa.yml files—you will inherit the changes without needing to manually update your Derived Detection (given that these changes were not to fields you added overrides for in your Derived Detection). Most importantly, you will not encounter merge conflicts.

Last updated