Skip to content

Commit 418e4b4

Browse files
committed
1 parent bbda290 commit 418e4b4

File tree

3 files changed

+58
-28
lines changed

3 files changed

+58
-28
lines changed

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

Lines changed: 54 additions & 0 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() {
@@ -11,6 +13,46 @@ class CodeInjectionSink extends DataFlow::Node {
1113
}
1214
}
1315

16+
/**
17+
* Get the relevant event for the sink in CodeInjectionCritical.ql.
18+
*/
19+
Event getRelevantCriticalEventForSink(DataFlow::Node sink) {
20+
inPrivilegedContext(sink.asExpr(), result) and
21+
not exists(ControlCheck check | check.protects(sink.asExpr(), result, "code-injection")) and
22+
// exclude cases where the sink is a JS script and the expression uses toJson
23+
not exists(UsesStep script |
24+
script.getCallee() = "actions/github-script" and
25+
script.getArgumentExpr("script") = sink.asExpr() and
26+
exists(getAToJsonReferenceExpression(sink.asExpr().(Expression).getExpression(), _))
27+
)
28+
}
29+
30+
/**
31+
* Get the relevant event for the sink in CachePoisoningViaCodeInjection.ql.
32+
*/
33+
Event getRelevantCachePoisoningEventForSink(DataFlow::Node sink) {
34+
exists(LocalJob job |
35+
job = sink.asExpr().getEnclosingJob() and
36+
job.getATriggerEvent() = result and
37+
// job can be triggered by an external user
38+
result.isExternallyTriggerable() and
39+
// excluding privileged workflows since they can be exploited in easier circumstances
40+
// which is covered by `actions/code-injection/critical`
41+
not job.isPrivilegedExternallyTriggerable(result) and
42+
(
43+
// the workflow runs in the context of the default branch
44+
runsOnDefaultBranch(result)
45+
or
46+
// the workflow caller runs in the context of the default branch
47+
result.getName() = "workflow_call" and
48+
exists(ExternalJob caller |
49+
caller.getCallee() = job.getLocation().getFile().getRelativePath() and
50+
runsOnDefaultBranch(caller.getATriggerEvent())
51+
)
52+
)
53+
)
54+
}
55+
1456
/**
1557
* A taint-tracking configuration for unsafe user input
1658
* that is used to construct and evaluate a code script.
@@ -35,6 +77,18 @@ private module CodeInjectionConfig implements DataFlow::ConfigSig {
3577
exists(run.getScript().getAFileReadCommand())
3678
)
3779
}
80+
81+
predicate observeDiffInformedIncrementalMode() { any() }
82+
83+
Location getASelectedSourceLocation(DataFlow::Node source) { none() }
84+
85+
Location getASelectedSinkLocation(DataFlow::Node sink) {
86+
result = sink.getLocation()
87+
or
88+
result = getRelevantCriticalEventForSink(sink).getLocation()
89+
or
90+
result = getRelevantCachePoisoningEventForSink(sink).getLocation()
91+
}
3892
}
3993

4094
/** Tracks flow of unsafe user input that is used to construct and evaluate a code script. */

actions/ql/src/Security/CWE-094/CodeInjectionCritical.ql

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,8 @@ import codeql.actions.security.ControlChecks
2222
from CodeInjectionFlow::PathNode source, CodeInjectionFlow::PathNode sink, Event event
2323
where
2424
CodeInjectionFlow::flowPath(source, sink) and
25-
inPrivilegedContext(sink.getNode().asExpr(), event) and
26-
source.getNode().(RemoteFlowSource).getEventName() = event.getName() and
27-
not exists(ControlCheck check | check.protects(sink.getNode().asExpr(), event, "code-injection")) and
28-
// exclude cases where the sink is a JS script and the expression uses toJson
29-
not exists(UsesStep script |
30-
script.getCallee() = "actions/github-script" and
31-
script.getArgumentExpr("script") = sink.getNode().asExpr() and
32-
exists(getAToJsonReferenceExpression(sink.getNode().asExpr().(Expression).getExpression(), _))
33-
)
25+
event = getRelevantCriticalEventForSink(sink.getNode()) and
26+
source.getNode().(RemoteFlowSource).getEventName() = event.getName()
3427
select sink.getNode(), source, sink,
3528
"Potential code injection in $@, which may be controlled by an external user ($@).", sink,
3629
sink.getNode().asExpr().(Expression).getRawExpression(), event, event.getName()

actions/ql/src/Security/CWE-349/CachePoisoningViaCodeInjection.ql

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,13 @@ import codeql.actions.security.CachePoisoningQuery
1818
import CodeInjectionFlow::PathGraph
1919
import codeql.actions.security.ControlChecks
2020

21-
from CodeInjectionFlow::PathNode source, CodeInjectionFlow::PathNode sink, LocalJob job, Event event
21+
from CodeInjectionFlow::PathNode source, CodeInjectionFlow::PathNode sink, Event event
2222
where
2323
CodeInjectionFlow::flowPath(source, sink) and
24-
job = sink.getNode().asExpr().getEnclosingJob() and
25-
job.getATriggerEvent() = event and
26-
// job can be triggered by an external user
27-
event.isExternallyTriggerable() and
24+
event = getRelevantCachePoisoningEventForSink(sink.getNode()) and
2825
// the checkout is not controlled by an access check
2926
not exists(ControlCheck check |
3027
check.protects(source.getNode().asExpr(), event, "code-injection")
31-
) and
32-
// excluding privileged workflows since they can be exploited in easier circumstances
33-
// which is covered by `actions/code-injection/critical`
34-
not job.isPrivilegedExternallyTriggerable(event) and
35-
(
36-
// the workflow runs in the context of the default branch
37-
runsOnDefaultBranch(event)
38-
or
39-
// the workflow caller runs in the context of the default branch
40-
event.getName() = "workflow_call" and
41-
exists(ExternalJob caller |
42-
caller.getCallee() = job.getLocation().getFile().getRelativePath() and
43-
runsOnDefaultBranch(caller.getATriggerEvent())
44-
)
4528
)
4629
select sink.getNode(), source, sink,
4730
"Unprivileged code injection in $@, which may lead to cache poisoning ($@).", sink,

0 commit comments

Comments
 (0)