Skip to content

Commit ff3a4b9

Browse files
committed
Actions: CodeInjection
1 parent aa9fab3 commit ff3a4b9

File tree

1 file changed

+47
-2
lines changed

1 file changed

+47
-2
lines changed

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

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ private import codeql.actions.TaintTracking
33
private import codeql.actions.dataflow.ExternalFlow
44
import codeql.actions.dataflow.FlowSources
55
import codeql.actions.DataFlow
6+
import codeql.actions.security.ControlChecks
7+
import codeql.actions.security.CachePoisoningQuery
68

79
class CodeInjectionSink extends DataFlow::Node {
810
CodeInjectionSink() {
@@ -36,8 +38,51 @@ private module CodeInjectionConfig implements DataFlow::ConfigSig {
3638
)
3739
}
3840

39-
predicate observeDiffInformedIncrementalMode() {
40-
any() // TODO: Make sure that the location overrides match the query's select clause: Column 7 does not select a source or sink originating from the flow call on line 23 (/Users/d10c/src/semmle-code/ql/actions/ql/src/Security/CWE-349/CachePoisoningViaCodeInjection.ql@48:60:48:64), Column 7 does not select a source or sink originating from the flow call on line 24 (/Users/d10c/src/semmle-code/ql/actions/ql/src/Security/CWE-094/CodeInjectionCritical.ql@36:60:36:64)
41+
predicate observeDiffInformedIncrementalMode() { any() }
42+
43+
Location getASelectedSourceLocation(DataFlow::Node source) { none() }
44+
45+
Location getASelectedSinkLocation(DataFlow::Node sink) {
46+
result = sink.getLocation()
47+
or
48+
// where clause from CodeInjectionCritical.ql
49+
exists(Event event, RemoteFlowSource source | result = event.getLocation() |
50+
inPrivilegedContext(sink.asExpr(), event) and
51+
isSource(source) and
52+
source.getEventName() = event.getName() and
53+
not exists(ControlCheck check | check.protects(sink.asExpr(), event, "code-injection")) and
54+
// exclude cases where the sink is a JS script and the expression uses toJson
55+
not exists(UsesStep script |
56+
script.getCallee() = "actions/github-script" and
57+
script.getArgumentExpr("script") = sink.asExpr() and
58+
exists(getAToJsonReferenceExpression(sink.asExpr().(Expression).getExpression(), _))
59+
)
60+
)
61+
or
62+
// where clause from CachePoisoningViaCodeInjection.ql
63+
exists(Event event, LocalJob job, DataFlow::Node source | result = event.getLocation() |
64+
job = sink.asExpr().getEnclosingJob() and
65+
job.getATriggerEvent() = event and
66+
// job can be triggered by an external user
67+
event.isExternallyTriggerable() and
68+
// the checkout is not controlled by an access check
69+
isSource(source) and
70+
not exists(ControlCheck check | check.protects(source.asExpr(), event, "code-injection")) and
71+
// excluding privileged workflows since they can be exploited in easier circumstances
72+
// which is covered by `actions/code-injection/critical`
73+
not job.isPrivilegedExternallyTriggerable(event) and
74+
(
75+
// the workflow runs in the context of the default branch
76+
runsOnDefaultBranch(event)
77+
or
78+
// the workflow caller runs in the context of the default branch
79+
event.getName() = "workflow_call" and
80+
exists(ExternalJob caller |
81+
caller.getCallee() = job.getLocation().getFile().getRelativePath() and
82+
runsOnDefaultBranch(caller.getATriggerEvent())
83+
)
84+
)
85+
)
4186
}
4287
}
4388

0 commit comments

Comments
 (0)