@@ -2,25 +2,26 @@ package com.datadog.iast.sink
22
33import com.datadog.iast.IastModuleImplTestBase
44import com.datadog.iast.Reporter
5+ import com.datadog.iast.model.Range
6+ import com.datadog.iast.model.Source
57import com.datadog.iast.model.Vulnerability
68import com.datadog.iast.model.VulnerabilityType
7- import datadog.trace.api.iast.SourceTypes
89import datadog.trace.api.iast.VulnerabilityMarks
910import datadog.trace.api.iast.sink.HeaderInjectionModule
1011
11- import static com.datadog.iast.taint.TaintUtils.addFromRangeList
1212import static com.datadog.iast.taint.TaintUtils.addFromTaintFormat
1313import static com.datadog.iast.taint.TaintUtils.taintFormat
1414import static com.datadog.iast.util.HttpHeader.CONNECTION
1515import static com.datadog.iast.util.HttpHeader.LOCATION
1616import static com.datadog.iast.util.HttpHeader.SEC_WEBSOCKET_ACCEPT
1717import static com.datadog.iast.util.HttpHeader.SEC_WEBSOCKET_LOCATION
18- import static com.datadog.iast.util.HttpHeader.SET_COOKIE
1918import static com.datadog.iast.util.HttpHeader.UPGRADE
20- import static com.datadog.iast.util.HttpHeader.COOKIE
19+ import static datadog.trace.api.iast.SourceTypes.REQUEST_HEADER_NAME
20+ import static datadog.trace.api.iast.SourceTypes.REQUEST_HEADER_VALUE
21+ import static datadog.trace.api.iast.SourceTypes.REQUEST_PARAMETER_VALUE
2122import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED
2223
23- class HeaderInjectionModuleTest extends IastModuleImplTestBase {
24+ class HeaderInjectionModuleTest extends IastModuleImplTestBase {
2425
2526 private HeaderInjectionModule module
2627
@@ -48,11 +49,11 @@ class HeaderInjectionModuleTest extends IastModuleImplTestBase{
4849 }
4950
5051 where :
51- headerValue | mark | headerName | expected
52- ' /==>var<==' | NOT_MARKED | ' headerName' | " headerName: /==>var<=="
53- ' /==>var<==' | VulnerabilityMarks . XPATH_INJECTION_MARK | ' headerName' | " headerName: /==>var<=="
54- ' var' | NOT_MARKED | ' headerName' | null
55- ' /==>var<==' | VulnerabilityMarks . HEADER_INJECTION_MARK | ' headerName' | null
52+ headerValue | mark | headerName | expected
53+ ' /==>var<==' | NOT_MARKED | ' headerName' | " headerName: /==>var<=="
54+ ' /==>var<==' | VulnerabilityMarks . XPATH_INJECTION_MARK | ' headerName' | " headerName: /==>var<=="
55+ ' var' | NOT_MARKED | ' headerName' | null
56+ ' /==>var<==' | VulnerabilityMarks . HEADER_INJECTION_MARK | ' headerName' | null
5657 }
5758
5859 void ' check excluded headers' () {
@@ -66,96 +67,94 @@ class HeaderInjectionModuleTest extends IastModuleImplTestBase{
6667 header << [SEC_WEBSOCKET_LOCATION , SEC_WEBSOCKET_ACCEPT , UPGRADE , CONNECTION , LOCATION ]
6768 }
6869
69- void ' check set-cookie exclusion' (){
70+ void ' check exclusion for #header (excluded: #excluded) ' () {
7071 given :
7172 final headerValue = ' headerValue'
72- addFromRangeList( ctx. taintedObjects, ' headerValue' , ranges )
73+ ctx. taintedObjects. taint( headerValue, sources as Range [] )
7374
7475 when :
75- module. onHeader(SET_COOKIE . name , headerValue)
76+ module. onHeader(header , headerValue)
7677
7778 then :
78- expected * reporter. report(_, _ as Vulnerability )
79+ if (excluded) {
80+ 0 * reporter. _
81+ } else {
82+ 1 * reporter. report(_, _) >> { it -> assertVulnerability(it[1 ] as Vulnerability , VulnerabilityType . HEADER_INJECTION ) }
83+ }
7984
8085 where :
81- ranges | expected
82- [[0 , 2 , COOKIE , ' sourceValue' , SourceTypes . REQUEST_HEADER_VALUE ]] | 0
83- [
84- [0 , 2 , COOKIE , ' sourceValue' , SourceTypes . REQUEST_HEADER_VALUE ],
85- [3 , 4 , COOKIE , ' sourceValue2' , SourceTypes . REQUEST_HEADER_VALUE ]
86- ] | 0
87- [
88- [0 , 2 , COOKIE , ' sourceValue' , SourceTypes . REQUEST_HEADER_VALUE ],
89- [0 , 2 , ' sourceName' , ' sourceValue' , SourceTypes . REQUEST_HEADER_VALUE ]
90- ] | 1
91- [[0 , 2 , ' sourceName' , ' sourceValue' , SourceTypes . REQUEST_HEADER_VALUE ]] | 1
92- [[0 , 2 , ' sourceName' , ' sourceValue' , SourceTypes . GRPC_BODY ]] | 1
93- [[0 , 2 , SET_COOKIE , ' sourceValue' , SourceTypes . REQUEST_HEADER_VALUE ]] | 1
94- }
95-
96- void ' check reflected header exclusion' (){
97- given :
98- final headerName = ' headerName'
99- final headerValue = ' headerValue'
100- addFromRangeList(ctx. taintedObjects, ' headerValue' , ranges)
101-
102- when :
103- module. onHeader(headerName, headerValue)
86+ header | sources | excluded
87+ // exclude if sources coming only from headers
88+ ' Access-Control-Allow-Origin' | [requestParam()] | false
89+ ' Access-Control-Allow-Origin' | [requestParam(), contentType()] | false
90+ ' Access-Control-Allow-Origin' | [contentType()] | true
91+
92+ // exclude if sources coming only from headers
93+ ' Access-Control-Allow-Methods' | [requestParam()] | false
94+ ' Access-Control-Allow-Methods' | [requestParam(), contentType()] | false
95+ ' Access-Control-Allow-Methods' | [contentType()] | true
96+
97+ // exclude if sources coming only from 'Cookie' headers
98+ ' Set-Cookie' | [requestParam()] | false
99+ ' Set-Cookie' | [requestParam(), contentType()] | false
100+ ' Set-Cookie' | [cookie()] | true
101+
102+ // exclude if reflected from header with the same name
103+ ' Reflected' | contentType() | false
104+ ' Reflected' | [contentType(), requestHeader(' Reflected' , ' value' )] | false
105+ ' Reflected' | [requestHeader(' Reflected' , ' value' )] | true
106+
107+ // exclude if reflected from 'Cache-Control' header
108+ ' Pragma' | [contentType()] | false
109+ ' Pragma' | [contentType(), cacheControl()] | false
110+ ' Pragma' | [cacheControl()] | true
111+
112+ // exclude if reflected from 'Accept-Encoding' header
113+ ' Transfer-Encoding' | [contentType()] | false
114+ ' Transfer-Encoding' | [contentType(), acceptEncoding()] | false
115+ ' Transfer-Encoding' | [acceptEncoding()] | true
116+
117+ // exclude if reflected from 'Accept-Encoding' header
118+ ' Content-Encoding' | [contentType()] | false
119+ ' Content-Encoding' | [contentType(), acceptEncoding()] | false
120+ ' Content-Encoding' | [acceptEncoding()] | true
121+
122+ // exclude if sources coming only from request header names
123+ ' Vary' | [contentType()] | false
124+ ' Vary' | [contentType(), headerName(' Accept' )] | false
125+ ' Vary' | [headerName(' Content-Type' ), headerName(' Authorization' )] | true
126+ }
104127
105- then :
106- expected * reporter. report(_, _ as Vulnerability )
128+ private static Range headerName (String name ) {
129+ return source(REQUEST_HEADER_NAME , name, name)
130+ }
107131
108- where :
109- ranges | expected
110- [[0 , 2 , COOKIE , ' sourceValue' , SourceTypes . REQUEST_HEADER_VALUE ]] | 1
111- [
112- [0 , 2 , ' headerName' , ' sourceValue' , SourceTypes . REQUEST_HEADER_VALUE ],
113- [3 , 4 , ' headerName' , ' sourceValue2' , SourceTypes . REQUEST_HEADER_VALUE ]
114- ] | 1
115- [[0 , 2 , ' headerName' , ' sourceValue' , SourceTypes . REQUEST_HEADER_VALUE ]] | 0
132+ private static Range cookie (String name = ' p' , String value = ' value' ) {
133+ return source(REQUEST_HEADER_VALUE , ' Cookie' , " $name =$value " )
116134 }
117135
118- void ' check access-control-allow-* exclusion' (){
119- given :
120- final headerValue = ' headerValue'
121- addFromRangeList(ctx. taintedObjects, ' headerValue' , suite. ranges)
136+ private static Range requestParam (String name = ' p' , String value = ' value' ) {
137+ return source(REQUEST_PARAMETER_VALUE , name, value)
138+ }
122139
123- when :
124- module. onHeader(suite. header, headerValue)
140+ private static Range acceptEncoding (String value = ' gzip' ) {
141+ return requestHeader(' Accept-Encoding' , value)
142+ }
125143
126- then :
127- suite. expected * reporter. report(_, _ as Vulnerability )
144+ private static Range cacheControl (String value = ' no-cache' ) {
145+ return requestHeader(' Cache-Control' , value)
146+ }
128147
129- where :
130- suite << createTestSuite()
131- }
132-
133- private Iterable<TestSuite > createTestSuite () {
134- final result = []
135- final headerNames = [' Access-Control-Allow-Origin' , ' access-control-allow-origin' , ' ACCESS-CONTROL-ALLOW-METHODS' ]
136- for (headerName in headerNames){
137- result. add(createTestSuite(headerName, [[0 , 2 , ' sourceName' , ' sourceValue' , SourceTypes . REQUEST_HEADER_VALUE ]], 0 ))
138- result. add(createTestSuite(headerName, [
139- [0 , 2 , ' sourceName' , ' sourceValue' , SourceTypes . REQUEST_HEADER_VALUE ],
140- [3 , 4 , ' sourceName2' , ' sourceValue2' , SourceTypes . REQUEST_HEADER_VALUE ]
141- ], 0 ))
142- result. add(createTestSuite(headerName, [[0 , 2 , ' sourceName' , ' sourceValue' , SourceTypes . GRPC_BODY ]], 1 ))
143- result. add(createTestSuite(headerName, [
144- [0 , 2 , ' sourceName' , ' sourceValue' , SourceTypes . GRPC_BODY ],
145- [3 , 4 , ' sourceName2' , ' sourceValue2' , SourceTypes . REQUEST_HEADER_VALUE ]
146- ], 1 ))
147- }
148- return result as Iterable<TestSuite >
148+ private static Range contentType (String value = ' text/html' ) {
149+ return requestHeader(' Content-Type' , value)
149150 }
150151
151- private createTestSuite ( header , ranges , expected ) {
152- return new TestSuite ( ' header ' : header as String , ' ranges ' : ranges as List< List< Object > > , ' expected ' : expected as Integer )
152+ private static Range requestHeader ( String name , String value = ' text/html ' ) {
153+ return source( REQUEST_HEADER_VALUE , name, value )
153154 }
154155
155- private static class TestSuite {
156- String header
157- List<List<Object > > ranges
158- Integer expected
156+ private static Range source (byte origin , String name , String value ) {
157+ return new Range (0 , value. length(), new Source (origin, name, value), NOT_MARKED )
159158 }
160159
161160 private String mapTainted (final String value , final int mark ) {
@@ -164,7 +163,7 @@ class HeaderInjectionModuleTest extends IastModuleImplTestBase{
164163 return result
165164 }
166165
167- private static void assertVulnerability (final Vulnerability vuln , final VulnerabilityType type ) {
166+ private static void assertVulnerability (final Vulnerability vuln , final VulnerabilityType type ) {
168167 assert vuln != null
169168 assert vuln. getType() == type
170169 assert vuln. getLocation() != null
0 commit comments