Query Details

New GitHub workflow is using secrets - Historic allow list

New Workflow Using Secrets

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 allowlisted 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

Explanation

This query is designed to monitor the use of secrets in GitHub workflows and detect any unusual or unauthorized usage patterns. Here's a simplified explanation of what the query does:

  1. Purpose: The query aims to identify potential security risks by tracking the use of secrets in GitHub workflows. It flags any increase in secret usage that is not part of a predefined allow list or historical usage pattern.

  2. Time Frame: It examines data from the past 14 days and compares it to the last 6 hours to identify any recent changes in secret usage.

  3. Thresholds:

    • It ignores workflows with secret usage below a certain threshold (3 times).
    • It flags workflows that show a significant increase in secret usage (more than 1 new usage).
  4. Allow Lists:

    • It uses allow lists to exclude certain repositories and workflows from being flagged. These lists can be customized to ignore known and safe workflows.
  5. Historical Comparison:

    • It compares the current secret usage with historical data to identify any new or unexpected usage patterns.
  6. Exclusions:

    • It excludes workflows and repositories that are part of the allow lists from being flagged.
  7. Data Enrichment:

    • It enriches the data by correlating workflow runs with the actors who initiated them, providing context on who might be responsible for the changes.
  8. Output:

    • The query outputs details such as the time of the event, the actor involved, the organization and repository, the secret used, and the workflow reference ID.
  9. Severity and Tactics:

    • The detection is marked with medium severity and is associated with the "Credential Access" tactic, specifically targeting the technique of "Unsecured Credentials" (T1552).

Overall, this query helps in identifying potential security threats by monitoring and analyzing the usage of secrets in GitHub workflows, ensuring that any unauthorized or unexpected usage is promptly flagged for further investigation.

Details

Thomas Naunheim profile picture

Thomas Naunheim

Released: July 31, 2024

Tables

GitHubAuditLogPolling_CL

Keywords

GitHubWorkflowsSecretsRepositoriesOrganizationActorAccount

Operators

letbetweenagodatatablewhereextendparse_jsonmv-expandtostringsummarizecountjoinkind=leftouterkind=antikind=inneruniquesplitprojectarg_minunixtime_milliseconds_todatetime

Severity

Medium

Tactics

CredentialAccess

MITRE Techniques

Frequency: 6h

Period: 14d

Actions

GitHub