AnalysisType: rule
Filename: github_webhook_modified.py
RuleID: "GitHub.Webhook.Modified"
DisplayName: "GitHub Web Hook Modified"
Enabled: true
LogTypes:
- GitHub.Audit
Tags:
- GitHub
- Exfiltration:Automated Exfiltration
Reports:
MITRE ATT&CK:
- TA0010:T1020
Reference:
https://docs.github.com/en/webhooks/about-webhooks
# GH audit logs for hook events don't include the type: field
# Only type:repo webhooks are obvious due to the repo field, Org and App look the same
# GETs to /orgs/{org}/hooks or /repos/{owner}/{repo}/hooks will return type
# App hooks don't return type and are defined by their API endpoint
Severity: Info
Description: Detects when a webhook is added, modified, or deleted
Tests:
- Name: GitHub - Webhook Created
ExpectedResult: true
Log:
{
"actor": "cat",
"action": "hook.create",
"data":
{
"hook_id": 111222333444555,
"events": ["fork", "public", "pull_request", "push", "repository"],
},
"config": { "url": "https://fake.url" },
"org": "my-org",
"p_log_type": "GitHub.Audit",
"repo": "my-org/my-repo",
"public_repo": false,
}
- Name: GitHub - Webhook Deleted
ExpectedResult: true
Log:
{
"actor": "cat",
"action": "hook.destroy",
"data":
{
"hook_id": 111222333444555,
"events": ["fork", "public", "pull_request", "push", "repository"],
},
"org": "my-org",
"p_log_type": "GitHub.Audit",
"repo": "my-org/my-repo",
"public_repo": false,
}
- Name: GitHub - Non Webhook Event
ExpectedResult: false
Log:
{
"actor": "cat",
"action": "org.invite_member",
"org": "my-org",
"p_log_type": "GitHub.Audit",
"repo": "my-org/my-repo",
}
- Name: Github - App Webhook Created #App and Org webhooks look the same in audit logs
ExpectedResult: true
Log:
{
"action": "hook.create",
"actor": "dog",
"actor_id": "11112222",
"actor_location": { "country_code": "US" },
"business": "my-biz",
"business_id": "9999999",
"config":
{
"content_type": "json",
"insecure_ssl": "0",
"url": "https://fake.url/",
},
"hook_id": "111222333444555",
"integration": "My Cool Github Integration",
"name": "webhook",
"operation_type": "create",
"org": "my-org",
"org_id": 9999999,
"p_log_type": "GitHub.Audit",
}
# ------ paired body: github_webhook_modified.py ------
from panther_github_helpers import github_alert_context
def rule(event):
return event.get("action").startswith("hook.")
def title(event):
repo = event.get("repo", "<UNKNOWN_REPO>")
action = "modified"
if event.get("action").endswith("destroy"):
action = "deleted"
elif event.get("action").endswith("create"):
action = "created"
title_str = (
f"Github webhook [{event.deep_get('config','url',default='<UNKNOWN_URL>')}]"
f" {action} by [{event.get('actor','<UNKNOWN_ACTOR>')}]"
)
if repo != "<UNKNOWN_REPO>":
title_str += f" in repository [{repo}]"
return title_str
def severity(event):
if event.get("action").endswith("create"):
return "MEDIUM"
return "INFO"
def alert_context(event):
ctx = github_alert_context(event)
ctx["business"] = event.get("business", "")
ctx["hook_id"] = event.get("hook_id", "")
ctx["integration"] = event.get("integration", "")
ctx["operation_type"] = event.get("operation_type", "")
ctx["url"] = event.deep_get("config", "url", default="<UNKNOWN_URL>")
return ctx