1
1
/**
2
- * @name Cache Poisoning
2
+ * @name Cache Poisoning via caching of untrusted files
3
3
* @description The cache can be poisoned by untrusted code, leading to a cache poisoning attack.
4
4
* @kind path-problem
5
5
* @problem.severity error
6
6
* @precision high
7
7
* @security-severity 7.5
8
- * @id actions/cache-poisoning
8
+ * @id actions/cache-poisoning/direct-cache
9
9
* @tags actions
10
10
* security
11
11
* external/cwe/cwe-349
@@ -45,6 +45,8 @@ query predicate edges(Step a, Step b) { a.getNextStep() = b }
45
45
46
46
from LocalJob j , Event e , Step source , Step s , string message , string path
47
47
where
48
+ // the job checkouts untrusted code from a pull request or downloads an untrusted artifact
49
+ j .getAStep ( ) = source and
48
50
(
49
51
source instanceof PRHeadCheckoutStep and
50
52
message = "due to privilege checkout of untrusted code." and
@@ -54,46 +56,35 @@ where
54
56
message = "due to downloading an untrusted artifact." and
55
57
path = source .( UntrustedArtifactDownloadStep ) .getPath ( )
56
58
) and
59
+ // the checkout/download is not controlled by an access check
60
+ not exists ( ControlCheck check | check .protects ( source , j .getATriggerEvent ( ) ) ) and
57
61
j .getATriggerEvent ( ) = e and
58
62
// job can be triggered by an external user
59
63
e .isExternallyTriggerable ( ) and
60
- // the checkout is not controlled by an access check
61
- not exists ( ControlCheck check | check .protects ( source , j .getATriggerEvent ( ) ) ) and
62
64
(
63
65
// the workflow runs in the context of the default branch
64
66
runsOnDefaultBranch ( e )
65
67
or
66
- // the workflow caller runs in the context of the default branch
68
+ // the workflow's caller runs in the context of the default branch
67
69
e .getName ( ) = "workflow_call" and
68
70
exists ( ExternalJob caller |
69
71
caller .getCallee ( ) = j .getLocation ( ) .getFile ( ) .getRelativePath ( ) and
70
72
runsOnDefaultBranch ( caller .getATriggerEvent ( ) )
71
73
)
72
74
) and
73
- // the job checkouts untrusted code from a pull request
74
- j .getAStep ( ) = source and
75
+ // the job writes to the cache
76
+ // (No need to follow the checkout/download step since the cache is normally write after the job completes)
77
+ j .getAStep ( ) = s and
78
+ s instanceof CacheWritingStep and
75
79
(
76
- // the job writes to the cache
77
- // (No need to follow the checkout step as the cache writing is normally done after the job completes)
78
- j .getAStep ( ) = s and
79
- s instanceof CacheWritingStep and
80
- (
81
- // we dont know what code can be controlled by the attacker
82
- path = "?"
83
- or
84
- // we dont know what files are being cached
85
- s .( CacheWritingStep ) .getPath ( ) = "?"
86
- or
87
- // the cache writing step reads from the path the attacker can control
88
- not path = "?" and controlledCachePath ( s .( CacheWritingStep ) .getPath ( ) , path )
89
- ) and
90
- not s instanceof PoisonableStep
80
+ // we dont know what code can be controlled by the attacker
81
+ path = "?"
91
82
or
92
- // the job executes checked-out code
93
- // (The cache specific token can be leaked even for non-privileged workflows)
94
- source . getAFollowingStep ( ) = s and
95
- s instanceof PoisonableStep and
96
- // excluding privileged workflows since they can be exploited in easier circumstances
97
- not j . isPrivileged ( )
98
- )
83
+ // we dont know what files are being cached
84
+ s . ( CacheWritingStep ) . getPath ( ) = "?"
85
+ or
86
+ // the cache writing step reads from a path the attacker can control
87
+ not path = "?" and controlledCachePath ( s . ( CacheWritingStep ) . getPath ( ) , path )
88
+ ) and
89
+ not s instanceof PoisonableStep
99
90
select s , source , s , "Potential cache poisoning in the context of the default branch " + message
0 commit comments