Skip to content

Commit 00f6ff8

Browse files
author
Alvaro Muñoz
committed
Split sources by taint type
1 parent 27d0a34 commit 00f6ff8

31 files changed

+313
-774
lines changed
Lines changed: 135 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
private import actions
2-
private import codeql.actions.DataFlow
31
private import codeql.actions.dataflow.ExternalFlow
42
private import codeql.actions.security.ArtifactPoisoningQuery
53

@@ -22,152 +20,220 @@ abstract class RemoteFlowSource extends SourceNode {
2220
}
2321

2422
bindingset[context]
25-
private predicate isExternalUserControlled(string context) {
26-
exists(string reg | reg = "github\\.event" |
23+
private predicate titleEvent(string context) {
24+
exists(string reg |
25+
reg =
26+
[
27+
// title
28+
"github\\.event\\.issue\\.title", // issue
29+
"github\\.event\\.pull_request\\.title", // pull request
30+
"github\\.event\\.discussion\\.title", // discussion
31+
"github\\.event\\.pages\\[[0-9]+\\]\\.page_name",
32+
"github\\.event\\.pages\\[[0-9]+\\]\\.title",
33+
"github\\.event\\.workflow_run\\.display_title", // The event-specific title associated with the run or the run-name if set, or the value of run-name if it is set in the workflow.
34+
]
35+
|
2736
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp(reg))
2837
)
2938
}
3039

3140
bindingset[context]
32-
private predicate isExternalUserControlledIssue(string context) {
33-
exists(string reg | reg = ["github\\.event\\.issue\\.title", "github\\.event\\.issue\\.body"] |
41+
private predicate urlEvent(string context) {
42+
exists(string reg |
43+
reg =
44+
[
45+
// url
46+
"github\\.event\\.pull_request\\.head\\.repo\\.homepage",
47+
]
48+
|
3449
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp(reg))
3550
)
3651
}
3752

3853
bindingset[context]
39-
private predicate isExternalUserControlledPullRequest(string context) {
54+
private predicate textEvent(string context) {
4055
exists(string reg |
4156
reg =
4257
[
43-
"github\\.event\\.pull_request\\.title", "github\\.event\\.pull_request\\.body",
44-
"github\\.event\\.pull_request\\.head\\.label",
45-
"github\\.event\\.pull_request\\.head\\.repo\\.default_branch",
46-
"github\\.event\\.pull_request\\.head\\.repo\\.description",
47-
"github\\.event\\.pull_request\\.head\\.repo\\.homepage",
48-
"github\\.event\\.pull_request\\.head\\.ref", "github\\.head_ref"
58+
// text
59+
"github\\.event\\.issue\\.body", // body
60+
"github\\.event\\.pull_request\\.body", // body
61+
"github\\.event\\.discussion\\.body", // body
62+
"github\\.event\\.review\\.body", // body
63+
"github\\.event\\.comment\\.body", // body
64+
"github\\.event\\.commits\\[[0-9]+\\]\\.message", // messsage
65+
"github\\.event\\.head_commit\\.message", // message
66+
"github\\.event\\.workflow_run\\.head_commit\\.message", // message
67+
"github\\.event\\.pull_request\\.head\\.repo\\.description", // description
68+
"github\\.event\\.workflow_run\\.head_repository\\.description", // description
69+
"github\\.event\\.client_payload\\[[0-9]+\\]", // payload
70+
"github\\.event\\.client_payload", // payload
71+
"github\\.event\\.inputs\\[[0-9]+\\]", // input
72+
"github\\.event\\.inputs", // input
4973
]
5074
|
5175
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp(reg))
5276
)
5377
}
5478

5579
bindingset[context]
56-
private predicate isExternalUserControlledReview(string context) {
57-
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp("github\\.event\\.review\\.body"))
80+
private predicate repoNameEvent(string context) {
81+
exists(string reg |
82+
reg =
83+
[
84+
// repo name
85+
// Owner: All characters must be either a hyphen (-) or alphanumeric
86+
// Repo: All code points must be either a hyphen (-), an underscore (_), a period (.), or an ASCII alphanumeric code point
87+
"github\\.event\\.workflow_run\\.pull_requests\\[[0-9]+\\]\\.head\\.repo\\.name", // repo name
88+
"github\\.event\\.workflow_run\\.head_repository\\.name", // repo name
89+
"github\\.event\\.workflow_run\\.head_repository\\.full_name", // nwo
90+
]
91+
|
92+
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp(reg))
93+
)
5894
}
5995

6096
bindingset[context]
61-
private predicate isExternalUserControlledComment(string context) {
62-
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp("github\\.event\\.comment\\.body"))
97+
private predicate branchEvent(string context) {
98+
exists(string reg |
99+
reg =
100+
[
101+
// branch
102+
// https://docs.github.com/en/get-started/using-git/dealing-with-special-characters-in-branch-and-tag-names
103+
// - They can include slash / for hierarchical (directory) grouping, but no slash-separated component can begin with a dot . or end with the sequence .lock.
104+
// - They must contain at least one /
105+
// - They cannot have two consecutive dots .. anywhere.
106+
// - They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
107+
// - They cannot have question-mark ?, asterisk *, or open bracket [ anywhere.
108+
// - They cannot begin or end with a slash / or contain multiple consecutive slashes
109+
// - They cannot end with a dot .
110+
// - They cannot contain a sequence @{
111+
// - They cannot be the single character @
112+
// - They cannot contain a \
113+
// eg: zzz";echo${IFS}"hello";# would be a valid branch name
114+
"github\\.event\\.pull_request\\.head\\.repo\\.default_branch",
115+
"github\\.event\\.pull_request\\.head\\.ref", "github\\.head_ref",
116+
"github\\.event\\.workflow_run\\.head_branch",
117+
"github\\.event\\.workflow_run\\.head_branch",
118+
"github\\.event\\.workflow_run\\.pull_requests\\[[0-9]+\\]\\.head\\.ref",
119+
]
120+
|
121+
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp(reg))
122+
)
63123
}
64124

