Query Details
id: d0b2208c-b4cb-4f66-8857-0155722266ac
name: 'New GitHub workflow is using secrets - Historic allow list'
description: |
'This detection builds an allow list of historical usage of secrets by Workflows and compares to recent history, flagging growth of secrets use which are not manually included in the allow list and not historically included in the allow list of workflows. This is to determine if someone is hijacking or use secrets in other workflows.'
severity: Medium
requiredDataConnectors: []
queryFrequency: 6h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1552
query: |
let starttime = 14d;
let endtime = 6h;
// Ignore Build/Releases with less/equal this number
let SecretUsageThreshold = 3;
// New Connections need to exhibit execution of more "new" connections than this number.
let NewSecretUsageThreshold = 1;
// List of Repositories and Worfklows (GitHub Actions) to ignore in your space
let BypassRepositories = datatable(organization:string, repository:string)
[
//"yourOrg", "yourRepo"
];
let BypassWorkflows = datatable(job_workflow_ref_s:string, secret:string)
[
//Optional: Add runner group Id to detect first time usage on other runner than expected (self hosted runner to steal token or secret)
//"yourOrg/yourRepo/.github/workflows/pull.yml@refs/heads/main", "ARM_CLIENT_ID",
//"yourOrg/yourRepo/.github/workflows/push.yml@refs/heads/main", "AZURE_SENTINEL_CREDENTIALS_XYZ"
];
let HistoricDefs = GitHubAuditLogPolling_CL
| where TimeGenerated between (ago(starttime) .. ago(endtime))
| where action_s == "workflows.prepared_workflow_job"
| extend secrets = (parse_json(secrets_passed_s))
| mv-expand secrets
| extend secret = tostring(secrets)
| summarize HistoricCount=count() by job_workflow_ref_s, secret, repo_s;
GitHubAuditLogPolling_CL
| where TimeGenerated >= ago(endtime)
| where action_s == "workflows.prepared_workflow_job"
| extend secrets = (parse_json(secrets_passed_s))
| mv-expand secrets
| extend secret = tostring(secrets)
| summarize CurrentCount=count() by job_workflow_ref_s, secret, repo_s
| where CurrentCount > SecretUsageThreshold
| join kind= leftouter (HistoricDefs) on job_workflow_ref_s, secret
// Exlude workflows from bypass list
| join kind=anti BypassWorkflows on $left.job_workflow_ref_s == $right.job_workflow_ref_s and $left.secret == $right.secret
| where CurrentCount >= HistoricCount + NewSecretUsageThreshold or job_workflow_ref_s !in (HistoricDefs)
| extend organization = tostring(split(repo_s, "/")[0])
| extend repository = tostring(split(repo_s, "/")[1])
// Exclude workflows from whitelisted repositories
| join kind=anti BypassRepositories on $left.organization == $right.organization and $left.repository == $right.repository
// Lookup back to PreparedWorkflow action to get Workflow Run Id
| join kind=innerunique (
GitHubAuditLogPolling_CL
| where TimeGenerated >= ago(endtime)
| where action_s == "workflows.prepared_workflow_job"
| summarize arg_min(TimeGenerated, *) by job_workflow_ref_s
| project workflow_run_id_d, job_workflow_ref_s, _timestamp_d
) on $left.job_workflow_ref_s == $right.job_workflow_ref_s
// Use Workflow Run Id for correlation to Created Worfklow Event for enrichment of actor
| join kind=innerunique (
GitHubAuditLogPolling_CL
| where TimeGenerated >= ago(endtime)
| where action_s == "workflows.created_workflow_run"
| project actor_s, workflow_run_id_d, _timestamp_d
) on $left.workflow_run_id_d == $right.workflow_run_id_d
| extend date_time = unixtime_milliseconds_todatetime(_timestamp_d)
| project TimeGenerated = _timestamp_d1, AccountCustomEntity = actor_s, organization, repository, secret, workflow_RefId = job_workflow_ref_s, CurrentCount, HistoricCount, WorkflowRundId = workflow_run_id_d
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.0.0
This query is used to detect if someone is hijacking or using secrets in GitHub workflows. It compares the historical usage of secrets by workflows to recent history and flags any growth in secrets use that is not manually included in the allow list or historically included in the allow list of workflows. The query looks at the number of secrets used in workflows and compares it to a threshold. It also includes filters to ignore certain repositories and workflows. The query runs every 6 hours and looks at data from the past 14 days. The severity of this detection is medium.

Thomas Naunheim
Released: February 3, 2022
Tables
Keywords
Operators