Skip to content

Commit 5706e8b

Browse files
committed
Improve PathSanitizer
Rename PathTraversalSanitizer to PathInjectionSanitizer
1 parent 50ad234 commit 5706e8b

File tree

4 files changed

+47
-41
lines changed

4 files changed

+47
-41
lines changed

java/ql/lib/semmle/code/java/security/PathSanitizer.qll

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1+
/** Provides classes and predicates to reason about sanitization of path injection vulnerabilities. */
2+
13
import java
24
private import semmle.code.java.controlflow.Guards
35
private import semmle.code.java.dataflow.FlowSources
46
private import semmle.code.java.dataflow.ExternalFlow
57

6-
/**
7-
* DEPRECATED: Use `PathTraversalSanitizer` instead.
8-
*
9-
* A barrier guard that protects against path traversal vulnerabilities.
10-
*/
11-
abstract deprecated class PathTraversalBarrierGuard extends DataFlow::BarrierGuard { }
12-
13-
/** A sanitizer that protects against path traversal vulnerabilities. */
14-
abstract class PathTraversalSanitizer extends DataFlow::Node { }
8+
/** A sanitizer that protects against path injection vulnerabilities. */
9+
abstract class PathInjectionSanitizer extends DataFlow::Node { }
1510

1611
/**
1712
* Holds if `g` is guard that compares a string to a trusted value.
@@ -26,7 +21,7 @@ private predicate exactStringPathMatchGuard(Guard g, Expr e, boolean branch) {
2621
)
2722
}
2823

29-
private class ExactStringPathMatchSanitizer extends PathTraversalSanitizer {
24+
private class ExactStringPathMatchSanitizer extends PathInjectionSanitizer {
3025
ExactStringPathMatchSanitizer() {
3126
this = DataFlow::BarrierGuard<exactStringPathMatchGuard/3>::getABarrierNode()
3227
}
@@ -38,7 +33,7 @@ private class ExactStringPathMatchSanitizer extends PathTraversalSanitizer {
3833
* This is used to look through field accessors such as `uri.getPath()`.
3934
*/
4035
private Expr getUnderlyingVarAccess(Expr e) {
41-
result = getUnderlyingVarAccess(e.(MethodAccess).getQualifier())
36+
result = getUnderlyingVarAccess(e.(MethodAccess).getQualifier().getUnderlyingExpr())
4237
or
4338
result = e.(VarAccess)
4439
}
@@ -49,7 +44,9 @@ private class AllowListGuard extends Guard instanceof MethodAccess {
4944
not isDisallowedWord(super.getAnArgument())
5045
}
5146

52-
Expr getCheckedExpr() { result = getUnderlyingVarAccess(super.getQualifier()) }
47+
Expr getCheckedExpr() {
48+
result = getUnderlyingVarAccess(super.getQualifier().getUnderlyingExpr())
49+
}
5350
}
5451

5552
/**
@@ -72,7 +69,7 @@ private predicate allowListGuard(Guard g, Expr e, boolean branch) {
7269
)
7370
}
7471

75-
private class AllowListSanitizer extends PathTraversalSanitizer {
72+
private class AllowListSanitizer extends PathInjectionSanitizer {
7673
AllowListSanitizer() { this = DataFlow::BarrierGuard<allowListGuard/3>::getABarrierNode() }
7774
}
7875

@@ -90,7 +87,7 @@ private predicate dotDotCheckGuard(Guard g, Expr e, boolean branch) {
9087
)
9188
}
9289

93-
private class DotDotCheckSanitizer extends PathTraversalSanitizer {
90+
private class DotDotCheckSanitizer extends PathInjectionSanitizer {
9491
DotDotCheckSanitizer() { this = DataFlow::BarrierGuard<dotDotCheckGuard/3>::getABarrierNode() }
9592
}
9693

@@ -100,7 +97,9 @@ private class BlockListGuard extends Guard instanceof MethodAccess {
10097
isDisallowedWord(super.getAnArgument())
10198
}
10299

103-
Expr getCheckedExpr() { result = getUnderlyingVarAccess(super.getQualifier()) }
100+
Expr getCheckedExpr() {
101+
result = getUnderlyingVarAccess(super.getQualifier().getUnderlyingExpr())
102+
}
104103
}
105104

106105
/**
@@ -123,7 +122,7 @@ private predicate blockListGuard(Guard g, Expr e, boolean branch) {
123122
)
124123
}
125124

126-
private class BlockListSanitizer extends PathTraversalSanitizer {
125+
private class BlockListSanitizer extends PathInjectionSanitizer {
127126
BlockListSanitizer() { this = DataFlow::BarrierGuard<blockListGuard/3>::getABarrierNode() }
128127
}
129128

@@ -140,7 +139,7 @@ private predicate urlEncodingGuard(Guard g, Expr e, boolean branch) {
140139
)
141140
}
142141

143-
private class UrlEncodingSanitizer extends PathTraversalSanitizer {
142+
private class UrlEncodingSanitizer extends PathInjectionSanitizer {
144143
UrlEncodingSanitizer() { this = DataFlow::BarrierGuard<urlEncodingGuard/3>::getABarrierNode() }
145144
}
146145

@@ -149,38 +148,51 @@ private class UrlEncodingSanitizer extends PathTraversalSanitizer {
149148
*/
150149
private predicate isStringPartialMatch(MethodAccess ma) {
151150
ma.getMethod().getDeclaringType() instanceof TypeString and
152-
ma.getMethod().getName() =
153-
["contains", "startsWith", "matches", "regionMatches", "indexOf", "lastIndexOf"]
151+
ma.getMethod()
152+
.hasName(["contains", "startsWith", "matches", "regionMatches", "indexOf", "lastIndexOf"])
154153
}
155154

