Merge pull request #47 from github/poisonable_config

Move configuration to MaD files
This commit is contained in:
GitHub Security Lab
2024-06-25 09:48:06 +02:00
committed by GitHub
150 changed files with 224 additions and 148 deletions

View File

@@ -1,7 +1,7 @@
private import codeql.actions.ast.internal.Yaml
private import codeql.Locations
private import codeql.actions.Helper
private import codeql.actions.dataflow.ExternalFlow
private import codeql.actions.config.Config
/**
* Gets the length of each line in the StringValue .

View File

@@ -0,0 +1,74 @@
import ConfigExtensions as Extensions
/**
* MaD models for workflow details
* Fields:
* - path: Path to the workflow file
* - trigger: Trigger for the workflow
* - job: Job name
* - secrets_source: Source of secrets
* - permissions: Permissions for the workflow
* - runner: Runner info for the workflow
*/
predicate workflowDataModel(
string path, string trigger, string job, string secrets_source, string permissions, string runner
) {
Extensions::workflowDataModel(path, trigger, job, secrets_source, permissions, runner)
}
/**
* MaD models for repository details
* Fields:
* - visibility: Visibility of the repository
* - default_branch_name: Default branch name
*/
predicate repositoryDataModel(string visibility, string default_branch_name) {
Extensions::repositoryDataModel(visibility, default_branch_name)
}
/**
* MaD models for context/trigger mapping
* Fields:
* - trigger: Trigger for the workflow
* - context_prefix: Prefix for the context
*/
predicate contextTriggerDataModel(string trigger, string context_prefix) {
Extensions::contextTriggerDataModel(trigger, context_prefix)
}
/**
* MaD models for externally triggerable events
* Fields:
* - event: Event name
*/
predicate externallyTriggerableEventsDataModel(string event) {
Extensions::externallyTriggerableEventsDataModel(event)
}
/**
* MaD models for poisonable commands
* Fields:
* - regexp: Regular expression for matching poisonable commands
*/
predicate poisonableCommandsDataModel(string regexp) {
Extensions::poisonableCommandsDataModel(regexp)
}
/**
* MaD models for poisonable local scripts
* Fields:
* - regexp: Regular expression for matching poisonable local scripts
* - group: Script capture group number for the regular expression
*/
predicate poisonableLocalScriptsDataModel(string regexp, int group) {
Extensions::poisonableLocalScriptsDataModel(regexp, group)
}
/**
* MaD models for poisonable actions
* Fields:
* - action: action name
*/
predicate poisonableActionsDataModel(string action) {
Extensions::poisonableActionsDataModel(action)
}

View File

@@ -0,0 +1,41 @@
/**
* This module provides extensible predicates for defining MaD models.
*/
/**
* Holds if workflow data model exists for the given parameters.
*/
extensible predicate workflowDataModel(
string path, string trigger, string job, string secrets_source, string permissions, string runner
);
/**
* Holds if repository data model exists for the given parameters.
*/
extensible predicate repositoryDataModel(string visibility, string default_branch_name);
/**
* Holds if a context expression starting with context_prefix is available for a given trigger.
*/
extensible predicate contextTriggerDataModel(string trigger, string context_prefix);
/**
* Holds if a given trigger event can be fired by an external actor.
*/
extensible predicate externallyTriggerableEventsDataModel(string event);
/**
* Holds for strings that match poisonable commands.
*/
extensible predicate poisonableCommandsDataModel(string regexp);
/**
* Holds for strings that match poisonable local scripts.
*/
extensible predicate poisonableLocalScriptsDataModel(string regexp, int group);
/**
* Holds for actions that can be poisoned through local files.
*/
extensible predicate poisonableActionsDataModel(string action);

View File

