@@ -19,15 +19,15 @@ import semmle.code.java.dataflow.NullGuards
1919import DataFlow:: PathGraph
2020
2121/**
22- * Holds if `ma` is a call to a method that checks exact match of string, probably a whitelisted one .
22+ * Holds if `ma` is a call to a method that checks exact match of string.
2323 */
2424predicate isExactStringPathMatch ( MethodAccess ma ) {
2525 ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
2626 ma .getMethod ( ) .getName ( ) = [ "equals" , "equalsIgnoreCase" ]
2727}
2828
2929/**
30- * Holds if `ma` is a call to a method that checks a path string, probably a whitelisted one .
30+ * Holds if `ma` is a call to a method that checks a path string.
3131 */
3232predicate isStringPathMatch ( MethodAccess ma ) {
3333 ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
@@ -36,46 +36,44 @@ predicate isStringPathMatch(MethodAccess ma) {
3636}
3737
3838/**
39- * Holds if `ma` is a call to a method of `java.nio.file.Path` that checks a path, probably
40- * a whitelisted one.
39+ * Holds if `ma` is a call to a method of `java.nio.file.Path` that checks a path.
4140 */
4241predicate isFilePathMatch ( MethodAccess ma ) {
4342 ma .getMethod ( ) .getDeclaringType ( ) instanceof TypePath and
4443 ma .getMethod ( ) .getName ( ) = "startsWith"
4544}
4645
4746/**
48- * Holds if `ma` is a call to a method that checks an input doesn't match using the `!`
49- * logical negation expression.
47+ * Holds if `ma` protects against path traversal, by either:
48+ * * looking for the literal `..`
49+ * * performing path normalization
5050 */
51- predicate checkNoPathMatch ( MethodAccess ma ) {
52- exists ( LogNotExpr lne |
53- ( isStringPathMatch ( ma ) or isFilePathMatch ( ma ) ) and
54- lne .getExpr ( ) = ma
55- )
56- }
57-
58- /**
59- * Holds if `ma` is a call to a method that checks special characters `..` used in path traversal.
60- */
61- predicate isPathTraversalCheck ( MethodAccess ma ) {
51+ predicate isPathTraversalCheck ( MethodAccess ma , Expr checked ) {
6252 ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
6353 ma .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
64- ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".."
54+ ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".." and
55+ ma .( Guard ) .controls ( checked .getBasicBlock ( ) , false )
56+ or
57+ ma .getMethod ( ) instanceof PathNormalizeMethod and
58+ checked = ma
6559}
6660
6761/**
68- * Holds if `ma` is a call to a method that decodes a URL string or check URL encoding.
62+ * Holds if `ma` protects against double URL encoding, by either:
63+ * * looking for the literal `%`
64+ * * performing URL decoding
6965 */
70- predicate isPathDecoding ( MethodAccess ma ) {
66+ predicate isURLEncodingCheck ( MethodAccess ma , Expr checked ) {
7167 // Search the special character `%` used in url encoding
7268 ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
7369 ma .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
74- ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "%"
70+ ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "%" and
71+ ma .( Guard ) .controls ( checked .getBasicBlock ( ) , false )
7572 or
7673 // Call to `URLDecoder` assuming the implementation handles double encoding correctly
7774 ma .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "java.net" , "URLDecoder" ) and
78- ma .getMethod ( ) .hasName ( "decode" )
75+ ma .getMethod ( ) .hasName ( "decode" ) and
76+ checked = ma
7977}
8078
8179/** The Java method `normalize` of `java.nio.file.Path`. */
@@ -86,90 +84,54 @@ class PathNormalizeMethod extends Method {
8684 }
8785}
8886
87+ private predicate isDisallowedWord ( CompileTimeConstantExpr word ) {
88+ word .getStringValue ( ) .matches ( [ "%WEB-INF%" , "%META-INF%" , "%..%" ] )
89+ }
90+
91+ private predicate isAllowListCheck ( MethodAccess ma ) {
92+ ( isStringPathMatch ( ma ) or isFilePathMatch ( ma ) ) and
93+ not isDisallowedWord ( ma .getAnArgument ( ) )
94+ }
95+
96+ private predicate isDisallowListCheck ( MethodAccess ma ) {
97+ ( isStringPathMatch ( ma ) or isFilePathMatch ( ma ) ) and
98+ isDisallowedWord ( ma .getAnArgument ( ) )
99+ }
100+
89101/**
90- * Sanitizer to check the following scenarios in a web application :
102+ * A guard that checks a path with the following methods :
91103 * 1. Exact string match
92- * 2. String startsWith or match check with path traversal validation
93- * 3. String not startsWith or not match check with decoding processing
94- * 4. java.nio.file.Path startsWith check having path normalization
104+ * 2. Path matches allowed values (needs to protect against path traversal)
105+ * 3. Path matches disallowed values (needs to protect against URL encoding)
95106 */
96107private class PathMatchGuard extends DataFlow:: BarrierGuard {
97108 PathMatchGuard ( ) {
98- isExactStringPathMatch ( this )
99- or
100- isStringPathMatch ( this ) and
101- not checkNoPathMatch ( this ) and
102- exists ( MethodAccess tma |
103- isPathTraversalCheck ( tma ) and
104- DataFlow:: localExprFlow ( this .( MethodAccess ) .getQualifier ( ) , tma .getQualifier ( ) )
105- )
106- or
107- checkNoPathMatch ( this ) and
108- exists ( MethodAccess dma |
109- isPathDecoding ( dma ) and
110- DataFlow:: localExprFlow ( dma , this .( MethodAccess ) .getQualifier ( ) )
111- )
112- or
113- isFilePathMatch ( this ) and
114- exists ( MethodAccess pma |
115- pma .getMethod ( ) instanceof PathNormalizeMethod and
116- DataFlow:: localExprFlow ( pma , this .( MethodAccess ) .getQualifier ( ) )
117- )
109+ isExactStringPathMatch ( this ) or isStringPathMatch ( this ) or isFilePathMatch ( this )
118110 }
119111
120112 override predicate checks ( Expr e , boolean branch ) {
121113 e = this .( MethodAccess ) .getQualifier ( ) and
122114 (
123- branch = true and not checkNoPathMatch ( this )
115+ isExactStringPathMatch ( this ) and
116+ branch = true
124117 or
125- branch = false and checkNoPathMatch ( this )
126- )
127- }
128- }
129-
130- /**
131- * Holds if `ma` is a call to a method that checks string content, which means an input string is not
132- * blindly trusted and helps to reduce FPs.
133- */
134- predicate checkStringContent ( MethodAccess ma , Expr expr ) {
135- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
136- ma .getMethod ( )
137- .hasName ( [
138- "charAt" , "getBytes" , "getChars" , "length" , "replace" , "replaceAll" , "replaceFirst" ,
139- "substring"
140- ] ) and
141- expr = ma .getQualifier ( )
142- or
143- (
144- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeStringBuffer or
145- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeStringBuilder
146- ) and
147- expr = ma .getAnArgument ( )
148- }
149-
150- private class StringOperationSanitizer extends DataFlow:: Node {
151- StringOperationSanitizer ( ) { exists ( MethodAccess ma | checkStringContent ( ma , this .asExpr ( ) ) ) }
152- }
153-
154- private class NullOrEmptyCheckGuard extends DataFlow:: BarrierGuard {
155- NullOrEmptyCheckGuard ( ) {
156- this = nullGuard ( _, _, _)
157- or
158- exists ( MethodAccess ma |
159- cb .getCondition ( ) = ma and
160- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
161- ma .getMethod ( ) .hasName ( "equals" ) and
162- ma .getArgument ( 0 ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "" and
163- this = ma
118+ isAllowListCheck ( this ) and
119+ exists ( MethodAccess ma , Expr checked | isPathTraversalCheck ( ma , checked ) |
120+ DataFlow:: localExprFlow ( checked , e )
121+ or
122+ ma .getParent * ( ) .( BinaryExpr ) = this .( MethodAccess ) .getParent * ( )
123+ ) and
124+ branch = true
125+ or
126+ isDisallowListCheck ( this ) and
127+ exists ( MethodAccess ma , Expr checked | isURLEncodingCheck ( ma , checked ) |
128+ DataFlow:: localExprFlow ( checked , e )
129+ or
130+ ma .getParent * ( ) .( BinaryExpr ) = this .( MethodAccess ) .getParent * ( )
131+ ) and
132+ branch = false
164133 )
165134 }
166-
167- override predicate checks ( Expr e , boolean branch ) {
168- exists ( SsaVariable ssa | this = nullGuard ( ssa , branch , true ) and e = ssa .getAFirstUse ( ) )
169- or
170- e = this .( MethodAccess ) .getQualifier ( ) and
171- branch = true
172- }
173135}
174136
175137class UnsafeUrlForwardFlowConfig extends TaintTracking:: Configuration {
@@ -189,14 +151,10 @@ class UnsafeUrlForwardFlowConfig extends TaintTracking::Configuration {
189151
190152 override predicate isSink ( DataFlow:: Node sink ) { sink instanceof UnsafeUrlForwardSink }
191153
192- override predicate isSanitizer ( DataFlow:: Node node ) {
193- node instanceof UnsafeUrlForwardSanitizer or
194- node instanceof StringOperationSanitizer
195- }
154+ override predicate isSanitizer ( DataFlow:: Node node ) { node instanceof UnsafeUrlForwardSanitizer }
196155
197156 override predicate isSanitizerGuard ( DataFlow:: BarrierGuard guard ) {
198- guard instanceof PathMatchGuard or
199- guard instanceof NullOrEmptyCheckGuard
157+ guard instanceof PathMatchGuard
200158 }
201159
202160 override DataFlow:: FlowFeature getAFeature ( ) {
0 commit comments