Skip to content

Commit 9f3d04d

Browse files
authored
Merge branch 'master' into andrea.marziali/jetty-http-client
2 parents 4bf72e4 + 1377296 commit 9f3d04d

File tree

27 files changed

+987
-47
lines changed

27 files changed

+987
-47
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.datadog.iast.sensitive;
2+
3+
import com.datadog.iast.model.Evidence;
4+
import com.datadog.iast.util.Ranged;
5+
import java.util.NoSuchElementException;
6+
import java.util.regex.Pattern;
7+
import javax.annotation.Nullable;
8+
9+
public class HeaderRegexpTokenizer implements SensitiveHandler.Tokenizer {
10+
11+
@Nullable private Ranged current;
12+
13+
private boolean checked = false;
14+
15+
private String evidenceValue;
16+
17+
private Pattern namePattern;
18+
19+
private Pattern valuePattern;
20+
21+
public HeaderRegexpTokenizer(final Evidence evidence, Pattern namePattern, Pattern valuePattern) {
22+
this.evidenceValue = evidence.getValue();
23+
this.namePattern = namePattern;
24+
this.valuePattern = valuePattern;
25+
}
26+
27+
@Override
28+
public boolean next() {
29+
current = buildNext();
30+
return current != null;
31+
}
32+
33+
@Override
34+
public Ranged current() {
35+
if (current == null) {
36+
throw new NoSuchElementException();
37+
}
38+
return current;
39+
}
40+
41+
@Nullable
42+
private Ranged buildNext() {
43+
if (!checked) {
44+
checked = true;
45+
// Header evidence format is <headerName>: <headerValue>
46+
int separatorIndex = evidenceValue.indexOf(":");
47+
if (separatorIndex < 1) {
48+
return null; // Wrong evidence format: there is no separator or <headerName>
49+
}
50+
int headerValueIndex = separatorIndex + 2; // there is a white space after :
51+
if (evidenceValue.length() <= headerValueIndex) {
52+
return null; // Wrong evidence format: there is no <headerValue>
53+
}
54+
String name = evidenceValue.substring(0, separatorIndex);
55+
String value = evidenceValue.substring(headerValueIndex);
56+
if (namePattern.matcher(name).find() || valuePattern.matcher(value).find()) {
57+
return Ranged.build(name.length() + 2, value.length());
58+
}
59+
}
60+
return null;
61+
}
62+
}

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/SensitiveHandlerImpl.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ public SensitiveHandlerImpl() {
4646
tokenizers.put(VulnerabilityType.SSRF, UrlRegexpTokenizer::new);
4747
tokenizers.put(VulnerabilityType.UNVALIDATED_REDIRECT, UrlRegexpTokenizer::new);
4848
tokenizers.put(VulnerabilityType.XSS, TaintedRangeBasedTokenizer::new);
49-
tokenizers.put(VulnerabilityType.HEADER_INJECTION, TaintedRangeBasedTokenizer::new);
49+
tokenizers.put(
50+
VulnerabilityType.HEADER_INJECTION,
51+
evidence -> new HeaderRegexpTokenizer(evidence, namePattern, valuePattern));
5052
}
5153