65125
bindingset[context]
66-
private predicate isExternalUserControlledGollum(string context) {
126+
private predicate labelEvent(string context) {
67127
exists(string reg |
68128
reg =
69129
[
70-
"github\\.event\\.pages\\[[0-9]+\\]\\.page_name",
71-
"github\\.event\\.pages\\[[0-9]+\\]\\.title"
130+
// label
131+
// - They cannot contain a escaping \
132+
"github\\.event\\.pull_request\\.head\\.label",
72133
]
73134
|
74135
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp(reg))
75136
)
76137
}
77138

78139
bindingset[context]
79-
private predicate isExternalUserControlledCommit(string context) {
140+
private predicate emailEvent(string context) {
80141
exists(string reg |
81142
reg =
82143
[
83-
"github\\.event\\.commits\\[[0-9]+\\]\\.message", "github\\.event\\.head_commit\\.message",
144+
// email
145+
// `echo${IFS}hello`@domain.com
84146
"github\\.event\\.head_commit\\.author\\.email",
85-
"github\\.event\\.head_commit\\.author\\.name",
86147
"github\\.event\\.head_commit\\.committer\\.email",
87-
"github\\.event\\.head_commit\\.committer\\.name",
88148
"github\\.event\\.commits\\[[0-9]+\\]\\.author\\.email",
89-
"github\\.event\\.commits\\[[0-9]+\\]\\.author\\.name",
90149
"github\\.event\\.commits\\[[0-9]+\\]\\.committer\\.email",
91-
"github\\.event\\.commits\\[[0-9]+\\]\\.committer\\.name",
150+
"github\\.event\\.workflow_run\\.head_commit\\.author\\.email",
151+
"github\\.event\\.workflow_run\\.head_commit\\.committer\\.email",
92152
]
93153
|
94154
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp(reg))
95155
)
96156
}
97157

98158
bindingset[context]
99-
private predicate isExternalUserControlledDiscussion(string context) {
159+
private predicate usernameEvent(string context) {
100160
exists(string reg |
101-
reg = ["github\\.event\\.discussion\\.title", "github\\.event\\.discussion\\.body"]
161+
reg =
162+
[
163+
// username
164+
// All characters must be either a hyphen (-) or alphanumeric
165+
"github\\.event\\.head_commit\\.author\\.name",
166+
"github\\.event\\.head_commit\\.committer\\.name",
167+
"github\\.event\\.commits\\[[0-9]+\\]\\.author\\.name",
168+
"github\\.event\\.commits\\[[0-9]+\\]\\.committer\\.name",
169+
"github\\.event\\.workflow_run\\.head_commit\\.author\\.name",
170+
"github\\.event\\.workflow_run\\.head_commit\\.committer\\.name",
171+
]
102172
|
103173
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp(reg))
104174
)
105175
}
106176

