Skip to content

Commit d558ff8

Browse files
author
Alvaro Muñoz
committed
New Command sources for git and GITHUB_EVENT_PATH
1 parent d4a24df commit d558ff8

4 files changed

Lines changed: 153 additions & 3 deletions

File tree

ql/lib/codeql/actions/config/Config.qll

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,13 @@ predicate vulnerableActionsDataModel(
128128
) {
129129
Extensions::vulnerableActionsDataModel(action, vulnerable_version, vulnerable_sha, fixed_version)
130130
}
131+
132+
/**
133+
* MaD models for untrusted git commands
134+
* Fields:
135+
* - cmd_regex: Regular expression for matching untrusted git commands
136+
* - flag: Flag for the command
137+
*/
138+
predicate untrustedGitCommandsDataModel(string cmd_regex, string flag) {
139+
Extensions::untrustedGitCommandsDataModel(cmd_regex, flag)
140+
}

ql/lib/codeql/actions/config/ConfigExtensions.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,8 @@ extensible predicate argumentInjectionSinksDataModel(
5757
extensible predicate vulnerableActionsDataModel(
5858
string action, string vulnerable_version, string vulnerable_sha, string fixed_version
5959
);
60+
61+
/**
62+
* Holds for git commands that may introduce untrusted data when called on an attacker controlled branch.
63+
*/
64+
extensible predicate untrustedGitCommandsDataModel(string cmd_regex, string flag);

ql/lib/codeql/actions/dataflow/FlowSources.qll

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,88 @@ class GitHubEventCtxSource extends RemoteFlowSource {
6464
override string getSourceType() { result = flag }
6565
}
6666

67+
abstract class CommandSource extends RemoteFlowSource {
68+
abstract string getCommand();
69+
70+
abstract Run getEnclosingRun();
71+
}
72+
73+
class GitCommandSource extends RemoteFlowSource, CommandSource {
74+
Run run;
75+
string cmd;
76+
string flag;
77+
78+
GitCommandSource() {
79+
exists(Step checkout, string cmd_regex |
80+
// This shoould be:
81+
// source instanceof PRHeadCheckoutStep
82+
// but PRHeadCheckoutStep uses Taint Tracking anc causes a non-Monolitic Recursion error
83+
// so we list all the subclasses of PRHeadCheckoutStep here and use actions/checkout as a workaround
84+
// instead of using ActionsMutableRefCheckout and ActionsSHACheckout
85+
(
86+
exists(Uses uses |
87+
checkout = uses and
88+
uses.getCallee() = "actions/checkout" and
89+
exists(uses.getArgument("ref"))
90+
)
91+
or
92+
checkout instanceof GitMutableRefCheckout
93+
or
94+
checkout instanceof GitSHACheckout
95+
or
96+
checkout instanceof GhMutableRefCheckout
97+
or
98+
checkout instanceof GhSHACheckout
99+
) and
100+
this.asExpr() = run.getScriptScalar() and
101+
checkout.getAFollowingStep() = run and
102+
run.getACommand() = cmd and
103+
cmd.indexOf("git") = 0 and
104+
untrustedGitCommandsDataModel(cmd_regex, flag) and
105+
cmd.regexpMatch(cmd_regex)
106+
)
107+
}
108+
109+
override string getSourceType() { result = flag }
110+
111+
override string getCommand() { result = cmd }
112+
113+
override Run getEnclosingRun() { result = run }
114+
}
115+
116+
class GitHubEventPathSource extends RemoteFlowSource, CommandSource {
117+
string cmd;
118+
string flag;
119+
string access_path;
120+
Run run;
121+
122+
// Examples
123+
// COMMENT_AUTHOR=$(jq -r .comment.user.login "$GITHUB_EVENT_PATH")
124+
// CURRENT_COMMENT=$(jq -r .comment.body "$GITHUB_EVENT_PATH")
125+
// PR_HEAD=$(jq --raw-output .pull_request.head.ref ${GITHUB_EVENT_PATH})
126+
// PR_NUMBER=$(jq --raw-output .pull_request.number ${GITHUB_EVENT_PATH})
127+
// PR_TITLE=$(jq --raw-output .pull_request.title ${GITHUB_EVENT_PATH})
128+
// BODY=$(jq -r '.issue.body' "$GITHUB_EVENT_PATH" | sed -n '3p')
129+
GitHubEventPathSource() {
130+
this.asExpr() = run.getScriptScalar() and
131+
run.getACommand() = cmd and
132+
cmd.matches("jq%") and
133+
cmd.matches("%GITHUB_EVENT_PATH%") and
134+
exists(string regexp |
135+
untrustedEventPropertiesDataModel(regexp, flag) and
136+
not flag = "json" and
137+
access_path = "github.event" + cmd.regexpCapture(".*\\s+([^\\s]+)\\s+.*", 1) and
138+
normalizeExpr(access_path).regexpMatch("(?i)\\s*" + wrapRegexp(regexp) + ".*")
139+
)
140+
}
141+
142+
override string getSourceType() { result = flag }
143+
144+
override string getCommand() { result = cmd }
145+
146+
override Run getEnclosingRun() { result = run }
147+
}
148+
67149
class GitHubEventJsonSource extends RemoteFlowSource {
68150
string flag;
69151

@@ -104,10 +186,12 @@ class MaDSource extends RemoteFlowSource {
104186
override string getSourceType() { result = sourceType }
105187
}
106188

189+
abstract class FileSource extends RemoteFlowSource { }
190+
107191
/**
108192
* A downloaded artifact.
109193
*/
110-
class ArtifactSource extends RemoteFlowSource {
194+
class ArtifactSource extends RemoteFlowSource, FileSource {
111195
ArtifactSource() { this.asExpr() instanceof UntrustedArtifactDownloadStep }
112196

113197
override string getSourceType() { result = "artifact" }
@@ -116,8 +200,27 @@ class ArtifactSource extends RemoteFlowSource {
116200
/**
117201
* A file from an untrusted checkout.
118202
*/
119-
private class CheckoutSource extends RemoteFlowSource {
120-
CheckoutSource() { this.asExpr() instanceof PRHeadCheckoutStep }
203+
private class CheckoutSource extends RemoteFlowSource, FileSource {
204+
CheckoutSource() {
205+
// This shoould be:
206+
// source instanceof PRHeadCheckoutStep
207+
// but PRHeadCheckoutStep uses Taint Tracking anc causes a non-Monolitic Recursion error
208+
// so we list all the subclasses of PRHeadCheckoutStep here and use actions/checkout as a workaround
209+
// instead of using ActionsMutableRefCheckout and ActionsSHACheckout
210+
exists(Uses u |
211+
this.asExpr() = u and
212+
u.getCallee() = "actions/checkout" and
213+
exists(u.getArgument("ref"))
214+
)
215+
or
216+
this.asExpr() instanceof GitMutableRefCheckout
217+
or
218+
this.asExpr() instanceof GitSHACheckout
219+
or
220+
this.asExpr() instanceof GhMutableRefCheckout
221+
or
222+
this.asExpr() instanceof GhSHACheckout
223+
}
121224

122225
override string getSourceType() { result = "artifact" }
123226
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
extensions:
2+
- addsTo:
3+
pack: github/actions-all
4+
extensible: untrustedGitCommandsDataModel
5+
data:
6+
# FILES=$(git diff-tree --no-commit-id --name-only HEAD -r)
7+
- [".*git\\b.*\\bdiff-tree\\b.*", "filename,multiline"]
8+
# CHANGES=$(git --no-pager diff --name-only $NAME | grep -v -f .droneignore);
9+
# CHANGES=$(git diff --name-only)
10+
- [".*git\\b.*\\bdiff\\b.*", "filename,multiline"]
11+
# COMMIT_MESSAGE=$(git log --format=%s -n 1)
12+
- [".*git\\b.*\\blog\\b.*%s.*", "text,online"]
13+
# COMMIT_MESSAGE=$(git log --format=%B -n 1)
14+
- [".*git\\b.*\\blog\\b.*%B.*", "text,multiline"]
15+
# COMMIT_MESSAGE=$(git log --format=oneline)
16+
- [".*git\\b.*\\blog\\b.*oneline.*", "text,oneline"]
17+
# COMMIT_MESSAGE=$(git show -s --format=%B)
18+
# COMMIT_MESSAGE=$(git show -s --format=%s)
19+
- [".*git\\b.*\\bshow\\b.*-s.*%s.*", "text,oneline"]
20+
- [".*git\\b.*\\bshow\\b.*-s.*%B.*", "text,multiline"]
21+
# AUTHOR=$(git log -1 --pretty=format:'%an')
22+
- [".*git\\b.*\\blog\\b.*%an.*", "username,oneline"]
23+
# AUTHOR=$(git show -s --pretty=%an)
24+
- [".*git\\b.*\\bshow\\b.*%an.*", "username,oneline"]
25+
# EMAIL=$(git log -1 --pretty=format:'%ae')
26+
- [".*git\\b.*\\blog\\b.*%ae.*", "email,oneline"]
27+
# EMAIL=$(git show -s --pretty=%ae)
28+
- [".*git\\b.*\\bshow\\b.*%ae.*", "email,oneline"]
29+
# BRANCH=$(git branch --show-current)
30+
- [".*git\\b.*\\bbranch\\b.*\\b--show-current\\b.*", "branch,oneline"]
31+
# BRANCH=$(git rev-parse --abbrev-ref HEAD)
32+
- [".*git\\b.*\\brev-parse\\b.*\\b--abbrev-ref\\b.*", "branch,oneline"]

0 commit comments

Comments
 (0)