Skip to content

Commit

Permalink
Merge pull request #3682 from DataDog/okhttpVuln
Browse files Browse the repository at this point in the history
Added exception catches for the OkHTTPClient header vulnerability
  • Loading branch information
nayeem-kamal authored Aug 2, 2022
2 parents 91e567f + fd7c3d0 commit a7564a4
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,8 @@ private static OkHttpClient buildHttpClient(
public Request authenticate(final Route route, final Response response) {
final String credential =
Credentials.basic(proxyUsername, proxyPassword == null ? "" : proxyPassword);
return response
.request()
.newBuilder()

return new SafeRequestBuilder(response.request().newBuilder())
.header("Proxy-Authorization", credential)
.build();
}
Expand All @@ -175,8 +174,9 @@ public Request authenticate(final Route route, final Response response) {
}

public static Request.Builder prepareRequest(final HttpUrl url, Map<String, String> headers) {
final Request.Builder builder =
new Request.Builder()

final SafeRequestBuilder builder =
new SafeRequestBuilder()
.url(url)
.addHeader(DATADOG_META_LANG, "java")
.addHeader(DATADOG_META_LANG_VERSION, JAVA_VERSION)
Expand All @@ -192,7 +192,7 @@ public static Request.Builder prepareRequest(final HttpUrl url, Map<String, Stri
builder.addHeader(e.getKey(), e.getValue());
}

return builder;
return builder.getBuilder();
}

public static Request.Builder prepareRequest(
Expand All @@ -206,7 +206,7 @@ public static Request.Builder prepareRequest(
if (agentless && apiKey != null) {
// we only add the api key header if we know we're doing agentless. No point in adding it to
// other agent-based requests since we know the datadog-agent isn't going to make use of it.
builder.addHeader(DD_API_KEY, apiKey);
builder = SafeRequestBuilder.addHeader(builder, DD_API_KEY, apiKey);
}

return builder;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package datadog.communication.http;

import java.net.URL;
import javax.annotation.Nullable;
import okhttp3.CacheControl;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.RequestBody;

/*
The purpose of this class is to wrap okhttp3.Request.Builder.
This adapter is used to resolve a vulnerability in Request.builder where
the .header() and .addHeader() methods can cause header secrets to be
printed/logged when invalid characters are parsed.
*/
public final class SafeRequestBuilder {

private final Request.Builder requestBuilder;

public SafeRequestBuilder() {
this.requestBuilder = new Request.Builder();
}

// Constructs a SafeRequestBuilder from an existing request
public SafeRequestBuilder(Request.Builder request) {
this.requestBuilder = request;
}

public SafeRequestBuilder url(HttpUrl url) {
this.requestBuilder.url(url);
return this;
}

public SafeRequestBuilder url(String url) {
this.requestBuilder.url(url);
return this;
}

public SafeRequestBuilder url(URL url) {
this.requestBuilder.url(url);
return this;
}

public SafeRequestBuilder header(String name, String value) {
// This try/catch block prevents header secrets from being outputted to
// console or logs.
try {
this.requestBuilder.header(name, value);
return this;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
new StringBuilder()
.append("InvalidArgumentException at header() for header: ")
.append(name)
.toString());
}
}

public SafeRequestBuilder addHeader(String name, String value) {
// This try/catch block prevents header secrets from being outputted to
// console or logs.
try {
this.requestBuilder.addHeader(name, value);
return this;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
new StringBuilder()
.append("InvalidArgumentException at addHeader() for header: ")
.append(name)
.toString());
}
}

public static Request.Builder addHeader(Request.Builder builder, String name, String value) {
try {
return builder.addHeader(name, value);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
new StringBuilder()
.append("InvalidArgumentException at addHeader() for header: ")
.append(name)
.toString());
}
}

public SafeRequestBuilder removeHeader(String name) {
this.requestBuilder.removeHeader(name);
return this;
}

public SafeRequestBuilder headers(Headers headers) {
this.requestBuilder.headers(headers);
return this;
}

public SafeRequestBuilder cacheControl(CacheControl cacheControl) {
this.requestBuilder.cacheControl(cacheControl);
return this;
}

public SafeRequestBuilder get() {
this.requestBuilder.get();
return this;
}

public SafeRequestBuilder head() {
this.requestBuilder.head();
return this;
}

public SafeRequestBuilder post(RequestBody body) {
this.requestBuilder.post(body);
return this;
}

public SafeRequestBuilder delete(@Nullable RequestBody body) {
this.requestBuilder.delete(body);
return this;
}

public SafeRequestBuilder delete() {
this.requestBuilder.delete();
return this;
}

public SafeRequestBuilder put(RequestBody body) {
this.requestBuilder.put(body);
return this;
}

public SafeRequestBuilder patch(RequestBody body) {
this.requestBuilder.patch(body);
return this;
}

public SafeRequestBuilder method(String method, @Nullable RequestBody body) {
this.requestBuilder.method(method, body);
return this;
}

public SafeRequestBuilder tag(@Nullable Object tag) {
this.requestBuilder.tag(tag);
return this;
}

public <T> SafeRequestBuilder tag(Class<? super T> type, @Nullable T tag) {
this.requestBuilder.tag(type, tag);
return this;
}

public Request build() {
return this.requestBuilder.build();
}

public Request.Builder getBuilder() {
return this.requestBuilder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package datadog.communication.http


import okhttp3.Headers
import okhttp3.MediaType
import okhttp3.Request
import okhttp3.RequestBody
import org.junit.Test
import org.junit.Assert

class SafeRequestBuilderTest {
SafeRequestBuilder testBuilder = new SafeRequestBuilder()

@Test
void "test add header"(){
testBuilder.url("http:localhost").addHeader("test","test")
Assert.assertEquals(testBuilder.build().headers().get("test"),"test")
}
@Test
void "test remove header"(){
testBuilder.url("http:localhost").removeHeader("test")
Assert.assertEquals(testBuilder.build().headers().get("test"),null)
}
@Test
void "test static add header"(){
Request.Builder builder = new Request.Builder().url("http://localhost")
builder = SafeRequestBuilder.addHeader(builder,"test","test")
Assert.assertEquals(builder.build().headers().get("test"),"test")
}
@Test (expected = IllegalArgumentException)
void "test bad static add header"(){
Request.Builder builder = new Request.Builder().url("http://localhost")
builder = SafeRequestBuilder.addHeader(builder,"\n\n","\n\n")
}
@Test(expected = IllegalArgumentException)
void "test adding bad header"(){
testBuilder.url("http:localhost").addHeader("\n\n","\n\n")
}
@Test (expected = IllegalArgumentException)
void "test adding bad header2"(){
testBuilder.url("localhost").header("\u0019","\u0080")
}
@Test
void "test building result"(){
Request testRequest = new SafeRequestBuilder().url("http://localhost")
.header("key","value").build()
Assert.assertEquals(Request,testRequest.getClass())
}
@Test
void "test getBuilder"(){
Request.Builder originalBuilder = new Request.Builder().url("http://localhost")
.addHeader("test","test")
testBuilder = new SafeRequestBuilder(originalBuilder)
Assert.assertEquals(originalBuilder,testBuilder.getBuilder())
}
@Test
void "test get method"(){
testBuilder = new SafeRequestBuilder().url("http://localhost")
testBuilder.get()
testBuilder.headers(new Headers())
Assert.assertEquals(testBuilder.build().method(),"GET")
}
@Test
void "test post method"(){
testBuilder = new SafeRequestBuilder().url("http://localhost")
RequestBody body = RequestBody.create(MediaType.parse("application/json"), "{}")
testBuilder.post(body)
Assert.assertEquals(testBuilder.build().method(),"POST")
}
@Test
void "test put method"(){
testBuilder = new SafeRequestBuilder().url("http://localhost")
RequestBody body = RequestBody.create(MediaType.parse("application/json"), "{}")
testBuilder.put(body)
Assert.assertEquals(testBuilder.build().method(),"PUT")
}
@Test
void "test patch method"(){
testBuilder = new SafeRequestBuilder().url("http://localhost")
RequestBody body = RequestBody.create(MediaType.parse("application/json"), "{}")
testBuilder.patch(body)
Assert.assertEquals(testBuilder.build().method(),"PATCH")
}
@Test
void "test delete method"(){
testBuilder = new SafeRequestBuilder().url("http://localhost")
testBuilder.delete()
Assert.assertEquals(testBuilder.build().method(),"DELETE")
}
@Test
void "test method adder"(){
testBuilder = new SafeRequestBuilder().url("http://localhost")
testBuilder.method("GET",null)
}
@Test
void "test tag"(){
testBuilder = new SafeRequestBuilder()
URL url = new URL("http://localhost")
testBuilder.url(url).tag("test")
Assert.assertEquals(testBuilder.build().tag().toString(),"test")
}
@Test
void "test head"(){
testBuilder = new SafeRequestBuilder().url("http://localhost")
testBuilder.head()
Assert.assertEquals(testBuilder.build().method(),"HEAD")
}
@Test
void "test delete with parameter method"(){
testBuilder = new SafeRequestBuilder().url("http://localhost")
RequestBody body = RequestBody.create(MediaType.parse("application/json"), "{}")
testBuilder.delete(body)
Assert.assertEquals(testBuilder.build().method(),"DELETE")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.datadog.debugger.util.DebuggerMetrics;
import datadog.common.container.ContainerInfo;
import datadog.communication.http.SafeRequestBuilder;
import datadog.trace.api.Config;
import datadog.trace.relocate.api.RatelimitedLogger;
import java.io.IOException;
Expand Down Expand Up @@ -151,7 +152,7 @@ private void makeUploadRequest(byte[] json, String tags) throws IOException {
if (!tags.isEmpty()) {
builder.addQueryParameter("ddtags", tags);
}
Request.Builder requestBuilder = new Request.Builder().url(builder.build()).post(body);
SafeRequestBuilder requestBuilder = new SafeRequestBuilder().url(builder.build()).post(body);
if (apiKey != null) {
if (apiKey.isEmpty()) {
log.debug("API key is empty");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import datadog.communication.http.SafeRequestBuilder;
import datadog.trace.api.DDId;
import datadog.trace.api.sampling.PrioritySampling;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.core.propagation.ExtractedContext;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.slf4j.Logger;
Expand Down Expand Up @@ -65,7 +65,7 @@ public static AgentSpan.Context notifyStartInvocation(Object event) {
try (Response response =
HTTP_CLIENT
.newCall(
new Request.Builder()
new SafeRequestBuilder()
.url(EXTENSION_BASE_URL + START_INVOCATION)
.addHeader(DATADOG_META_LANG, "java")
.post(body)
Expand Down Expand Up @@ -99,14 +99,15 @@ public static AgentSpan.Context notifyStartInvocation(Object event) {
}

public static boolean notifyEndInvocation(AgentSpan span, boolean isError) {

if (null == span || null == span.getSamplingPriority()) {
log.error(
"could not notify the extension as the lambda span is null or no sampling priority has been found");
return false;
}
RequestBody body = RequestBody.create(jsonMediaType, "{}");
Request.Builder builder =
new Request.Builder()
SafeRequestBuilder builder =
new SafeRequestBuilder()
.url(EXTENSION_BASE_URL + END_INVOCATION)
.addHeader(DATADOG_META_LANG, "java")
.addHeader(DATADOG_TRACE_ID, span.getTraceId().toString())
Expand All @@ -117,6 +118,7 @@ public static boolean notifyEndInvocation(AgentSpan span, boolean isError) {
if (isError) {
builder.addHeader(DATADOG_INVOCATION_ERROR, "true");
}

try (Response response = HTTP_CLIENT.newCall(builder.build()).execute()) {
if (response.isSuccessful()) {
log.debug("notifyEndInvocation success");
Expand Down
Loading

0 comments on commit a7564a4

Please sign in to comment.