107177
bindingset[context]
108-
private predicate isExternalUserControlledWorkflowRun(string context) {
178+
private predicate pathEvent(string context) {
109179
exists(string reg |
110180
reg =
111181
[
112-
"github\\.event\\.workflow\\.path", "github\\.event\\.workflow_run\\.head_branch",
113-
"github\\.event\\.workflow_run\\.display_title",
114-
"github\\.event\\.workflow_run\\.head_branch",
115-
"github\\.event\\.workflow_run\\.head_repository\\.description",
116-
"github\\.event\\.workflow_run\\.head_repository\\.full_name",
117-
"github\\.event\\.workflow_run\\.head_repository\\.name",
118-
"github\\.event\\.workflow_run\\.head_commit\\.message",
119-
"github\\.event\\.workflow_run\\.head_commit\\.author\\.email",
120-
"github\\.event\\.workflow_run\\.head_commit\\.author\\.name",
121-
"github\\.event\\.workflow_run\\.head_commit\\.committer\\.email",
122-
"github\\.event\\.workflow_run\\.head_commit\\.committer\\.name",
123-
"github\\.event\\.workflow_run\\.pull_requests\\[[0-9]+\\]\\.head\\.ref",
124-
"github\\.event\\.workflow_run\\.pull_requests\\[[0-9]+\\]\\.head\\.repo\\.name",
182+
// filename
183+
"github\\.event\\.workflow\\.path",
125184
]
126185
|
127186
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp(reg))
128187
)
129188
}
130189

131190
bindingset[context]
132-
private predicate isExternalUserControlledRepositoryDispatch(string context) {
191+
private predicate jsonEvent(string context) {
133192
exists(string reg |
134-
reg = ["github\\.event\\.client_payload\\[[0-9]+\\]", "github\\.event\\.client_payload",]
193+
reg =
194+
[
195+
// json
196+
"github\\.event",
197+
]
135198
|
136199
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp(reg))
137200
)
138201
}
139202

