11/**
2- * @name Cors header being set from remote source
3- * @description Cors header is being set from remote source , allowing to control the origin .
2+ * @name CORS is derived from untrusted input
3+ * @description CORS header is derived from untrusted input , allowing a remote user to control which origins are trusted .
44 * @kind path-problem
55 * @problem.severity error
66 * @precision high
@@ -13,6 +13,7 @@ import java
1313import semmle.code.java.dataflow.FlowSources
1414import semmle.code.java.frameworks.Servlets
1515import semmle.code.java.dataflow.TaintTracking
16+ import semmle.code.java.dataflow.TaintTracking2
1617import DataFlow:: PathGraph
1718
1819/**
@@ -25,10 +26,10 @@ private predicate setsAllowCredentials(MethodAccess header) {
2526 ) and
2627 header .getArgument ( 0 ) .( CompileTimeConstantExpr ) .getStringValue ( ) .toLowerCase ( ) =
2728 "access-control-allow-credentials" and
28- header .getArgument ( 1 ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "true"
29+ header .getArgument ( 1 ) .( CompileTimeConstantExpr ) .getStringValue ( ) . toLowerCase ( ) = "true"
2930}
3031
31- class CorsProbableCheckAccess extends MethodAccess {
32+ private class CorsProbableCheckAccess extends MethodAccess {
3233 CorsProbableCheckAccess ( ) {
3334 getMethod ( ) .hasName ( "contains" ) and
3435 getMethod ( ) .getDeclaringType ( ) .getASourceSupertype * ( ) instanceof CollectionType
@@ -45,46 +46,41 @@ private Expr getAccessControlAllowOriginHeaderName() {
4546 result .( CompileTimeConstantExpr ) .getStringValue ( ) .toLowerCase ( ) = "access-control-allow-origin"
4647}
4748
49+ /**
50+ * This taintflow2 configuration checks if there is a flow from source node towards CorsProbableCheckAccess methods.
51+ */
52+ class CorsSourceReachesCheckConfig extends TaintTracking2:: Configuration {
53+ CorsSourceReachesCheckConfig ( ) { this = "CorsOriginConfig" }
54+
55+ override predicate isSource ( DataFlow:: Node source ) { any ( CorsOriginConfig c ) .hasFlow ( source , _) }
56+
57+ override predicate isSink ( DataFlow:: Node sink ) {
58+ sink .asExpr ( ) = any ( CorsProbableCheckAccess check ) .getAnArgument ( )
59+ }
60+ }
61+
4862class CorsOriginConfig extends TaintTracking:: Configuration {
4963 CorsOriginConfig ( ) { this = "CorsOriginConfig" }
5064
5165 override predicate isSource ( DataFlow:: Node source ) { source instanceof RemoteFlowSource }
5266
5367 override predicate isSink ( DataFlow:: Node sink ) {
54- exists ( MethodAccess corsheader , MethodAccess allowcredentialsheader |
68+ exists ( MethodAccess corsHeader , MethodAccess allowCredentialsHeader |
5569 (
56- corsheader .getMethod ( ) instanceof ResponseSetHeaderMethod or
57- corsheader .getMethod ( ) instanceof ResponseAddHeaderMethod
70+ corsHeader .getMethod ( ) instanceof ResponseSetHeaderMethod or
71+ corsHeader .getMethod ( ) instanceof ResponseAddHeaderMethod
5872 ) and
59- getAccessControlAllowOriginHeaderName ( ) = corsheader .getArgument ( 0 ) and
60- setsAllowCredentials ( allowcredentialsheader ) and
61- corsheader .getEnclosingCallable ( ) = allowcredentialsheader .getEnclosingCallable ( ) and
62- sink .asExpr ( ) = corsheader .getArgument ( 1 )
63- )
64- }
65-
66- /**
67- * this sanitizer is oversimplistic:
68- * - it only considers local dataflows
69- * - it will consider any method calling `Collection.contains` or `String.equals` as a sanitizer
70- * no matter if that check is taken into account and its result reaches the
71- * return statement of the wrapper.
72- */
73- override predicate isSanitizer ( DataFlow:: Node node ) {
74- exists ( CorsProbableCheckAccess check |
75- TaintTracking:: localTaint ( node , DataFlow:: exprNode ( check .getAnArgument ( ) ) )
76- or
77- exists ( MethodAccess wrapperAccess , Method wrapper , int i |
78- TaintTracking:: localTaint ( node , DataFlow:: exprNode ( wrapperAccess .getArgument ( i ) ) ) and
79- wrapperAccess .getMethod ( ) = wrapper and
80- TaintTracking:: localTaint ( DataFlow:: parameterNode ( wrapper .getParameter ( i ) ) ,
81- DataFlow:: exprNode ( check .getAnArgument ( ) ) )
82- )
73+ getAccessControlAllowOriginHeaderName ( ) = corsHeader .getArgument ( 0 ) and
74+ setsAllowCredentials ( allowCredentialsHeader ) and
75+ corsHeader .getEnclosingCallable ( ) = allowCredentialsHeader .getEnclosingCallable ( ) and
76+ sink .asExpr ( ) = corsHeader .getArgument ( 1 )
8377 )
8478 }
8579}
8680
87- from DataFlow:: PathNode source , DataFlow:: PathNode sink , CorsOriginConfig conf
88- where conf .hasFlowPath ( source , sink )
89- select sink .getNode ( ) , source , sink , "Cors header is being set using user controlled value $@." ,
81+ from
82+ DataFlow:: PathNode source , DataFlow:: PathNode sink , CorsOriginConfig conf ,
83+ CorsSourceReachesCheckConfig sanconf
84+ where conf .hasFlowPath ( source , sink ) and not sanconf .hasFlow ( source .getNode ( ) , _)
85+ select sink .getNode ( ) , source , sink , "CORS header is being set using user controlled value $@." ,
9086 source .getNode ( ) , "user-provided value"
0 commit comments