156155
/**
157-
* Holds if `ma` is a call to a method of `java.nio.file.Path` that checks a partial path match.
156+
* Holds if `ma` is a call to a method that checks a partial path match.
158157
*/
159158
private predicate isPathPartialMatch(MethodAccess ma) {
160159
ma.getMethod().getDeclaringType() instanceof TypePath and
161-
ma.getMethod().getName() = "startsWith"
160+
ma.getMethod().hasName("startsWith")
161+
or
162+
ma.getMethod().getDeclaringType().hasQualifiedName("kotlin.io", "FilesKt") and
163+
ma.getMethod().hasName("startsWith")
162164
}
163165

164166
private predicate isDisallowedWord(CompileTimeConstantExpr word) {
165167
word.getStringValue().matches(["%WEB-INF%", "%META-INF%", "%..%"])
166168
}
167169

168170
/** A complementary guard that protects against path traversal, by looking for the literal `..`. */
169-
class PathTraversalGuard extends Guard instanceof MethodAccess {
171+
private class PathTraversalGuard extends Guard instanceof MethodAccess {
170172
PathTraversalGuard() {
171173
super.getMethod().getDeclaringType() instanceof TypeString and
172174
super.getMethod().hasName(["contains", "indexOf"]) and
173175
super.getAnArgument().(CompileTimeConstantExpr).getStringValue() = ".."
174176
}
175177

176-
Expr getCheckedExpr() { result = getUnderlyingVarAccess(super.getQualifier()) }
178+
Expr getCheckedExpr() {
179+
result = getUnderlyingVarAccess(super.getQualifier().getUnderlyingExpr())
180+
}
177181
}
178182

179183
/** A complementary sanitizer that protects against path traversal using path normalization. */
180184
private class PathNormalizeSanitizer extends MethodAccess {
181185
PathNormalizeSanitizer() {
182-
this.getMethod().getDeclaringType().hasQualifiedName("java.nio.file", "Path") and
183-
this.getMethod().hasName("normalize")
186+
exists(RefType t |
187+
t instanceof TypePath or
188+
t.hasQualifiedName("kotlin.io", "FilesKt")
189+
|
190+
this.getMethod().getDeclaringType() = t and
191+
this.getMethod().hasName("normalize")
192+
)
193+
or
194+
this.getMethod().getDeclaringType() instanceof TypeFile and
195+
this.getMethod().hasName(["getCanonicalPath", "getCanonicalFile"])
184196
}
185197
}
186198

@@ -198,8 +210,13 @@ private class UrlEncodingGuard extends Guard instanceof MethodAccess {
198210
/** A complementary sanitizer that protects against double URL encoding using URL decoding. */
199211
private class UrlDecodeSanitizer extends MethodAccess {
200212
UrlDecodeSanitizer() {
201-
this.getMethod().getDeclaringType().hasQualifiedName("java.net", "URLDecoder") and
202-
this.getMethod().hasName("decode")
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+
)
203220
}
204221
}
205222

@@ -209,14 +226,3 @@ class NormalizedPathNode extends DataFlow::Node {
209226
TaintTracking::localExprTaint(this.asExpr(), any(PathNormalizeSanitizer ma))
210227
}
211228
}
212-
213-
/** Data model related to `java.nio.file.Path`. */
214-
private class PathDataModel extends SummaryModelCsv {
215-
override predicate row(string row) {
216-
row =
217-
[
218-
"java.nio.file;Paths;true;get;;;Argument[0];ReturnValue;taint;manual",
219-
"java.nio.file;Path;true;normalize;;;Argument[-1];ReturnValue;taint;manual"
220-
]
221-
}
222-
}

java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class InjectFilePathConfig extends TaintTracking::Configuration {
3131
override predicate isSanitizer(DataFlow::Node node) {
3232
exists(Type t | t = node.getType() | t instanceof BoxedType or t instanceof PrimitiveType)
3333
or
34-
node instanceof PathTraversalSanitizer
34+
node instanceof PathInjectionSanitizer
3535
}
3636
}
3737

java/ql/src/experimental/Security/CWE/CWE-200/InsecureWebResourceResponse.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class InsecureWebResourceResponseConfig extends TaintTracking::Configuration {
2424

2525
override predicate isSink(DataFlow::Node sink) { sink instanceof WebResourceResponseSink }
2626

27-
override predicate isSanitizer(DataFlow::Node node) { node instanceof PathTraversalSanitizer }
27+
override predicate isSanitizer(DataFlow::Node node) { node instanceof PathInjectionSanitizer }
2828
}
2929

3030
from DataFlow::PathNode source, DataFlow::PathNode sink, InsecureWebResourceResponseConfig conf

java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class UnsafeUrlForwardFlowConfig extends TaintTracking::Configuration {
3737

3838
override predicate isSanitizer(DataFlow::Node node) {
3939
node instanceof UnsafeUrlForwardSanitizer or
40-
node instanceof PathTraversalSanitizer
40+
node instanceof PathInjectionSanitizer
4141
}
4242

4343
override DataFlow::FlowFeature getAFeature() {

0 commit comments

Comments
 (0)