140-
bindingset[context]
141-
private predicate isExternalUserControlledWorkflowDispatch(string context) {
142-
exists(string reg | reg = ["github\\.event\\.inputs\\[[0-9]+\\]", "github\\.event\\.inputs",] |
143-
Utils::normalizeExpr(context).regexpMatch(Utils::wrapRegexp(reg))
144-
)
145-
}
203+
class EventSource extends RemoteFlowSource {
204+
string flag;
146205

147-
private class EventSource extends RemoteFlowSource {
148206
EventSource() {
149207
exists(Expression e, string context | this.asExpr() = e and context = e.getExpression() |
150-
isExternalUserControlled(context) or
151-
isExternalUserControlledIssue(context) or
152-
isExternalUserControlledPullRequest(context) or
153-
isExternalUserControlledReview(context) or
154-
isExternalUserControlledComment(context) or
155-
isExternalUserControlledGollum(context) or
156-
isExternalUserControlledCommit(context) or
157-
isExternalUserControlledDiscussion(context) or
158-
isExternalUserControlledWorkflowRun(context) or
159-
isExternalUserControlledRepositoryDispatch(context) or
160-
isExternalUserControlledWorkflowDispatch(context)
208+
titleEvent(context) and flag = "title"
209+
or
210+
urlEvent(context) and flag = "url"
211+
or
212+
textEvent(context) and flag = "text"
213+
or
214+
repoNameEvent(context) and flag = "repo name"
215+
or
216+
branchEvent(context) and flag = "branch"
217+
or
218+
labelEvent(context) and flag = "label"
219+
or
220+
emailEvent(context) and flag = "email"
221+
or
222+
usernameEvent(context) and flag = "username"
223+
or
224+
pathEvent(context) and flag = "filename"
225+
or
226+
jsonEvent(context) and flag = "json"
161227
)
162228
}
163229

164-
override string getSourceType() { result = "User-controlled events" }
230+
override string getSourceType() { result = flag }
165231
}
166232

167233
/**
168234
* A Source of untrusted data defined in a MaD specification
169235
*/
170-
private class ExternallyDefinedSource extends RemoteFlowSource {
236+
class ExternallyDefinedSource extends RemoteFlowSource {
171237
string sourceType;
172238

173239
ExternallyDefinedSource() { externallyDefinedSource(this, sourceType, _) }
@@ -178,19 +244,19 @@ private class ExternallyDefinedSource extends RemoteFlowSource {
178244
/**
179245
* An input for a Composite Action
180246
*/
181-
private class CompositeActionInputSource extends RemoteFlowSource {
247+
class CompositeActionInputSource extends RemoteFlowSource {
182248
CompositeAction c;
183249

184250
CompositeActionInputSource() { c.getAnInput() = this.asExpr() }
185251

186-
override string getSourceType() { result = "Composite action input" }
252+
override string getSourceType() { result = "input" }
187253
}
188254

189255
/**
190256
* A downloadeded artifact.
191257
*/
192-
private class ArtifactToOptionSource extends RemoteFlowSource {
193-
ArtifactToOptionSource() { this.asExpr() instanceof UntrustedArtifactDownloadStep }
258+
private class ArtifactSource extends RemoteFlowSource {
259+
ArtifactSource() { this.asExpr() instanceof UntrustedArtifactDownloadStep }
194260

195-
override string getSourceType() { result = "Step output from Artifact" }
261+
override string getSourceType() { result = "artifact" }
196262
}

ql/lib/codeql/actions/security/EnvVarInjectionQuery.qll

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,6 @@ import codeql.actions.DataFlow
77

88
abstract class EnvVarInjectionSink extends DataFlow::Node { }
99

10-
// predicate envVarInjectionFromEnvVarSink(DataFlow::Node sink) {
11-
// exists(Expression expr, Run run, string varName, string key, string value |
12-
// expr = run.getInScopeEnvVarExpr(varName) and
13-
// Utils::writeToGitHubEnv(run, key, value) and
14-
// expr = sink.asExpr() and
15-
// value.matches("%$" + ["", "{", "ENV{"] + varName + "%")
16-
// )
17-
// }
1810
class EnvVarInjectionFromEnvVarSink extends EnvVarInjectionSink {
1911
EnvVarInjectionFromEnvVarSink() {
2012
exists(Run run, Expression expr, string varname, string key, string value |

ql/lib/ext/ahmadnassri_action-changed-files.model.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ extensions:
33
pack: githubsecuritylab/actions-all
44
extensible: sourceModel
55
data:
6-
- ["ahmadnassri/action-changed-files", "*", "output.files", "PR changed files", "manual"]
7-
- ["ahmadnassri/action-changed-files", "*", "output.json", "PR changed files", "manual"]
6+
- ["ahmadnassri/action-changed-files", "*", "output.files", "filename", "manual"]
7+
- ["ahmadnassri/action-changed-files", "*", "output.json", "json", "manual"]

ql/lib/ext/amannn_action-semantic-pull-request.model.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ extensions:
33
pack: githubsecuritylab/actions-all
44
extensible: sourceModel
55
data:
6-
- ["amannn/action-semantic-pull-request", "*", "output.error_message", "PR title", "manual"]
6+
- ["amannn/action-semantic-pull-request", "*", "output.error_message", "text", "manual"]

ql/lib/ext/cypress-io_github-action.model.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ extensions:
33
pack: githubsecuritylab/actions-all
44
extensible: sourceModel
55
data:
6-
- ["cypress-io/github-action", "*", "env.GH_BRANCH", "PR branch", "manual"]
6+
- ["cypress-io/github-action", "*", "env.GH_BRANCH", "branch", "manual"]

ql/lib/ext/dawidd6_action-download-artifact.model.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ extensions:
33
pack: githubsecuritylab/actions-all
44
extensible: sourceModel
55
data:
6-
- ["dawidd6/action-download-artifact", "*", "output.artifacts", "Artifact details", "manual"]
6+
- ["dawidd6/action-download-artifact", "*", "output.artifacts", "artifact", "manual"]

ql/lib/ext/dorny_paths-filter.model.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ extensions:
33
pack: githubsecuritylab/actions-all
44
extensible: sourceModel
55
data:
6-
- ["dorny/paths-filter", "*", "output.changes", "PR changed files", "manual"]
6+
- ["dorny/paths-filter", "*", "output.changes", "filename", "manual"]

ql/lib/ext/franzdiebold_github-env-vars-action.model.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ extensions:
33
pack: githubsecuritylab/actions-all
44
extensible: sourceModel
55
data:
6-
- ["franzdiebold/github-env-vars-action", "*", "output.CI_PR_DESCRIPTION", "PR body", "manual"]
7-
- ["franzdiebold/github-env-vars-action", "*", "output.CI_PR_TITLE", "PR title", "manual"]
6+
- ["franzdiebold/github-env-vars-action", "*", "output.CI_PR_DESCRIPTION", "text", "manual"]
7+
- ["franzdiebold/github-env-vars-action", "*", "output.CI_PR_TITLE", "title", "manual"]

ql/lib/ext/generated/composite-actions/googlecloudplatform_dataflowtemplates.model.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ extensions:
88
pack: githubsecuritylab/actions-all
99
extensible: sourceModel
1010
data:
11-
- ["googlecloudplatform/magic-modules", "*", "output.changed-files", "PR changed files", "manual"]
11+
- ["googlecloudplatform/magic-modules", "*", "output.changed-files", "filename", "manual"]

ql/lib/ext/generated/reusable-workflows/puppeteer_puppeteer.model.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ extensions:
33
pack: githubsecuritylab/actions-all
44
extensible: sourceModel
55
data:
6-
- ["puppeteer/puppeteer/.github/workflows/changed-packages.yml", "*", "output.changes", "Changed files", "manual"]
6+
- ["puppeteer/puppeteer/.github/workflows/changed-packages.yml", "*", "output.changes", "filename", "manual"]

0 commit comments

Comments
 (0)