5254
@Override

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/CookieSecurityParser.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,16 @@ public static List<Cookie> parse(final HttpHeader headerName, String headerValue
6161
}
6262
boolean addCookie = false;
6363
boolean eof = i == headerValue.length() - 1;
64-
if (eof || next == ',' || next == '=' || next == ';') {
64+
boolean separator = next == ',' || next == '=' || next == ';';
65+
if (eof || separator) {
6566
if (next == ',') {
6667
if (quoteCount % 2 == 0 && version == 1) {
6768
addCookie = true; // multiple cookie separator
6869
} else {
6970
continue;
7071
}
7172
}
72-
final int end = eof ? i + 1 : i;
73+
final int end = separator ? i : i + 1;
7374
switch (state) {
7475
case COOKIE_NAME:
7576
cookieName = headerValue.substring(start, end).trim();

dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/util/CookieSecurityParserTest.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class CookieSecurityParserTest extends Specification {
3030

3131
where:
3232
header | cookieName | isSecure | isHttpOnly | sameSite
33+
"Set-Cookie: user-id=" | "user-id" | false | false | null
3334
"Set-Cookie: user-id=7" | "user-id" | false | false | null
3435
'Set-Cookie: user-id="7"' | "user-id" | false | false | null
3536
"Set-Cookie: user-id=7;Secure" | "user-id" | true | false | null

dd-java-agent/agent-iast/src/test/resources/redaction/evidence-redaction-suite.yml

Lines changed: 208 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,43 +2023,222 @@ suite:
20232023
]
20242024
}
20252025
- type: 'VULNERABILITIES'
2026-
description: 'Testing header injection redaction'
2026+
description: 'Header injection without sensitive data'
20272027
input: >
20282028
[
20292029
{
20302030
"type": "HEADER_INJECTION",
20312031
"evidence": {
2032-
"value": "Authorization: bearer 12345644",
2032+
"value": "custom: text",
20332033
"ranges": [
2034-
{ "start" : 0, "length" : 30, "source": { "origin": "http.request.header", "name": "X-Test-Header", "value": "Authorization: bearer 12345644" }}
2034+
{ "start" : 8, "length" : 4, "source": { "origin": "http.request.parameter", "name": "param", "value": "text" } }
20352035
]
20362036
}
20372037
}
20382038
]
20392039
expected: >
2040-
{
2041-
"vulnerabilities": [
2042-
{
2043-
"type": "HEADER_INJECTION",
2044-
"evidence": {
2045-
"valueParts": [
2046-
{
2047-
"source": 0,
2048-
"redacted": true,
2049-
"pattern": "abcdefghijklmnopqrstuvwxyzABCD"
2050-
}
2051-
]
2052-
},
2053-
"hash": 0,
2054-
"type": "HEADER_INJECTION"
2055-
}
2056-
],
2057-
"sources": [
2058-
{
2059-
"origin": "http.request.header",
2060-
"name": "X-Test-Header",
2061-
"redacted": true,
2062-
"pattern": "abcdefghijklmnopqrstuvwxyzABCD"
2063-
}
2064-
]
2065-
}
2040+
{
2041+
"sources": [
2042+
{ "origin": "http.request.parameter", "name": "param", "value": "text" }
2043+
],
2044+
"vulnerabilities": [
2045+
{
2046+
"type": "HEADER_INJECTION",
2047+
"evidence": {
2048+
"valueParts": [
2049+
{ "value": "custom: " },
2050+
{ "source": 0, "value": "text" }
2051+
]
2052+
}
2053+
}
2054+
]
2055+
}
2056+
- type: 'VULNERABILITIES'
2057+
description: 'Header injection with only sensitive data from tainted'
2058+
input: >
2059+
[
2060+
{
2061+
"type": "HEADER_INJECTION",
2062+
"evidence": {
2063+
"value": "custom: pass",
2064+
"ranges": [
2065+
{ "start" : 8, "length" : 4, "source": { "origin": "http.request.parameter", "name": "password", "value": "pass" } }
2066+
]
2067+
}
2068+
}
2069+
]
2070+
expected: >
2071+
{
2072+
"sources": [
2073+
{ "origin": "http.request.parameter", "name": "password", "redacted": true, "pattern": "abcd" }
2074+
],
2075+
"vulnerabilities": [
2076+
{
2077+
"type": "HEADER_INJECTION",
2078+
"evidence": {
2079+
"valueParts": [
2080+
{ "value": "custom: " },
2081+
{ "source": 0, "redacted": true, "pattern": "abcd" }
2082+
]
2083+
}
2084+
}
2085+
]
2086+
}
2087+
- type: 'VULNERABILITIES'
2088+
description: 'Header injection with partial sensitive data from tainted'
2089+
input: >
2090+
[
2091+
{
2092+
"type": "HEADER_INJECTION",
2093+
"evidence": {
2094+
"value": "custom: this is pass",
2095+
"ranges": [
2096+
{ "start" : 16, "length" : 4, "source": { "origin": "http.request.parameter", "name": "password", "value": "pass" } }
2097+
]
2098+
}
2099+
}
2100+
]
2101+
expected: >
2102+
{
2103+
"sources": [
2104+
{ "origin": "http.request.parameter", "name": "password", "redacted": true, "pattern": "abcd" }
2105+
],
2106+
"vulnerabilities": [
2107+
{
2108+
"type": "HEADER_INJECTION",
2109+
"evidence": {
2110+
"valueParts": [
2111+
{ "value": "custom: this is " },
2112+
{ "source": 0, "redacted": true, "pattern": "abcd" }
2113+
]
2114+
}
2115+
}
2116+
]
2117+
}
2118+
- type: 'VULNERABILITIES'
2119+
description: 'Header injection with sensitive data from header name'
2120+
input: >
2121+
[
2122+
{
2123+
"type": "HEADER_INJECTION",
2124+
"evidence": {
2125+
"value": "password: text",
2126+
"ranges": [
2127+
{ "start" : 10, "length" : 4, "source": { "origin": "http.request.parameter", "name": "param", "value": "text" } }
2128+
]
2129+
}
2130+
}
2131+
]
2132+
expected: >
2133+
{
2134+
"sources": [
2135+
{ "origin": "http.request.parameter", "name": "param", "redacted": true, "pattern": "abcd" }
2136+
],
2137+
"vulnerabilities": [
2138+
{
2139+
"type": "HEADER_INJECTION",
2140+
"evidence": {
2141+
"valueParts": [
2142+
{ "value": "password: " },
2143+
{ "source": 0, "redacted": true, "pattern": "abcd" }
2144+
]
2145+
}
2146+
}
2147+
]
2148+
}
2149+
- type: 'VULNERABILITIES'
2150+
description: 'Header injection with sensitive data from header value'
2151+
input: >
2152+
[
2153+
{
2154+
"type": "HEADER_INJECTION",
2155+
"evidence": {
2156+
"value": "custom: bearer 1234123",
2157+
"ranges": [
2158+
{ "start" : 15, "length" : 7, "source": { "origin": "http.request.parameter", "name": "param", "value": "1234123" } }
2159+
]
2160+
}
2161+
}
2162+
]
2163+
expected: >
2164+
{
2165+
"sources": [
2166+
{ "origin": "http.request.parameter", "name": "param", "redacted": true, "pattern": "abcdefg" }
2167+
],
2168+
"vulnerabilities": [
2169+
{
2170+
"type": "HEADER_INJECTION",
2171+
"evidence": {
2172+
"valueParts": [
2173+
{ "value": "custom: " },
2174+
{ "redacted": true },
2175+
{ "source": 0, "redacted": true, "pattern": "abcdefg" }
2176+
]
2177+
}
2178+
}
2179+
]
2180+
}
2181+
- type: 'VULNERABILITIES'
2182+
description: 'Header injection with sensitive data from header and tainted'
2183+
input: >
2184+
[
2185+
{
2186+
"type": "HEADER_INJECTION",
2187+
"evidence": {
2188+
"value": "password: this is pass",
2189+
"ranges": [
2190+
{ "start" : 18, "length" : 4, "source": { "origin": "http.request.parameter", "name": "password", "value": "pass" } }
2191+
]
2192+
}
2193+
}
2194+
]
2195+
expected: >
2196+
{
2197+
"sources": [
2198+
{ "origin": "http.request.parameter", "name": "password", "redacted": true, "pattern": "abcd" }
2199+
],
2200+
"vulnerabilities": [
2201+
{
2202+
"type": "HEADER_INJECTION",
2203+
"evidence": {
2204+
"valueParts": [
2205+
{ "value": "password: " },
2206+
{ "redacted": true },
2207+
{ "source": 0, "redacted": true, "pattern": "abcd" }
2208+
]
2209+
}
2210+
}
2211+
]
2212+
}
2213+
- type: 'VULNERABILITIES'
2214+
description: 'Header injection with sensitive data from header and tainted (source does not match)'
2215+
input: >
2216+
[
2217+
{
2218+
"type": "HEADER_INJECTION",
2219+
"evidence": {
2220+
"value": "password: this is key word",
2221+
"ranges": [
2222+
{ "start" : 18, "length" : 8, "source": { "origin": "http.request.parameter", "name": "password", "value": "key%20word" } }
2223+
]
2224+
}
2225+
}
2226+
]
2227+
expected: >
2228+
{
2229+
"sources": [
2230+
{ "origin": "http.request.parameter", "name": "password", "redacted": true, "pattern": "abcdefghij" }
2231+
],
2232+
"vulnerabilities": [
2233+
{
2234+
"type": "HEADER_INJECTION",
2235+
"evidence": {
2236+
"valueParts": [
2237+
{ "value": "password: " },
2238+
{ "redacted": true },
2239+
{ "source": 0, "redacted": true, "pattern": "********" }
2240+
]
2241+
}
2242+
}
2243+
]
2244+
}

dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@
193193
0 org.apache.http.client.methods.*
194194
# apache compiled jsps
195195
0 org.apache.jsp.*
196+
# Need for kafka propagation
197+
2 org.apache.kafka.common.serialization.ByteBufferDeserializer
198+
2 org.apache.kafka.common.serialization.StringDeserializer
196199
1 org.apiguardian.*
197200
1 org.aspectj.*
198201
1 org.attoparser.*

dd-java-agent/instrumentation/jackson-core/src/main/java/datadog/trace/instrumentation/jackson/core/Json2FactoryInstrumentation.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public void methodAdvice(MethodTransformer transformer) {
3434
takesArguments(String.class)
3535
.or(takesArguments(InputStream.class))
3636
.or(takesArguments(Reader.class))
37-
.or(takesArguments(URL.class)))),
37+
.or(takesArguments(URL.class))
38+
.or(takesArguments(byte[].class)))),
3839
Json2FactoryInstrumentation.class.getName() + "$InstrumenterAdvice");
3940
}
4041

dd-java-agent/instrumentation/jackson-core/src/test/groovy/Json2FactoryInstrumentationTest.groovy

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,22 @@ class Json2FactoryInstrumentationTest extends AgentTestRunner {
8080
0 * _
8181
}
8282
83+
void 'test createParser(byte[])'() {
84+
setup:
85+
final propagationModule = Mock(PropagationModule)
86+
InstrumentationBridge.registerIastModule(propagationModule)
87+
final bytes = '{}'.bytes
88+
89+
when:
90+
final result = new JsonFactory().createParser(bytes)
91+
92+
93+
then:
94+
result != null
95+
1 * propagationModule.taintIfTainted(_ as JsonParser, bytes)
96+
0 * _
97+
}
98+
8399
void 'test createParser(URL)'() {
84100
setup:
85101
final propagationModule = Mock(PropagationModule)

0 commit comments

Comments
 (0)