Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* @name Actions Direct Context to Sinks
* @description Finding dataflows from attacker controlled context objects
* to sinks in GitHub Actions written in javascript.
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id javascript/context-sinks
* @tags security
* external/cwe/cwe-094
* experimental
*/

import javascript
import DataFlow::PathGraph

DataFlow::Node mainDangerCalls() {
result =
DataFlow::globalVarRef(["eval", "setTimeout", "setInterval", "unserialize"])
.getACall()
.getArgument(0)
}

DataFlow::Node execActionCalls() {
result = DataFlow::moduleImport("@actions/exec").getAMemberCall("exec").getArgument(0)
}

DataFlow::Node childProcessCalls() {
result =
DataFlow::moduleImport("child_process")
.getAMemberCall(["exec", "execSync", "execFile", "execFileSync", "spawn", "spawnSync"])
.getArgument(0)
}

DataFlow::PropRead payloadObject(string event, string object) {
result =
DataFlow::moduleImport("@actions/github")
.getAPropertyRead("context")
.getAPropertyRead("payload")
.getAPropertyRead(event)
.getAPropertyRead(object)
}

DataFlow::PropRead payloadEvent(string event) {
result =
DataFlow::moduleImport("@actions/github")
.getAPropertyRead("context")
.getAPropertyRead("payload")
.getAPropertyRead(event)
}

DataFlow::Node pullrequestSources() {
result = payloadObject("pull_request", "head").getAPropertyRead("ref") or
result = payloadObject("pull_request", "head").getAPropertyRead("label") or
result =
payloadObject("pull_request", "head")
.getAPropertyRead("repo")
.getAPropertyRead("default_branch") or
result =
payloadObject("pull_request", "head").getAPropertyRead("repo").getAPropertyRead("description") or
result =
payloadObject("pull_request", "head").getAPropertyRead("repo").getAPropertyRead("homepage") or
result = payloadObject("pull_request", "title") or
result = payloadObject("pull_request", "body")
}

DataFlow::Node issueSources() {
result = payloadObject("issue", "title") or
result = payloadObject("issue", "body")
}

DataFlow::Node discussionSources() {
result = payloadObject("discussion", "title") or
result = payloadObject("discussion", "body")
}

DataFlow::Node commentSources() {
result = payloadObject("comment", "body") or
result = payloadObject("review", "body")
}

DataFlow::Node workflowRunSources() {
result = payloadObject("workflow_run", "head_branch") or
result = payloadObject("workflow_run", "display_title") or
result = payloadObject("workflow_run", "head_repository").getAPropertyRead("description") or
result = payloadObject("workflow_run", "head_commit").getAPropertyRead("message") or
result =
payloadObject("workflow_run", "head_commit").getAPropertyRead("author").getAPropertyRead("name") or
result =
payloadObject("workflow_run", "head_commit")
.getAPropertyRead("committer")
.getAPropertyRead("name") or
result =
payloadObject("workflow_run", "head_commit")
.getAPropertyRead("author")
.getAPropertyRead("email") or
result =
payloadObject("workflow_run", "head_commit")
.getAPropertyRead("committer")
.getAPropertyRead("email") or
result =
payloadObject("workflow_run", "head_commit")
.getAPropertyRead("pull_requests")
.getAPropertyRead()
.getAPropertyRead("head")
.getAPropertyRead("ref")
}

DataFlow::Node headCommitSources() {
result = payloadObject("head_commit", "message") or
result = payloadObject("head_commit", "author").getAPropertyRead("name") or
result = payloadObject("head_commit", "author").getAPropertyRead("email") or
result = payloadObject("head_commit", "committer").getAPropertyRead("name") or
result = payloadObject("head_commit", "committer").getAPropertyRead("email") or
result = payloadEvent("commits").getAPropertyRead().getAPropertyRead("message") or
result =
payloadEvent("commits").getAPropertyRead().getAPropertyRead("author").getAPropertyRead("name") or
result =
payloadEvent("commits").getAPropertyRead().getAPropertyRead("author").getAPropertyRead("email") or
result =
payloadEvent("commits")
.getAPropertyRead()
.getAPropertyRead("committer")
.getAPropertyRead("name") or
result =
payloadEvent("commits")
.getAPropertyRead()
.getAPropertyRead("committer")
.getAPropertyRead("email")
}

class MyConfiguration extends TaintTracking::Configuration {
MyConfiguration() { this = "ActionsContextToSink" }

override predicate isSource(DataFlow::Node source) {
source = pullrequestSources() or
source = issueSources() or
source = discussionSources() or
source = workflowRunSources() or
source = commentSources() or
source = headCommitSources()
}

override predicate isSink(DataFlow::Node sink) {
sink = mainDangerCalls() or
sink = execActionCalls() or
sink = childProcessCalls()
}
}

from
MyConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Expr arg, string fnname
where
cfg.hasFlowPath(source, sink) and
source.getNode().asExpr() = arg and
fnname = sink.getNode().asExpr().getParent().(CallExpr).getCalleeName()
select arg, source, sink, fnname
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* @name Actions Environment Variables to JS Sinks
* @description Finding dataflows from Environment variables to sinks in GitHub Actions written in javascript.
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id javascript/env-sinks
* @tags security
* external/cwe/cwe-094
* experimental
*/

