mirror of
https://github.com/github/codeql.git
synced 2026-04-27 09:45:15 +02:00
Merge pull request #12978 from asgerf/js/github-actions-sources
JS: Add sources and sinks related to GitHub Actions
This commit is contained in:
@@ -67,6 +67,7 @@ import semmle.javascript.YAML
|
||||
import semmle.javascript.dataflow.DataFlow
|
||||
import semmle.javascript.dataflow.TaintTracking
|
||||
import semmle.javascript.dataflow.TypeInference
|
||||
import semmle.javascript.frameworks.ActionsLib
|
||||
import semmle.javascript.frameworks.Angular2
|
||||
import semmle.javascript.frameworks.AngularJS
|
||||
import semmle.javascript.frameworks.Anser
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Contains models for `@actions/core` related libraries.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.security.dataflow.IndirectCommandInjectionCustomizations
|
||||
|
||||
private API::Node payload() {
|
||||
result = API::moduleImport("@actions/github").getMember("context").getMember("payload")
|
||||
}
|
||||
|
||||
private API::Node workflowRun() { result = payload().getMember("workflow_run") }
|
||||
|
||||
private API::Node commitObj() {
|
||||
result = workflowRun().getMember("head_commit")
|
||||
or
|
||||
result = payload().getMember("commits").getAMember()
|
||||
}
|
||||
|
||||
private API::Node pullRequest() {
|
||||
result = payload().getMember("pull_request")
|
||||
or
|
||||
result = commitObj().getMember("pull_requests").getAMember()
|
||||
}
|
||||
|
||||
private API::Node taintSource() {
|
||||
result = pullRequest().getMember("head").getMember(["ref", "label"])
|
||||
or
|
||||
result =
|
||||
[pullRequest(), payload().getMember(["discussion", "issue"])].getMember(["title", "body"])
|
||||
or
|
||||
result = payload().getMember(["review", "review_comment", "comment"]).getMember("body")
|
||||
or
|
||||
result = workflowRun().getMember(["head_branch", "display_title"])
|
||||
or
|
||||
result = workflowRun().getMember("head_repository").getMember("description")
|
||||
or
|
||||
result = commitObj().getMember("message")
|
||||
or
|
||||
result = commitObj().getMember(["author", "committer"]).getMember(["name", "email"])
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of taint originating from the context.
|
||||
*/
|
||||
private class GitHubActionsContextSource extends RemoteFlowSource {
|
||||
GitHubActionsContextSource() { this = taintSource().asSource() }
|
||||
|
||||
override string getSourceType() { result = "GitHub Actions context" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of taint originating from user input.
|
||||
*
|
||||
* At the momemnt this is only treated as a taint source for the indirect-command injection
|
||||
* query.
|
||||
*/
|
||||
private class GitHubActionsInputSource extends IndirectCommandInjection::Source {
|
||||
GitHubActionsInputSource() {
|
||||
this =
|
||||
API::moduleImport("@actions/core")
|
||||
.getMember(["getInput", "getMultilineInput"])
|
||||
.getReturn()
|
||||
.asSource()
|
||||
}
|
||||
|
||||
override string describe() { result = "GitHub Actions user input" }
|
||||
}
|
||||
|
||||
private class ExecActionsCall extends SystemCommandExecution, DataFlow::CallNode {
|
||||
ExecActionsCall() {
|
||||
this = API::moduleImport("@actions/exec").getMember(["exec", "getExecOutput"]).getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getACommandArgument() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getArgumentList() { result = this.getArgument(1) }
|
||||
|
||||
override DataFlow::Node getOptionsArg() { result = this.getArgument(2) }
|
||||
|
||||
override predicate isSync() { none() }
|
||||
}
|
||||
@@ -49,6 +49,38 @@ module IndirectCommandInjection {
|
||||
override string describe() { result = "environment variable" }
|
||||
}
|
||||
|
||||
/** Gets a data flow node referring to `process.env`. */
|
||||
private DataFlow::SourceNode envObject(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = NodeJSLib::process().getAPropertyRead("env")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = envObject(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a data flow node referring to `process.env`. */
|
||||
private DataFlow::SourceNode envObject() { result = envObject(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Gets the name of an environment variable that is assumed to be safe.
|
||||
*/
|
||||
private string getASafeEnvironmentVariable() {
|
||||
result =
|
||||
[
|
||||
"GITHUB_ACTION", "GITHUB_ACTION_PATH", "GITHUB_ACTION_REPOSITORY", "GITHUB_ACTIONS",
|
||||
"GITHUB_ACTOR", "GITHUB_API_URL", "GITHUB_BASE_REF", "GITHUB_ENV", "GITHUB_EVENT_NAME",
|
||||
"GITHUB_EVENT_PATH", "GITHUB_GRAPHQL_URL", "GITHUB_JOB", "GITHUB_PATH", "GITHUB_REF",
|
||||
"GITHUB_REPOSITORY", "GITHUB_REPOSITORY_OWNER", "GITHUB_RUN_ID", "GITHUB_RUN_NUMBER",
|
||||
"GITHUB_SERVER_URL", "GITHUB_SHA", "GITHUB_WORKFLOW", "GITHUB_WORKSPACE"
|
||||
]
|
||||
}
|
||||
|
||||
/** Sanitizer that blocks flow through safe environment variables. */
|
||||
private class SafeEnvVariableSanitizer extends Sanitizer {
|
||||
SafeEnvVariableSanitizer() {
|
||||
this = envObject().getAPropertyRead(getASafeEnvironmentVariable())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An object containing parsed command-line arguments, considered as a flow source for command injection.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* Added taint sources from the `@actions/core` and `@actions/github` packages.
|
||||
* Added command-injection sinks from the `@actions/exec` package.
|
||||
@@ -1,4 +1,16 @@
|
||||
nodes
|
||||
| actions.js:8:9:8:57 | title |
|
||||
| actions.js:8:17:8:57 | github. ... t.title |
|
||||
| actions.js:8:17:8:57 | github. ... t.title |
|
||||
| actions.js:9:8:9:22 | `echo ${title}` |
|
||||
| actions.js:9:8:9:22 | `echo ${title}` |
|
||||
| actions.js:9:16:9:20 | title |
|
||||
| actions.js:18:9:18:63 | head_ref |
|
||||
| actions.js:18:20:18:63 | github. ... ead.ref |
|
||||
| actions.js:18:20:18:63 | github. ... ead.ref |
|
||||
| actions.js:19:14:19:31 | `echo ${head_ref}` |
|
||||
| actions.js:19:14:19:31 | `echo ${head_ref}` |
|
||||
| actions.js:19:22:19:29 | head_ref |
|
||||
| child_process-test.js:6:9:6:49 | cmd |
|
||||
| child_process-test.js:6:15:6:38 | url.par ... , true) |
|
||||
| child_process-test.js:6:15:6:44 | url.par ... ).query |
|
||||
@@ -179,6 +191,16 @@ nodes
|
||||
| third-party-command-injection.js:6:21:6:27 | command |
|
||||
| third-party-command-injection.js:6:21:6:27 | command |
|
||||
edges
|
||||
| actions.js:8:9:8:57 | title | actions.js:9:16:9:20 | title |
|
||||
| actions.js:8:17:8:57 | github. ... t.title | actions.js:8:9:8:57 | title |
|
||||
| actions.js:8:17:8:57 | github. ... t.title | actions.js:8:9:8:57 | title |
|
||||
| actions.js:9:16:9:20 | title | actions.js:9:8:9:22 | `echo ${title}` |
|
||||
| actions.js:9:16:9:20 | title | actions.js:9:8:9:22 | `echo ${title}` |
|
||||
| actions.js:18:9:18:63 | head_ref | actions.js:19:22:19:29 | head_ref |
|
||||
| actions.js:18:20:18:63 | github. ... ead.ref | actions.js:18:9:18:63 | head_ref |
|
||||
| actions.js:18:20:18:63 | github. ... ead.ref | actions.js:18:9:18:63 | head_ref |
|
||||
| actions.js:19:22:19:29 | head_ref | actions.js:19:14:19:31 | `echo ${head_ref}` |
|
||||
| actions.js:19:22:19:29 | head_ref | actions.js:19:14:19:31 | `echo ${head_ref}` |
|
||||
| child_process-test.js:6:9:6:49 | cmd | child_process-test.js:17:13:17:15 | cmd |
|
||||
| child_process-test.js:6:9:6:49 | cmd | child_process-test.js:17:13:17:15 | cmd |
|
||||
| child_process-test.js:6:9:6:49 | cmd | child_process-test.js:18:17:18:19 | cmd |
|
||||
@@ -344,6 +366,8 @@ edges
|
||||
| third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command |
|
||||
| third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command |
|
||||
#select
|
||||
| actions.js:9:8:9:22 | `echo ${title}` | actions.js:8:17:8:57 | github. ... t.title | actions.js:9:8:9:22 | `echo ${title}` | This command line depends on a $@. | actions.js:8:17:8:57 | github. ... t.title | user-provided value |
|
||||
| actions.js:19:14:19:31 | `echo ${head_ref}` | actions.js:18:20:18:63 | github. ... ead.ref | actions.js:19:14:19:31 | `echo ${head_ref}` | This command line depends on a $@. | actions.js:18:20:18:63 | github. ... ead.ref | user-provided value |
|
||||
| child_process-test.js:17:13:17:15 | cmd | child_process-test.js:6:25:6:31 | req.url | child_process-test.js:17:13:17:15 | cmd | This command line depends on a $@. | child_process-test.js:6:25:6:31 | req.url | user-provided value |
|
||||
| child_process-test.js:18:17:18:19 | cmd | child_process-test.js:6:25:6:31 | req.url | child_process-test.js:18:17:18:19 | cmd | This command line depends on a $@. | child_process-test.js:6:25:6:31 | req.url | user-provided value |
|
||||
| child_process-test.js:19:17:19:19 | cmd | child_process-test.js:6:25:6:31 | req.url | child_process-test.js:19:17:19:19 | cmd | This command line depends on a $@. | child_process-test.js:6:25:6:31 | req.url | user-provided value |
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
const github = require('@actions/github');
|
||||
const aexec = require('@actions/exec');
|
||||
const { exec } = require('child_process');
|
||||
|
||||
// function to echo title
|
||||
function echo_title() {
|
||||
// get the title from the event pull request
|
||||
const title = github.context.payload.pull_request.title;
|
||||
exec(`echo ${title}`, (err, stdout, stderr) => { // NOT OK
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// function which passes the issue title into an exec
|
||||
function exec_head_ref() {
|
||||
const head_ref = github.context.payload.pull_request.head.ref;
|
||||
aexec.exec(`echo ${head_ref}`).then((res) => { // NOT OK
|
||||
console.log(res);
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,17 @@
|
||||
nodes
|
||||
| actions.js:4:6:4:16 | process.env |
|
||||
| actions.js:4:6:4:16 | process.env |
|
||||
| actions.js:4:6:4:29 | process ... _DATA'] |
|
||||
| actions.js:4:6:4:29 | process ... _DATA'] |
|
||||
| actions.js:7:15:7:15 | e |
|
||||
| actions.js:8:10:8:10 | e |
|
||||
| actions.js:8:10:8:23 | e['TEST_DATA'] |
|
||||
| actions.js:8:10:8:23 | e['TEST_DATA'] |
|
||||
| actions.js:12:6:12:16 | process.env |
|
||||
| actions.js:12:6:12:16 | process.env |
|
||||
| actions.js:14:6:14:21 | getInput('data') |
|
||||
| actions.js:14:6:14:21 | getInput('data') |
|
||||
| actions.js:14:6:14:21 | getInput('data') |
|
||||
| command-line-parameter-command-injection.js:4:10:4:21 | process.argv |
|
||||
| command-line-parameter-command-injection.js:4:10:4:21 | process.argv |
|
||||
| command-line-parameter-command-injection.js:4:10:4:21 | process.argv |
|
||||
@@ -212,6 +225,16 @@ nodes
|
||||
| command-line-parameter-command-injection.js:146:22:146:38 | program.pizzaType |
|
||||
| command-line-parameter-command-injection.js:146:22:146:38 | program.pizzaType |
|
||||
edges
|
||||
| actions.js:4:6:4:16 | process.env | actions.js:4:6:4:29 | process ... _DATA'] |
|
||||
| actions.js:4:6:4:16 | process.env | actions.js:4:6:4:29 | process ... _DATA'] |
|
||||
| actions.js:4:6:4:16 | process.env | actions.js:4:6:4:29 | process ... _DATA'] |
|
||||
| actions.js:4:6:4:16 | process.env | actions.js:4:6:4:29 | process ... _DATA'] |
|
||||
| actions.js:7:15:7:15 | e | actions.js:8:10:8:10 | e |
|
||||
| actions.js:8:10:8:10 | e | actions.js:8:10:8:23 | e['TEST_DATA'] |
|
||||
| actions.js:8:10:8:10 | e | actions.js:8:10:8:23 | e['TEST_DATA'] |
|
||||
| actions.js:12:6:12:16 | process.env | actions.js:7:15:7:15 | e |
|
||||
| actions.js:12:6:12:16 | process.env | actions.js:7:15:7:15 | e |
|
||||
| actions.js:14:6:14:21 | getInput('data') | actions.js:14:6:14:21 | getInput('data') |
|
||||
| command-line-parameter-command-injection.js:4:10:4:21 | process.argv | command-line-parameter-command-injection.js:4:10:4:21 | process.argv |
|
||||
| command-line-parameter-command-injection.js:8:22:8:33 | process.argv | command-line-parameter-command-injection.js:8:22:8:36 | process.argv[2] |
|
||||
| command-line-parameter-command-injection.js:8:22:8:33 | process.argv | command-line-parameter-command-injection.js:8:22:8:36 | process.argv[2] |
|
||||
@@ -400,6 +423,9 @@ edges
|
||||
| command-line-parameter-command-injection.js:146:22:146:38 | program.pizzaType | command-line-parameter-command-injection.js:146:10:146:38 | "cmd.sh ... zzaType |
|
||||
| command-line-parameter-command-injection.js:146:22:146:38 | program.pizzaType | command-line-parameter-command-injection.js:146:10:146:38 | "cmd.sh ... zzaType |
|
||||
#select
|
||||
| actions.js:4:6:4:29 | process ... _DATA'] | actions.js:4:6:4:16 | process.env | actions.js:4:6:4:29 | process ... _DATA'] | This command depends on an unsanitized $@. | actions.js:4:6:4:16 | process.env | environment variable |
|
||||
| actions.js:8:10:8:23 | e['TEST_DATA'] | actions.js:12:6:12:16 | process.env | actions.js:8:10:8:23 | e['TEST_DATA'] | This command depends on an unsanitized $@. | actions.js:12:6:12:16 | process.env | environment variable |
|
||||
| actions.js:14:6:14:21 | getInput('data') | actions.js:14:6:14:21 | getInput('data') | actions.js:14:6:14:21 | getInput('data') | This command depends on an unsanitized $@. | actions.js:14:6:14:21 | getInput('data') | GitHub Actions user input |
|
||||
| command-line-parameter-command-injection.js:4:10:4:21 | process.argv | command-line-parameter-command-injection.js:4:10:4:21 | process.argv | command-line-parameter-command-injection.js:4:10:4:21 | process.argv | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:4:10:4:21 | process.argv | command-line argument |
|
||||
| command-line-parameter-command-injection.js:8:10:8:36 | "cmd.sh ... argv[2] | command-line-parameter-command-injection.js:8:22:8:33 | process.argv | command-line-parameter-command-injection.js:8:10:8:36 | "cmd.sh ... argv[2] | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:8:22:8:33 | process.argv | command-line argument |
|
||||
| command-line-parameter-command-injection.js:11:14:11:20 | args[0] | command-line-parameter-command-injection.js:10:13:10:24 | process.argv | command-line-parameter-command-injection.js:11:14:11:20 | args[0] | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:10:13:10:24 | process.argv | command-line argument |
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { exec } from "@actions/exec";
|
||||
import { getInput } from "@actions/core";
|
||||
|
||||
exec(process.env['TEST_DATA']); // NOT OK
|
||||
exec(process.env['GITHUB_ACTION']); // OK
|
||||
|
||||
function test(e) {
|
||||
exec(e['TEST_DATA']); // NOT OK
|
||||
exec(e['GITHUB_ACTION']); // OK
|
||||
}
|
||||
|
||||
test(process.env);
|
||||
|
||||
exec(getInput('data')); // NOT OK
|
||||
@@ -13,6 +13,9 @@ nodes
|
||||
| NoSQLCodeInjection.js:22:36:22:43 | req.body |
|
||||
| NoSQLCodeInjection.js:22:36:22:43 | req.body |
|
||||
| NoSQLCodeInjection.js:22:36:22:48 | req.body.name |
|
||||
| actions.js:4:10:4:50 | github. ... message |
|
||||
| actions.js:4:10:4:50 | github. ... message |
|
||||
| actions.js:4:10:4:50 | github. ... message |
|
||||
| angularjs.js:10:22:10:36 | location.search |
|
||||
| angularjs.js:10:22:10:36 | location.search |
|
||||
| angularjs.js:10:22:10:36 | location.search |
|
||||
@@ -191,6 +194,7 @@ edges
|
||||
| NoSQLCodeInjection.js:22:36:22:43 | req.body | NoSQLCodeInjection.js:22:36:22:48 | req.body.name |
|
||||
| NoSQLCodeInjection.js:22:36:22:48 | req.body.name | NoSQLCodeInjection.js:22:24:22:48 | "name = ... dy.name |
|
||||
| NoSQLCodeInjection.js:22:36:22:48 | req.body.name | NoSQLCodeInjection.js:22:24:22:48 | "name = ... dy.name |
|
||||
| actions.js:4:10:4:50 | github. ... message | actions.js:4:10:4:50 | github. ... message |
|
||||
| angularjs.js:10:22:10:36 | location.search | angularjs.js:10:22:10:36 | location.search |
|
||||
| angularjs.js:13:23:13:37 | location.search | angularjs.js:13:23:13:37 | location.search |
|
||||
| angularjs.js:16:28:16:42 | location.search | angularjs.js:16:28:16:42 | location.search |
|
||||
@@ -306,6 +310,7 @@ edges
|
||||
| NoSQLCodeInjection.js:18:24:18:37 | req.body.query | NoSQLCodeInjection.js:18:24:18:31 | req.body | NoSQLCodeInjection.js:18:24:18:37 | req.body.query | This code execution depends on a $@. | NoSQLCodeInjection.js:18:24:18:31 | req.body | user-provided value |
|
||||
| NoSQLCodeInjection.js:19:24:19:48 | "name = ... dy.name | NoSQLCodeInjection.js:19:36:19:43 | req.body | NoSQLCodeInjection.js:19:24:19:48 | "name = ... dy.name | This code execution depends on a $@. | NoSQLCodeInjection.js:19:36:19:43 | req.body | user-provided value |
|
||||
| NoSQLCodeInjection.js:22:24:22:48 | "name = ... dy.name | NoSQLCodeInjection.js:22:36:22:43 | req.body | NoSQLCodeInjection.js:22:24:22:48 | "name = ... dy.name | This code execution depends on a $@. | NoSQLCodeInjection.js:22:36:22:43 | req.body | user-provided value |
|
||||
| actions.js:4:10:4:50 | github. ... message | actions.js:4:10:4:50 | github. ... message | actions.js:4:10:4:50 | github. ... message | This code execution depends on a $@. | actions.js:4:10:4:50 | github. ... message | user-provided value |
|
||||
| angularjs.js:10:22:10:36 | location.search | angularjs.js:10:22:10:36 | location.search | angularjs.js:10:22:10:36 | location.search | This code execution depends on a $@. | angularjs.js:10:22:10:36 | location.search | user-provided value |
|
||||
| angularjs.js:13:23:13:37 | location.search | angularjs.js:13:23:13:37 | location.search | angularjs.js:13:23:13:37 | location.search | This code execution depends on a $@. | angularjs.js:13:23:13:37 | location.search | user-provided value |
|
||||
| angularjs.js:16:28:16:42 | location.search | angularjs.js:16:28:16:42 | location.search | angularjs.js:16:28:16:42 | location.search | This code execution depends on a $@. | angularjs.js:16:28:16:42 | location.search | user-provided value |
|
||||
|
||||
@@ -13,6 +13,9 @@ nodes
|
||||
| NoSQLCodeInjection.js:22:36:22:43 | req.body |
|
||||
| NoSQLCodeInjection.js:22:36:22:43 | req.body |
|
||||
| NoSQLCodeInjection.js:22:36:22:48 | req.body.name |
|
||||
| actions.js:4:10:4:50 | github. ... message |
|
||||
| actions.js:4:10:4:50 | github. ... message |
|
||||
| actions.js:4:10:4:50 | github. ... message |
|
||||
| angularjs.js:10:22:10:36 | location.search |
|
||||
| angularjs.js:10:22:10:36 | location.search |
|
||||
| angularjs.js:10:22:10:36 | location.search |
|
||||
@@ -195,6 +198,7 @@ edges
|
||||
| NoSQLCodeInjection.js:22:36:22:43 | req.body | NoSQLCodeInjection.js:22:36:22:48 | req.body.name |
|
||||
| NoSQLCodeInjection.js:22:36:22:48 | req.body.name | NoSQLCodeInjection.js:22:24:22:48 | "name = ... dy.name |
|
||||
| NoSQLCodeInjection.js:22:36:22:48 | req.body.name | NoSQLCodeInjection.js:22:24:22:48 | "name = ... dy.name |
|
||||
| actions.js:4:10:4:50 | github. ... message | actions.js:4:10:4:50 | github. ... message |
|
||||
| angularjs.js:10:22:10:36 | location.search | angularjs.js:10:22:10:36 | location.search |
|
||||
| angularjs.js:13:23:13:37 | location.search | angularjs.js:13:23:13:37 | location.search |
|
||||
| angularjs.js:16:28:16:42 | location.search | angularjs.js:16:28:16:42 | location.search |
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
const github = require('@actions/github');
|
||||
|
||||
function test() {
|
||||
eval(github.context.payload.commits[1].message); // NOT OK
|
||||
}
|
||||
Reference in New Issue
Block a user