2
2
3
3
import java
4
4
private import semmle.code.java.controlflow.Guards
5
- private import semmle.code.java.dataflow.FlowSources
6
5
private import semmle.code.java.dataflow.ExternalFlow
6
+ private import semmle.code.java.dataflow.FlowSources
7
+ private import semmle.code.java.dataflow.SSA
7
8
8
9
/** A sanitizer that protects against path injection vulnerabilities. */
9
10
abstract class PathInjectionSanitizer extends DataFlow:: Node { }
10
11
11
12
/**
12
- * Holds if `g` is guard that compares a string to a trusted value .
13
+ * Provides a set of nodes validated by a method that uses a validation guard .
13
14
*/
14
- private predicate exactStringPathMatchGuard ( Guard g , Expr e , boolean branch ) {
15
- exists ( MethodAccess ma |
15
+ private module ValidationMethod< DataFlow:: guardChecksSig / 3 validationGuard> {
16
+ /** Gets a node that is safely guarded by a method that uses the given guard check. */
17
+ DataFlow:: Node getAValidatedNode ( ) {
18
+ exists ( MethodAccess ma , int pos , RValue rv |
19
+ validationMethod ( ma .getMethod ( ) , pos ) and
20
+ ma .getArgument ( pos ) = rv and
21
+ adjacentUseUseSameVar ( rv , result .asExpr ( ) ) and
22
+ ma .getBasicBlock ( ) .bbDominates ( result .asExpr ( ) .getBasicBlock ( ) )
23
+ )
24
+ }
25
+
26
+ /**
27
+ * Holds if `m` validates its `arg`th parameter by using `validationGuard`.
28
+ */
29
+ private predicate validationMethod ( Method m , int arg ) {
30
+ exists (
31
+ Guard g , SsaImplicitInit var , ControlFlowNode exit , ControlFlowNode normexit , boolean branch
32
+ |
33
+ validationGuard ( g , var .getAUse ( ) , branch ) and
34
+ var .isParameterDefinition ( m .getParameter ( arg ) ) and
35
+ exit = m and
36
+ normexit .getANormalSuccessor ( ) = exit and
37
+ 1 = strictcount ( ControlFlowNode n | n .getANormalSuccessor ( ) = exit )
38
+ |
39
+ g .( ConditionNode ) .getABranchSuccessor ( branch ) = exit or
40
+ g .controls ( normexit .getBasicBlock ( ) , branch )
41
+ )
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Holds if `g` is guard that compares a path to a trusted value.
47
+ */
48
+ private predicate exactPathMatchGuard ( Guard g , Expr e , boolean branch ) {
49
+ exists ( MethodAccess ma , RefType t |
50
+ t instanceof TypeString or
51
+ t instanceof TypeUri or
52
+ t instanceof TypePath or
53
+ t instanceof TypeFile or
54
+ t .hasQualifiedName ( "android.net" , "Uri" )
55
+ |
56
+ ma .getMethod ( ) .getDeclaringType ( ) = t and
16
57
ma = g and
17
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
18
58
ma .getMethod ( ) .getName ( ) = [ "equals" , "equalsIgnoreCase" ] and
19
59
e = ma .getQualifier ( ) and
20
60
branch = true
21
61
)
22
62
}
23
63
24
- private class ExactStringPathMatchSanitizer extends PathInjectionSanitizer {
25
- ExactStringPathMatchSanitizer ( ) {
26
- this = DataFlow:: BarrierGuard< exactStringPathMatchGuard / 3 > :: getABarrierNode ( )
64
+ private class ExactPathMatchSanitizer extends PathInjectionSanitizer {
65
+ ExactPathMatchSanitizer ( ) {
66
+ this = DataFlow:: BarrierGuard< exactPathMatchGuard / 3 > :: getABarrierNode ( )
67
+ or
68
+ this = ValidationMethod< exactPathMatchGuard / 3 > :: getAValidatedNode ( )
27
69
}
28
70
}
29
71
30
- /**
31
- * Given input `e` = `v.method1(...).method2(...)...`, returns `v` where `v` is a `VarAccess`.
32
- *
33
- * This is used to look through field accessors such as `uri.getPath()`.
34
- */
35
- private Expr getUnderlyingVarAccess ( Expr e ) {
36
- result = getUnderlyingVarAccess ( e .( MethodAccess ) .getQualifier ( ) .getUnderlyingExpr ( ) )
37
- or
38
- result = e .( VarAccess )
39
- }
40
-
41
72
private class AllowListGuard extends Guard instanceof MethodAccess {
42
73
AllowListGuard ( ) {
43
- ( isStringPartialMatch ( this ) or isPathPartialMatch ( this ) ) and
44
- not isDisallowedWord ( super .getAnArgument ( ) )
74
+ ( isStringPrefixMatch ( this ) or isPathPrefixMatch ( this ) ) and
75
+ not isDisallowedPrefix ( super .getAnArgument ( ) )
45
76
}
46
77
47
- Expr getCheckedExpr ( ) {
48
- result = getUnderlyingVarAccess ( super .getQualifier ( ) .getUnderlyingExpr ( ) )
49
- }
78
+ Expr getCheckedExpr ( ) { result = super .getQualifier ( ) }
50
79
}
51
80
52
81
/**
@@ -55,116 +84,119 @@ private class AllowListGuard extends Guard instanceof MethodAccess {
55
84
* or a sanitizer (`PathNormalizeSanitizer`), to ensure any internal `..` components are removed from the path.
56
85
*/
57
86
private predicate allowListGuard ( Guard g , Expr e , boolean branch ) {
58
- e = g .( AllowListGuard ) .getCheckedExpr ( ) and
59
87
branch = true and
60
- (
61
- // Either a path normalization sanitizer comes before the guard,
62
- exists ( PathNormalizeSanitizer sanitizer | DataFlow:: localExprFlow ( sanitizer , e ) )
88
+ TaintTracking:: localExprTaint ( e , g .( AllowListGuard ) .getCheckedExpr ( ) ) and
89
+ exists ( MethodAccess previousGuard |
90
+ TaintTracking:: localExprTaint ( previousGuard .( PathNormalizeSanitizer ) ,
91
+ g .( AllowListGuard ) .getCheckedExpr ( ) )
63
92
or
64
- // or a check like `!path.contains("..")` comes before the guard
65
- exists ( PathTraversalGuard previousGuard |
66
- DataFlow:: localExprFlow ( previousGuard .getCheckedExpr ( ) , e ) and
67
- previousGuard .controls ( g .getBasicBlock ( ) .( ConditionBlock ) , false )
68
- )
93
+ previousGuard .( PathTraversalGuard ) .controls ( g .getBasicBlock ( ) .( ConditionBlock ) , false )
69
94
)
70
95
}
71
96
72
97
private class AllowListSanitizer extends PathInjectionSanitizer {
73
- AllowListSanitizer ( ) { this = DataFlow:: BarrierGuard< allowListGuard / 3 > :: getABarrierNode ( ) }
98
+ AllowListSanitizer ( ) {
99
+ this = DataFlow:: BarrierGuard< allowListGuard / 3 > :: getABarrierNode ( ) or
100
+ this = ValidationMethod< allowListGuard / 3 > :: getAValidatedNode ( )
101
+ }
74
102
}
75
103
76
104
/**
77
105
* Holds if `g` is a guard that considers a path safe because it is checked for `..` components, having previously
78
106
* been checked for a trusted prefix.
79
107
*/
80
108
private predicate dotDotCheckGuard ( Guard g , Expr e , boolean branch ) {
81
- e = g .( PathTraversalGuard ) .getCheckedExpr ( ) and
82
109
branch = false and
83
- // The same value has previously been checked against a list of allowed prefixes:
84
- exists ( AllowListGuard previousGuard |
85
- DataFlow:: localExprFlow ( previousGuard .getCheckedExpr ( ) , e ) and
86
- previousGuard .controls ( g .getBasicBlock ( ) .( ConditionBlock ) , true )
110
+ TaintTracking:: localExprTaint ( e , g .( PathTraversalGuard ) .getCheckedExpr ( ) ) and
111
+ exists ( MethodAccess previousGuard |
112
+ previousGuard .( AllowListGuard ) .controls ( g .getBasicBlock ( ) .( ConditionBlock ) , true )
113
+ or
114
+ previousGuard .( BlockListGuard ) .controls ( g .getBasicBlock ( ) .( ConditionBlock ) , false )
87
115
)
88
116
}
89
117
90
118
private class DotDotCheckSanitizer extends PathInjectionSanitizer {
91
- DotDotCheckSanitizer ( ) { this = DataFlow:: BarrierGuard< dotDotCheckGuard / 3 > :: getABarrierNode ( ) }
119
+ DotDotCheckSanitizer ( ) {
120
+ this = DataFlow:: BarrierGuard< dotDotCheckGuard / 3 > :: getABarrierNode ( ) or
121
+ this = ValidationMethod< dotDotCheckGuard / 3 > :: getAValidatedNode ( )
122
+ }
92
123
}
93
124
94
125
private class BlockListGuard extends Guard instanceof MethodAccess {
95
126
BlockListGuard ( ) {
96
- ( isStringPartialMatch ( this ) or isPathPartialMatch ( this ) ) and
127
+ ( isStringPrefixMatch ( this ) or isPathPrefixMatch ( this ) ) and
128
+ isDisallowedPrefix ( super .getAnArgument ( ) )
129
+ or
130
+ isStringPartialMatch ( this ) and
97
131
isDisallowedWord ( super .getAnArgument ( ) )
98
132
}
99
133
100
- Expr getCheckedExpr ( ) {
101
- result = getUnderlyingVarAccess ( super .getQualifier ( ) .getUnderlyingExpr ( ) )
102
- }
134
+ Expr getCheckedExpr ( ) { result = super .getQualifier ( ) }
103
135
}
104
136
105
137
/**
106
138
* Holds if `g` is a guard that considers a string safe because it is checked against a blocklist of known dangerous values.
107
- * This requires a prior check for URL encoding concealing a forbidden value , either a guard (`UrlEncodingGuard `)
108
- * or a sanitizer (`UrlDecodeSanitizer`) .
139
+ * This requires additional protection against path traversal , either another guard (`PathTraversalGuard `)
140
+ * or a sanitizer (`PathNormalizeSanitizer`), to ensure any internal `..` components are removed from the path .
109
141
*/
110
142
private predicate blockListGuard ( Guard g , Expr e , boolean branch ) {
111
- e = g .( BlockListGuard ) .getCheckedExpr ( ) and
112
143
branch = false and
113
- (
114
- // Either `e` has been URL decoded:
115
- exists ( UrlDecodeSanitizer sanitizer | DataFlow:: localExprFlow ( sanitizer , e ) )
144
+ TaintTracking:: localExprTaint ( e , g .( BlockListGuard ) .getCheckedExpr ( ) ) and
145
+ exists ( MethodAccess previousGuard |
146
+ TaintTracking:: localExprTaint ( previousGuard .( PathNormalizeSanitizer ) ,
147
+ g .( BlockListGuard ) .getCheckedExpr ( ) )
116
148
or
117
- // or `e` has previously been checked for URL encoding sequences:
118
- exists ( UrlEncodingGuard previousGuard |
119
- DataFlow:: localExprFlow ( previousGuard .getCheckedExpr ( ) , e ) and
120
- previousGuard .controls ( g .getBasicBlock ( ) , false )
121
- )
149
+ previousGuard .( PathTraversalGuard ) .controls ( g .getBasicBlock ( ) .( ConditionBlock ) , false )
122
150
)
123
151
}
124
152
125
153
private class BlockListSanitizer extends PathInjectionSanitizer {
126
- BlockListSanitizer ( ) { this = DataFlow:: BarrierGuard< blockListGuard / 3 > :: getABarrierNode ( ) }
154
+ BlockListSanitizer ( ) {
155
+ this = DataFlow:: BarrierGuard< blockListGuard / 3 > :: getABarrierNode ( ) or
156
+ this = ValidationMethod< blockListGuard / 3 > :: getAValidatedNode ( )
157
+ }
127
158
}
128
159
129
- /**
130
- * Holds if `g` is a guard that considers a string safe because it is checked for URL encoding sequences,
131
- * having previously been checked against a block-list of forbidden values.
132
- */
133
- private predicate urlEncodingGuard ( Guard g , Expr e , boolean branch ) {
134
- e = g .( UrlEncodingGuard ) .getCheckedExpr ( ) and
135
- branch = false and
136
- exists ( BlockListGuard previousGuard |
137
- DataFlow:: localExprFlow ( previousGuard .getCheckedExpr ( ) , e ) and
138
- previousGuard .controls ( g .getBasicBlock ( ) , false )
160
+ private predicate isStringPrefixMatch ( MethodAccess ma ) {
161
+ exists ( Method m | m = ma .getMethod ( ) and m .getDeclaringType ( ) instanceof TypeString |
162
+ m .hasName ( "startsWith" )
163
+ or
164
+ m .hasName ( "regionMatches" ) and
165
+ ma .getArgument ( 0 ) .( CompileTimeConstantExpr ) .getIntValue ( ) = 0
166
+ or
167
+ m .hasName ( "matches" ) and
168
+ not ma .getArgument ( 0 ) .( CompileTimeConstantExpr ) .getStringValue ( ) .matches ( ".*%" )
139
169
)
140
170
}
141
171
142
- private class UrlEncodingSanitizer extends PathInjectionSanitizer {
143
- UrlEncodingSanitizer ( ) { this = DataFlow:: BarrierGuard< urlEncodingGuard / 3 > :: getABarrierNode ( ) }
144
- }
145
-
146
172
/**
147
173
* Holds if `ma` is a call to a method that checks a partial string match.
148
174
*/
149
175
private predicate isStringPartialMatch ( MethodAccess ma ) {
150
176
ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
151
- ma .getMethod ( )
152
- .hasName ( [ "contains" , "startsWith" , "matches" , "regionMatches" , "indexOf" , "lastIndexOf" ] )
177
+ ma .getMethod ( ) .hasName ( [ "contains" , "matches" , "regionMatches" , "indexOf" , "lastIndexOf" ] )
153
178
}
154
179
155
180
/**
156
- * Holds if `ma` is a call to a method that checks a partial path match .
181
+ * Holds if `ma` is a call to a method that checks whether a path starts with a prefix .
157
182
*/
158
- private predicate isPathPartialMatch ( MethodAccess ma ) {
159
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypePath and
160
- ma .getMethod ( ) .hasName ( "startsWith" )
161
- or
162
- ma .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "kotlin.io" , "FilesKt" ) and
163
- ma .getMethod ( ) .hasName ( "startsWith" )
183
+ private predicate isPathPrefixMatch ( MethodAccess ma ) {
184
+ exists ( RefType t |
185
+ t instanceof TypePath
186
+ or
187
+ t .hasQualifiedName ( "kotlin.io" , "FilesKt" )
188
+ |
189
+ t = ma .getMethod ( ) .getDeclaringType ( ) and
190
+ ma .getMethod ( ) .hasName ( "startsWith" )
191
+ )
192
+ }
193
+
194
+ private predicate isDisallowedPrefix ( CompileTimeConstantExpr prefix ) {
195
+ prefix .getStringValue ( ) .matches ( [ "%WEB-INF%" , "/data%" ] )
164
196
}
165
197
166
198
private predicate isDisallowedWord ( CompileTimeConstantExpr word ) {
167
- word .getStringValue ( ) .matches ( [ "%WEB-INF% " , "%META-INF%" , "%..% "] )
199
+ word .getStringValue ( ) .matches ( [ "/ " , "\\ " ] )
168
200
}
169
201
170
202
/** A complementary guard that protects against path traversal, by looking for the literal `..`. */
@@ -175,9 +207,7 @@ private class PathTraversalGuard extends Guard instanceof MethodAccess {
175
207
super .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".."
176
208
}
177
209
178
- Expr getCheckedExpr ( ) {
179
- result = getUnderlyingVarAccess ( super .getQualifier ( ) .getUnderlyingExpr ( ) )
180
- }
210
+ Expr getCheckedExpr ( ) { result = super .getQualifier ( ) }
181
211
}
182
212
183
213
/** A complementary sanitizer that protects against path traversal using path normalization. */
@@ -196,30 +226,6 @@ private class PathNormalizeSanitizer extends MethodAccess {
196
226
}
197
227
}
198
228
199
- /** A complementary guard that protects against double URL encoding, by looking for the literal `%`. */
200
- private class UrlEncodingGuard extends Guard instanceof MethodAccess {
201
- UrlEncodingGuard ( ) {
202
- super .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
203
- super .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
204
- super .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "%"
205
- }
206
-
207
- Expr getCheckedExpr ( ) { result = super .getQualifier ( ) }
208
- }
209
-
210
- /** A complementary sanitizer that protects against double URL encoding using URL decoding. */
211
- private class UrlDecodeSanitizer extends MethodAccess {
212
- UrlDecodeSanitizer ( ) {
213
- exists ( RefType t |
214
- this .getMethod ( ) .getDeclaringType ( ) = t and
215
- this .getMethod ( ) .hasName ( "decode" )
216
- |
217
- t .hasQualifiedName ( "java.net" , "URLDecoder" ) or
218
- t .hasQualifiedName ( "android.net" , "Uri" )
219
- )
220
- }
221
- }
222
-
223
229
/** A node with path normalization. */
224
230
class NormalizedPathNode extends DataFlow:: Node {
225
231
NormalizedPathNode ( ) {
0 commit comments