import javascript
import DataFlow::PathGraph

DataFlow::Node mainDangerCalls() {
result =
DataFlow::globalVarRef(["eval", "setTimeout", "setInterval", "unserialize"])
.getACall()
.getArgument(0)
}

DataFlow::Node execActionCalls() {
result = DataFlow::moduleImport("@actions/exec").getAMemberCall("exec").getArgument(0)
}

DataFlow::Node childProcessCalls() {
result =
DataFlow::moduleImport("child_process")
.getAMemberCall(["exec", "execSync", "execFile", "execFileSync", "spawn", "spawnSync"])
.getArgument(0)
}

DataFlow::Node coreSources() {
// we need to get process.env
result = DataFlow::globalVarRef("process").getAPropertyRead("env").getAPropertyReference()
}

class MyConfiguration extends TaintTracking::Configuration {
MyConfiguration() { this = "ActionEnvToSink" }

override predicate isSource(DataFlow::Node source) { source = coreSources() }

override predicate isSink(DataFlow::Node sink) {
sink = mainDangerCalls() or
sink = execActionCalls() or
sink = childProcessCalls()
}
}

from
MyConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, IndexExpr srcidx,
DotExpr srcdot, string fnname, string envname, Expr arg
where
cfg.hasFlowPath(source, sink) and
source.getNode().asExpr() = arg and
fnname = sink.getNode().asExpr().getParent().(CallExpr).getCalleeName() and
(
srcidx = source.getNode().asExpr() and envname = srcidx.getPropertyName()
or
srcdot = source.getNode().asExpr() and envname = srcdot.getPropertyName()
Comment on lines +59 to +61

Check warning

Code scanning / CodeQL

Var only used in one side of disjunct.

The [variable srcidx](1) is only used in one side of disjunct. The [variable srcdot](2) is only used in one side of disjunct.
) and
// Pick the default environment variables from https://docs.github.com/en/actions/learn-github-actions/variables
not envname in [
"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"
]
select arg, source, sink, fnname
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @name Actions Input to JS Sinks
* @description Finding dataflows from inputs of the action to
* sinks in GitHub Actions written in javascript.
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id javascript/input-sinks
* @tags security
* external/cwe/cwe-094
* experimental
*/

import javascript
import DataFlow::PathGraph

DataFlow::Node mainDangerCalls() {
result =
DataFlow::globalVarRef(["eval", "setTimeout", "setInterval", "unserialize"])
.getACall()
.getArgument(0)
}

DataFlow::Node execActionCalls() {
result = DataFlow::moduleImport("@actions/exec").getAMemberCall("exec").getArgument(0)
}

DataFlow::Node childProcessCalls() {
result =
DataFlow::moduleImport("child_process")
.getAMemberCall(["exec", "execSync", "execFile", "execFileSync", "spawn", "spawnSync"])
.getArgument(0)
}

DataFlow::Node coreSources() {
result = DataFlow::moduleImport("@actions/core").getAMemberCall("getInput") or
result = DataFlow::moduleImport("@actions/core").getAMemberCall("getMultilineInput")
}

class MyConfiguration extends TaintTracking::Configuration {
MyConfiguration() { this = "ActionInputToSink" }

override predicate isSource(DataFlow::Node source) { source = coreSources() }

override predicate isSink(DataFlow::Node sink) {
sink = mainDangerCalls() or
sink = execActionCalls() or
sink = childProcessCalls()
}
}

from
MyConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, CallExpr srccall,
Expr arg, string fnname
where
cfg.hasFlowPath(source, sink) and
srccall = source.getNode().asExpr() and
arg = srccall.getArgument(0) and
fnname = sink.getNode().asExpr().getParent().(CallExpr).getCalleeName()
select arg, source, sink, fnname
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const core = require('@actions/core');
const github = require('@actions/github');
const aexec = require('@actions/exec');
// import exec from child_process
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;
// echo the title
exec(`echo ${title}`, (err, stdout, stderr) => {
if (err) {
// node couldn't execute the command
return;
}
// the *entire* stdout and stderr (buffered)
console.log(`stdout: ${stdout}`);
});
}

// function which passes the commit message into an eval
function eval_commit_message() {
// get the commit message from the event pull request
const commit = github.context.payload.commits[1]

// if commit author is x
if (commit.author.name === 'Test') {
// eval the commit message
const evalres = eval(commit.message);
// echo the result
console.log(evalres);
}
}

// 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) => {
console.log(res);
});
}

function input_to_eval() {
const numbers = core.getInput('numbers');
const fin = eval(numbers);
console.log(fin);
}

function env_to_eval() {
const fin = eval(process.env.EVAL_VALUES);
console.log(fin);
}

function github_env_to_exec() {
const workspace = process.env.GITHUB_WORKSPACE;
// echo the workspace
exec(`echo ${workspace}`, (err, stdout, stderr) => {
if (err) {
// node couldn't execute the command
return;
}
// the *entire* stdout and stderr (buffered)
console.log(`stdout: ${stdout}`);
});
}

async function run() {
// Try one source to sink flow for each type
// of function
echo_title();
eval_commit_message();
exec_head_ref();
input_to_eval();
env_to_eval();
github_env_to_exec();
}

run();