@@ -2,51 +2,6 @@ private import internal.ExternalFlowExtensions as Extensions
private import codeql.actions.DataFlow
private import actions
/**
* MaD models for workflow details
* Fields:
* - path: Path to the workflow file
* - trigger: Trigger for the workflow
* - job: Job name
* - secrets_source: Source of secrets
* - permissions: Permissions for the workflow
* - runner: Runner info for the workflow
*/
predicate workflowDataModel(
string path, string trigger, string job, string secrets_source, string permissions, string runner
) {
Extensions::workflowDataModel(path, trigger, job, secrets_source, permissions, runner)
}
/**
* MaD models for repository details
* Fields:
* - visibility: Visibility of the repository
* - default_branch_name: Default branch name
*/
predicate repositoryDataModel(string visibility, string default_branch_name) {
Extensions::repositoryDataModel(visibility, default_branch_name)
}
/**
* MaD models for context/trigger mapping
* Fields:
* - trigger: Trigger for the workflow
* - context_prefix: Prefix for the context
*/
predicate contextTriggerDataModel(string trigger, string context_prefix) {
Extensions::contextTriggerDataModel(trigger, context_prefix)
}
/**
* MaD models for externally triggerable events
* Fields:
* - event: Event name
*/
predicate externallyTriggerableEventsDataModel(string event) {
Extensions::externallyTriggerableEventsDataModel(event)
}
/**
* MaD sources
* Fields:

View File

@@ -1,5 +1,6 @@
private import codeql.actions.dataflow.ExternalFlow
private import codeql.actions.security.ArtifactPoisoningQuery
private import codeql.actions.config.Config
private import codeql.actions.dataflow.ExternalFlow
/**
* A data flow source.

View File

@@ -22,25 +22,3 @@ extensible predicate actionsSummaryModel(
extensible predicate actionsSinkModel(
string action, string version, string input, string kind, string provenance
);
/**
* Holds if workflow data model exists for the given parameters.
*/
extensible predicate workflowDataModel(
string path, string trigger, string job, string secrets_source, string permissions, string runner
);
/**
* Holds if repository data model exists for the given parameters.
*/
extensible predicate repositoryDataModel(string visibility, string default_branch_name);
/**
* Holds if a context expression starting with context_prefix is available for a given trigger.
*/
extensible predicate contextTriggerDataModel(string trigger, string context_prefix);
/**
* Holds if a given trigger event can be fired by an external actor.
*/
extensible predicate externallyTriggerableEventsDataModel(string event);

View File

@@ -254,8 +254,8 @@ class ArtifactPoisoningSink extends DataFlow::Node {
poisonable.(UsesStep) = this.asExpr()
) and
(
not poisonable instanceof LocalCommandExecutionRunStep or
poisonable.(LocalCommandExecutionRunStep).getCommand().matches(download.getPath() + "%")
not poisonable instanceof LocalScriptExecutionRunStep or
poisonable.(LocalScriptExecutionRunStep).getCommand().matches(download.getPath() + "%")
)
)
}

View File

@@ -1,5 +1,5 @@
import actions
import codeql.actions.dataflow.ExternalFlow
import codeql.actions.config.Config
string defaultBranchTriggerEvent() {
result =

View File

@@ -1,67 +1,35 @@
import actions
import codeql.actions.config.Config
abstract class PoisonableStep extends Step { }
// source: https://github.com/boostsecurityio/poutine/blob/main/opa/rego/rules/untrusted_checkout_exec.rego#L16
private string dangerousActions() {
result =
[
"pre-commit/action", "oxsecurity/megalinter", "bridgecrewio/checkov-action",
"ruby/setup-ruby", "actions/jekyll-build-pages"
]
exists(string action |
poisonableActionsDataModel(action) and
result = action
)
}
class DangerousActionUsesStep extends PoisonableStep, UsesStep {
DangerousActionUsesStep() { this.getCallee() = dangerousActions() }
}
// source: https://github.com/boostsecurityio/poutine/blob/main/opa/rego/rules/untrusted_checkout_exec.rego#L23
private string dangerousCommands() {
result =
[
"npm i(nstall)?(\\b|$)", "npm run ", "yarn ", "npm ci(\\b|$)", "make ", "terraform plan",
"terraform apply", "gomplate ", "pre-commit run", "pre-commit install", "go generate",
"msbuild ", "mvn ", "gradle ", "bundle install", "bundle exec ", "^ant ", "mkdocs build",
"pytest", "pip install -r ", "pip install --requirement", "java -jar ", "poetry install",
"poetry run", "cargo "
]
}
class BuildRunStep extends PoisonableStep, Run {
BuildRunStep() {
exists(
this.getScript().splitAt("\n").trim().regexpFind("([^a-z]|^)" + dangerousCommands(), _, _)
class PoisonableCommandStep extends PoisonableStep, Run {
PoisonableCommandStep() {
exists(string regexp |
poisonableCommandsDataModel(regexp) and
exists(this.getScript().splitAt("\n").trim().regexpFind("([^a-z]|^)" + regexp, _, _))
)
}
}
bindingset[cmdRegexp]
string wrapLocalCmd(string cmdRegexp) { result = "(^|;\\s*|\\s+)" + cmdRegexp + "(\\s+|;|$)" }
class LocalCommandExecutionRunStep extends PoisonableStep, Run {
class LocalScriptExecutionRunStep extends PoisonableStep, Run {
string cmd;
LocalCommandExecutionRunStep() {
// Heuristic:
exists(string line | line = this.getScript().splitAt("\n").trim() |
// ./xxxx
// TODO: It could also be in the form of `dir/cmd`
cmd = line.regexpCapture(wrapLocalCmd("\\.\\/(.*)"), 2)
or
// sh xxxx
cmd = line.regexpCapture(wrapLocalCmd("(ba|z|fi)?sh\\s+(.*)"), 3)
or
// node xxxx.js
cmd = line.regexpCapture(wrapLocalCmd("node\\s+(.*)(\\.js|\\.ts)"), 2)
or
// python xxxx.py
cmd = line.regexpCapture(wrapLocalCmd("python\\s+(.*)\\.py"), 2)
or
// ruby xxxx.rb
cmd = line.regexpCapture(wrapLocalCmd("ruby\\s+(.*)\\.rb"), 2)
or
// go xxxx.go
cmd = line.regexpCapture(wrapLocalCmd("go\\s+(.*)\\.go"), 2)
LocalScriptExecutionRunStep() {
exists(string line, string regexp, int group | line = this.getScript().splitAt("\n").trim() |
poisonableLocalScriptsDataModel(regexp, group) and
cmd = line.regexpCapture(regexp, group)
)
}

View File

@@ -1,5 +1,5 @@
import actions
import codeql.actions.dataflow.ExternalFlow
import codeql.actions.config.Config
bindingset[runner]
predicate isGithubHostedRunner(string runner) {

View File

@@ -1,12 +1,4 @@
extensions:
- addsTo:
pack: github/actions-all
extensible: repositoryDataModel
data: []
- addsTo:
pack: github/actions-all
extensible: workflowDataModel
data: []
- addsTo:
pack: github/actions-all
extensible: contextTriggerDataModel
@@ -54,19 +46,4 @@ extensions:
- ["workflow_call", "github.event.review"]
- ["workflow_call", "github.event.workflow"]
- ["workflow_call", "github.event.workflow_run"]
- addsTo:
pack: github/actions-all
extensible: externallyTriggerableEventsDataModel
data:
- ["discussion"]
- ["discussion_comment"]
- ["fork"]
- ["issue_comment"]
- ["issues"]
- ["pull_request"]
- ["pull_request_comment"]
- ["pull_request_review"]
- ["pull_request_review_comment"]
- ["pull_request_target"]
- ["workflow_run"] # depending on trigger workflow
- ["workflow_call"] # depending on caller

View File

@@ -0,0 +1,18 @@
extensions:
- addsTo:
pack: github/actions-all
extensible: externallyTriggerableEventsDataModel
data:
- ["discussion"]
- ["discussion_comment"]
- ["fork"]
- ["issue_comment"]
- ["issues"]
- ["pull_request"]
- ["pull_request_comment"]
- ["pull_request_review"]
- ["pull_request_review_comment"]
- ["pull_request_target"]
- ["workflow_run"] # depending on trigger workflow
- ["workflow_call"] # depending on caller

View File

@@ -0,0 +1,55 @@
extensions:
- addsTo:
pack: github/actions-all
extensible: poisonableActionsDataModel
# source: https://github.com/boostsecurityio/poutine/blob/main/opa/rego/rules/untrusted_checkout_exec.rego#L16
# source: https://boostsecurityio.github.io/lotp/
data:
- ["pre-commit/action"]
- ["oxsecurity/megalinter"]
- ["bridgecrewio/checkov-action"]
- ["ruby/setup-ruby"]
- ["actions/jekyll-build-pages"]
- addsTo:
pack: github/actions-all
extensible: poisonableCommandsDataModel
# source: https://github.com/boostsecurityio/poutine/blob/main/opa/rego/rules/untrusted_checkout_exec.rego#L23
# source: https://boostsecurityio.github.io/lotp/
data:
- ["ant "]
- ["bundle install"]
- ["bundle exec "]
- ["cargo "]
- ["go generate"]
- ["gomplate "]
- ["gradle "]
- ["java -jar "]
- ["make "]
- ["mkdocs build"]
- ["msbuild "]
- ["mvn "]
- ["npm i(nstall)?(\\b|$)"]
- ["npm run "]
- ["npm ci(\\b|$)"]
- ["pip install -r "]
- ["pip install --requirement"]
- ["poetry install"]
- ["poetry run"]
- ["pre-commit run"]
- ["pre-commit install"]
- ["pytest"]
- ["terraform plan"]
- ["terraform apply"]
- ["yarn "]
- addsTo:
pack: github/actions-all
extensible: poisonableLocalScriptsDataModel
data:
# TODO: It could also be in the form of `dir/cmd`
- ["(^|;\\s*|\\s+)(\\.\\/)(.*)(\\s+|;|$)", 3]
- ["(^|;\\s*|\\s+)(source|sh|bash|zsh|fish)\\s+(.*)(\\s+|;|$)", 3]
- ["(^|;\\s*|\\s+)(node)\\s+(.*)(\\.js|\\.ts)(\\s+|;|$)", 3]
- ["(^|;\\s*|\\s+)(python)\\s+(.*)\\.py(\\s+|;|$)", 3]
- ["(^|;\\s*|\\s+)(ruby)\\s+(.*)\\.rb(\\s+|;|$)", 3]
- ["(^|;\\s*|\\s+)(go)\\s+(.*)\\.go(\\s+|;|$)", 3]

View File

@@ -0,0 +1,9 @@
extensions:
- addsTo:
pack: github/actions-all
extensible: repositoryDataModel
data: []
- addsTo:
pack: github/actions-all
extensible: workflowDataModel
data: []

Some files were not shown because too many files have changed in this diff Show More