diff --git a/instrumentation-security/jersey-2.16/build.gradle b/instrumentation-security/jersey-2.16/build.gradle index d10b64445..56b302d44 100644 --- a/instrumentation-security/jersey-2.16/build.gradle +++ b/instrumentation-security/jersey-2.16/build.gradle @@ -4,6 +4,9 @@ dependencies { implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") implementation("org.glassfish.jersey.core:jersey-server:2.16") + testImplementation("org.glassfish.jersey.containers:jersey-container-grizzly2-http:2.28") + testImplementation("org.glassfish.jersey.containers:jersey-container-servlet:2.28") + testImplementation('org.glassfish.jersey.inject:jersey-hk2:2.28') } jar { diff --git a/instrumentation-security/jersey-2/build.gradle b/instrumentation-security/jersey-2/build.gradle index 6b8971009..7c18e6d6b 100644 --- a/instrumentation-security/jersey-2/build.gradle +++ b/instrumentation-security/jersey-2/build.gradle @@ -4,6 +4,9 @@ dependencies { implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") implementation("org.glassfish.jersey.core:jersey-server:2.0") + testImplementation("org.glassfish.jersey.containers:jersey-container-grizzly2-http:2.0") + testImplementation("org.glassfish.jersey.containers:jersey-container-servlet:2.0") + testImplementation('org.glassfish.hk2:hk2-api:2.1.88') } jar { diff --git a/instrumentation-security/jersey-3/build.gradle b/instrumentation-security/jersey-3/build.gradle index 4f65c91ea..38f1f1aaa 100644 --- a/instrumentation-security/jersey-3/build.gradle +++ b/instrumentation-security/jersey-3/build.gradle @@ -4,6 +4,9 @@ dependencies { implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") implementation("org.glassfish.jersey.core:jersey-server:3.0.0") + testImplementation("org.glassfish.jersey.containers:jersey-container-grizzly2-http:3.0.0") + testImplementation("org.glassfish.jersey.containers:jersey-container-servlet:3.0.0") + testImplementation('org.glassfish.jersey.inject:jersey-hk2:3.0.0') } jar { diff --git a/instrumentation-security/ning-async-http-client-1.1.0/build.gradle b/instrumentation-security/ning-async-http-client-1.1.0/build.gradle new file mode 100644 index 000000000..86197f362 --- /dev/null +++ b/instrumentation-security/ning-async-http-client-1.1.0/build.gradle @@ -0,0 +1,19 @@ +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") + implementation("com.ning:async-http-client:1.1.0") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.ning-async-http-client-1.1.0' } +} + +verifyInstrumentation { + passesOnly 'com.ning:async-http-client:[1.1,1.6.1)' +} + +site { + title 'Ning AsyncHttpClient' + type 'Messaging' +} \ No newline at end of file diff --git a/instrumentation-security/ning-async-http-client-1.1.0/src/main/java/com/newrelic/agent/security/instrumentation/ning/http_1_1/NingHelper.java b/instrumentation-security/ning-async-http-client-1.1.0/src/main/java/com/newrelic/agent/security/instrumentation/ning/http_1_1/NingHelper.java new file mode 100644 index 000000000..208c5bd7a --- /dev/null +++ b/instrumentation-security/ning-async-http-client-1.1.0/src/main/java/com/newrelic/agent/security/instrumentation/ning/http_1_1/NingHelper.java @@ -0,0 +1,82 @@ +package com.newrelic.agent.security.instrumentation.ning.http_1_1; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.SecurityMetaData; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.SSRFOperation; +import com.newrelic.api.agent.security.utils.SSRFUtils; +import com.ning.http.client.Request; + +public class NingHelper { + public static final String METHOD_NAME_EXECUTE = "execute"; + public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "SSRF_OPERATION_LOCK_NING-"; + + public static void registerExitOperation(boolean isProcessingAllowed, AbstractOperation operation) { + try { + if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive() || NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() + ) { + return; + } + NewRelicSecurity.getAgent().registerExitEvent(operation); + } catch (Throwable ignored) { + } + } + + public static AbstractOperation preprocessSecurityHook(Request request, String uri, String methodName, String className) { + try { + SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData(); + if (!NewRelicSecurity.isHookProcessingActive() || securityMetaData.getRequest().isEmpty() + ) { + return null; + } + + // Add Security IAST header + String iastHeader = NewRelicSecurity.getAgent().getSecurityMetaData().getFuzzRequestIdentifier().getRaw(); + if (iastHeader != null && !iastHeader.trim().isEmpty()) { + request.getHeaders().add(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, iastHeader); + } + + String csecParaentId = securityMetaData.getCustomAttribute(GenericHelper.CSEC_PARENT_ID, String.class); + if(StringUtils.isNotBlank(csecParaentId)){ + request.getHeaders().add(GenericHelper.CSEC_PARENT_ID, csecParaentId); + } + + SSRFOperation operation = new SSRFOperation(uri, className, methodName); + try { + NewRelicSecurity.getAgent().registerOperation(operation); + } finally { + if (operation.getApiID() != null && !operation.getApiID().trim().isEmpty() && + operation.getExecutionId() != null && !operation.getExecutionId().trim().isEmpty()) { + // Add Security distributed tracing header + request.getHeaders().add(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, SSRFUtils.generateTracingHeaderValue(securityMetaData.getTracingHeaderValue(), operation.getApiID(), operation.getExecutionId(), NewRelicSecurity.getAgent().getAgentUUID())); + } + } + return operation; + } catch (Throwable e) { + if (e instanceof NewRelicSecurityException) { + e.printStackTrace(); + throw e; + } + } + return null; + } + + public static void releaseLock(int hashCode) { + try { + GenericHelper.releaseLock(NR_SEC_CUSTOM_ATTRIB_NAME, hashCode); + } catch (Throwable ignored) { + } + } + + public static boolean acquireLockIfPossible(int hashCode) { + try { + return GenericHelper.acquireLockIfPossible(NR_SEC_CUSTOM_ATTRIB_NAME, hashCode); + } catch (Throwable ignored) { + } + return false; + } +} diff --git a/instrumentation-security/ning-async-http-client-1.1.0/src/main/java/com/ning/http/client/AsyncHttpProvider_Instrumentation.java b/instrumentation-security/ning-async-http-client-1.1.0/src/main/java/com/ning/http/client/AsyncHttpProvider_Instrumentation.java new file mode 100644 index 000000000..04511aa11 --- /dev/null +++ b/instrumentation-security/ning-async-http-client-1.1.0/src/main/java/com/ning/http/client/AsyncHttpProvider_Instrumentation.java @@ -0,0 +1,45 @@ +package com.ning.http.client; + +import com.newrelic.agent.security.instrumentation.ning.http_1_1.NingHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.Future; + +@Weave(type = MatchType.Interface, originalName = "com.ning.http.client.AsyncHttpProvider") +public class AsyncHttpProvider_Instrumentation { + + public Future execute(Request request, AsyncHandler handler) throws IOException { + boolean isLockAcquired = NingHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + URI uri = null; + Future returnObj = null; + + try { + uri = new URI(request.getUrl()); + String scheme = uri.getScheme(); + + if (isLockAcquired && (scheme == null || scheme.equals("http") || scheme.equals("https"))) { + operation = NingHelper.preprocessSecurityHook(request, uri.toString(), NingHelper.METHOD_NAME_EXECUTE, this.getClass().getName()); + } + } catch (URISyntaxException uriSyntaxException) { + // Instrumentation won't work and normal execution will continue + } + + try { + returnObj = Weaver.callOriginal(); + } finally { + if (isLockAcquired) { + NingHelper.releaseLock(this.hashCode()); + } + } + NingHelper.registerExitOperation(isLockAcquired, operation); + + return returnObj; + } +} diff --git a/instrumentation-security/ning-async-http-client-1.1.0/src/main/java/play/CorePlugin.java b/instrumentation-security/ning-async-http-client-1.1.0/src/main/java/play/CorePlugin.java new file mode 100644 index 000000000..1d68ebdd2 --- /dev/null +++ b/instrumentation-security/ning-async-http-client-1.1.0/src/main/java/play/CorePlugin.java @@ -0,0 +1,11 @@ +package play; + +import com.newrelic.api.agent.weaver.SkipIfPresent; + +/** + * Play v1 instrumentation is implemented using its own set of pointcuts that don't work well with our async APIs. This + * class is present in Play v1 but not v2, and will cause this module NOT to load if the customer is using Play v1. + */ +@SkipIfPresent +public class CorePlugin { +} diff --git a/instrumentation-security/ning-async-http-client-1.1.0/src/test/java/com/nr/agent/security/instrumentation/ning/http_1_1/NingAsyncHttpClient11Test.java b/instrumentation-security/ning-async-http-client-1.1.0/src/test/java/com/nr/agent/security/instrumentation/ning/http_1_1/NingAsyncHttpClient11Test.java new file mode 100644 index 000000000..c90486c40 --- /dev/null +++ b/instrumentation-security/ning-async-http-client-1.1.0/src/test/java/com/nr/agent/security/instrumentation/ning/http_1_1/NingAsyncHttpClient11Test.java @@ -0,0 +1,398 @@ +package com.nr.agent.security.instrumentation.ning.http_1_1; + +import com.newrelic.agent.security.introspec.InstrumentationTestConfig; +import com.newrelic.agent.security.introspec.SecurityInstrumentationTestRunner; +import com.newrelic.agent.security.introspec.SecurityIntrospector; +import com.newrelic.agent.security.introspec.internal.HttpServerRule; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; +import com.newrelic.api.agent.security.schema.operation.SSRFOperation; +import com.ning.http.client.AsyncCompletionHandler; +import com.ning.http.client.AsyncHttpClient; +import com.ning.http.client.Request; +import com.ning.http.client.RequestBuilder; +import com.ning.http.client.RequestType; +import com.ning.http.client.Response; +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; + +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +@RunWith(SecurityInstrumentationTestRunner.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@InstrumentationTestConfig(includePrefixes = { "com.newrelic.agent.security.instrumentation.ning.http_1_1", "com.ning" }) +public class NingAsyncHttpClient11Test { + + private static final int TIMEOUT = 30000; + + @ClassRule + public static HttpServerRule server = new HttpServerRule(); + + @Test + public void testPrepare() throws Exception { + URI endpoint = server.getEndPoint(); + String host = endpoint.getHost(); + + String headerValue = String.valueOf(UUID.randomUUID()); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + setCSECHeaders(headerValue, introspector); + + makeAsyncRequest(endpoint.toURL().toString()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + SSRFOperation operation = (SSRFOperation) operations.get(0); + Map headers = server.getHeaders(); + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint().toString(), operation.getArg()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.HTTP_REQUEST, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", "execute", operation.getMethodName()); + verifyHeaders(headerValue, headers); + } + + @Test + public void testPrepareGet() throws Exception { + URI endpoint = server.getEndPoint(); + String host = endpoint.getHost(); + + String headerValue = String.valueOf(UUID.randomUUID()); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + setCSECHeaders(headerValue, introspector); + + makeAsyncRequestGet(endpoint.toURL().toString()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + SSRFOperation operation = (SSRFOperation) operations.get(0); + Map headers = server.getHeaders(); + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint().toString(), operation.getArg()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.HTTP_REQUEST, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", "execute", operation.getMethodName()); + verifyHeaders(headerValue, headers); + } + + @Test + public void testPreparePost() throws Exception { + URI endpoint = server.getEndPoint(); + String host = endpoint.getHost(); + + String headerValue = String.valueOf(UUID.randomUUID()); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + setCSECHeaders(headerValue, introspector); + + makeAsyncRequestPost(endpoint.toURL().toString()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + SSRFOperation operation = (SSRFOperation) operations.get(0); + Map headers = server.getHeaders(); + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint().toString(), operation.getArg()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.HTTP_REQUEST, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", "execute", operation.getMethodName()); + verifyHeaders(headerValue, headers); + } + + @Test + public void testPreparePut() throws Exception { + URI endpoint = server.getEndPoint(); + String host = endpoint.getHost(); + + String headerValue = String.valueOf(UUID.randomUUID()); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + setCSECHeaders(headerValue, introspector); + + makeAsyncRequestPut(endpoint.toURL().toString()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + SSRFOperation operation = (SSRFOperation) operations.get(0); + Map headers = server.getHeaders(); + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint().toString(), operation.getArg()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.HTTP_REQUEST, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", "execute", operation.getMethodName()); + verifyHeaders(headerValue, headers); + } + + @Test + public void testPrepareDelete() throws Exception { + URI endpoint = server.getEndPoint(); + String host = endpoint.getHost(); + + String headerValue = String.valueOf(UUID.randomUUID()); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + setCSECHeaders(headerValue, introspector); + + makeAsyncRequestDelete(endpoint.toURL().toString()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + SSRFOperation operation = (SSRFOperation) operations.get(0); + Map headers = server.getHeaders(); + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint().toString(), operation.getArg()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.HTTP_REQUEST, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", "execute", operation.getMethodName()); + verifyHeaders(headerValue, headers); + } + + @Test + public void testPrepareHead() throws Exception { + URI endpoint = server.getEndPoint(); + String host = endpoint.getHost(); + + String headerValue = String.valueOf(UUID.randomUUID()); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + setCSECHeaders(headerValue, introspector); + + makeAsyncRequestHead(endpoint.toURL().toString()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + SSRFOperation operation = (SSRFOperation) operations.get(0); + Map headers = server.getHeaders(); + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint().toString(), operation.getArg()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.HTTP_REQUEST, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", "execute", operation.getMethodName()); + verifyHeaders(headerValue, headers); + } + + @Test + public void testPrepareOptions() throws Exception { + URI endpoint = server.getEndPoint(); + String host = endpoint.getHost(); + + String headerValue = String.valueOf(UUID.randomUUID()); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + setCSECHeaders(headerValue, introspector); + + makeAsyncRequestOptions(endpoint.toURL().toString()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + SSRFOperation operation = (SSRFOperation) operations.get(0); + Map headers = server.getHeaders(); + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint().toString(), operation.getArg()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.HTTP_REQUEST, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", "execute", operation.getMethodName()); + verifyHeaders(headerValue, headers); + } + + @Test + public void testExecuteRequest1() throws Exception { + URI endpoint = server.getEndPoint(); + String host = endpoint.getHost(); + + String headerValue = String.valueOf(UUID.randomUUID()); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + setCSECHeaders(headerValue, introspector); + + makeAsyncExecuteRequest1(endpoint.toURL().toString()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + SSRFOperation operation = (SSRFOperation) operations.get(0); + Map headers = server.getHeaders(); + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint().toString(), operation.getArg()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.HTTP_REQUEST, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", "execute", operation.getMethodName()); + verifyHeaders(headerValue, headers); + } + + @Test + public void testExecuteRequest2() throws Exception { + URI endpoint = server.getEndPoint(); + String host = endpoint.getHost(); + + String headerValue = String.valueOf(UUID.randomUUID()); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + setCSECHeaders(headerValue, introspector); + + makeAsyncExecuteRequest2(endpoint.toURL().toString()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + SSRFOperation operation = (SSRFOperation) operations.get(0); + Map headers = server.getHeaders(); + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint().toString(), operation.getArg()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.HTTP_REQUEST, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", "execute", operation.getMethodName()); + verifyHeaders(headerValue, headers); + } + + @Trace(dispatcher = true) + public static int makeAsyncRequestGet(String url) { + AsyncHttpClient client = new AsyncHttpClient(); + try { + AsyncHttpClient.BoundRequestBuilder builder = client.prepareGet(url); + Future future = builder.execute(); + Response response = future.get(); + return response.getStatusCode(); + } catch (Exception e) { + return -1; + } finally { + client.close(); + } + } + + @Trace(dispatcher = true) + public static int makeAsyncRequestPost(String url) { + AsyncHttpClient client = new AsyncHttpClient(); + try { + AsyncHttpClient.BoundRequestBuilder builder = client.preparePost(url); + Future future = builder.execute(); + Response response = future.get(); + return response.getStatusCode(); + } catch (Exception e) { + return -1; + } finally { + client.close(); + } + } + + @Trace(dispatcher = true) + public static int makeAsyncRequestPut(String url) { + AsyncHttpClient client = new AsyncHttpClient(); + try { + AsyncHttpClient.BoundRequestBuilder builder = client.preparePut(url); + Future future = builder.execute(); + Response response = future.get(); + return response.getStatusCode(); + } catch (Exception e) { + return -1; + } finally { + client.close(); + } + } + + @Trace(dispatcher = true) + public static int makeAsyncRequestDelete(String url) { + AsyncHttpClient client = new AsyncHttpClient(); + try { + AsyncHttpClient.BoundRequestBuilder builder = client.prepareDelete(url); + Future future = builder.execute(); + Response response = future.get(); + return response.getStatusCode(); + } catch (Exception e) { + return -1; + } finally { + client.close(); + } + } + + @Trace(dispatcher = true) + public static int makeAsyncRequestHead(String url) { + AsyncHttpClient client = new AsyncHttpClient(); + try { + AsyncHttpClient.BoundRequestBuilder builder = client.prepareHead(url); + Future future = builder.execute(); + Response response = future.get(); + return response.getStatusCode(); + } catch (Exception e) { + return -1; + } finally { + client.close(); + } + } + + @Trace(dispatcher = true) + public static int makeAsyncRequestOptions(String url) { + AsyncHttpClient client = new AsyncHttpClient(); + try { + AsyncHttpClient.BoundRequestBuilder builder = client.prepareOptions(url); + Future future = builder.execute(); + Response response = future.get(); + return response.getStatusCode(); + } catch (Exception e) { + return -1; + } finally { + client.close(); + } + } + + @Trace(dispatcher = true) + public static int makeAsyncRequest(String url) { + AsyncHttpClient client = new AsyncHttpClient(); + try { + Request request = new RequestBuilder(RequestType.GET).setUrl(url).build(); + AsyncHttpClient.BoundRequestBuilder builder = client.prepareRequest(request); + Future future = builder.execute(); + Response response = future.get(); + return response.getStatusCode(); + } catch (Exception e) { + return -1; + } finally { + client.close(); + } + } + + @Trace(dispatcher = true) + private static void makeAsyncExecuteRequest1(String url) { + AsyncHttpClient client = new AsyncHttpClient(); + try{ + Request request = new RequestBuilder(RequestType.GET).setUrl(url).build(); + Future f = client.executeRequest(request); + Response response = f.get(); + response.getStatusCode(); + } catch (InterruptedException | IOException | ExecutionException e) { + } + } + + @Trace(dispatcher = true) + private static void makeAsyncExecuteRequest2(String url) { + AsyncHttpClient client = new AsyncHttpClient(); + try{ + Request request = new RequestBuilder(RequestType.GET).setUrl(url).build(); + Future f = client.executeRequest(request, new AsyncCompletionHandler() { + + @Override + public Response onCompleted(Response response) { + return response; + } + + @Override + public void onThrowable(Throwable t) { + } + }); + Response response = f.get(); + response.getStatusCode(); + } catch (InterruptedException | IOException | ExecutionException e) { + } + } + + private void setCSECHeaders(String headerValue, SecurityIntrospector introspector) { + introspector.setK2FuzzRequestId(headerValue+"a"); + introspector.setK2ParentId(headerValue+"b"); + introspector.setK2TracingData(headerValue); + } + + private void verifyHeaders(String headerValue, Map headers) { + Assert.assertTrue(String.format("Missing K2 header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)); + Assert.assertEquals(String.format("Invalid K2 header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue+"a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)); + Assert.assertTrue(String.format("Missing K2 header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)); + Assert.assertEquals(String.format("Invalid K2 header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue+"b", headers.get(GenericHelper.CSEC_PARENT_ID)); + Assert.assertTrue(String.format("Missing K2 header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase())); + Assert.assertEquals(String.format("Invalid K2 header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", + headerValue), headers.get( + ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase())); + } +} diff --git a/instrumentation-security/spray-http-1.3.1/build.gradle b/instrumentation-security/spray-http-1.3.1/build.gradle index 23f9007c1..14d61a161 100644 --- a/instrumentation-security/spray-http-1.3.1/build.gradle +++ b/instrumentation-security/spray-http-1.3.1/build.gradle @@ -16,7 +16,7 @@ dependencies { implementation("org.scala-lang:scala-library:2.10.7") implementation("io.spray:spray-routing_2.10:1.3.3") implementation("com.typesafe.akka:akka-actor_2.10:2.3.14") - + testImplementation("io.spray:spray-can_2.10:1.3.3") } verifyInstrumentation { diff --git a/instrumentation-security/spray-http-1.3.1/src/test/java/com/nr/agent/security/instrumentation/spray/http/SprayTest.java b/instrumentation-security/spray-http-1.3.1/src/test/java/com/nr/agent/security/instrumentation/spray/http/SprayTest.java new file mode 100644 index 000000000..a96b77eda --- /dev/null +++ b/instrumentation-security/spray-http-1.3.1/src/test/java/com/nr/agent/security/instrumentation/spray/http/SprayTest.java @@ -0,0 +1,159 @@ +package com.nr.agent.security.instrumentation.spray.http; + +import com.newrelic.agent.security.introspec.InstrumentationTestConfig; +import com.newrelic.agent.security.introspec.SecurityInstrumentationTestRunner; +import com.newrelic.agent.security.introspec.SecurityIntrospector; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; +import com.newrelic.api.agent.security.schema.operation.RXSSOperation; +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.ServerSocket; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@RunWith(SecurityInstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = { "spray", "scala" }) +public class SprayTest { + + @ClassRule + public static HttpServer server = HttpServer$.MODULE$.apply(getRandomPort()); + private static int port; + + @Test + public void testGet() throws IOException { + makeRequest("GET", StringUtils.EMPTY); + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertNotNull(operations); + Assert.assertEquals(1, operations.size()); + + RXSSOperation operation = (RXSSOperation) operations.get(0); + Assert.assertNotNull("No target operation detected", operation); + Assert.assertEquals("Wrong case-type detected", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType()); + Assert.assertEquals("Wrong method name detected", "marshalTo", operation.getMethodName()); + + Assert.assertNotNull("Empty request detected", operation.getRequest()); + Assert.assertEquals("Wrong Protocol detected", "http", operation.getRequest().getProtocol()); + Assert.assertEquals("Wrong port detected", port, operation.getRequest().getServerPort()); + Assert.assertEquals("Wrong Content-type detected", StringUtils.EMPTY, operation.getRequest().getContentType()); + + Assert.assertNotNull("Empty response detected", operation.getResponse()); + Assert.assertEquals("Wrong port detected", "testing API", operation.getResponse().getResponseBody().toString()); + Assert.assertEquals("Wrong Content-type detected", "text/plain", operation.getResponse().getResponseContentType()); + + } + @Test + public void testPost() throws IOException { + makeRequest("POST", StringUtils.EMPTY); + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertNotNull(operations); + Assert.assertEquals(1, operations.size()); + + RXSSOperation operation = (RXSSOperation) operations.get(0); + Assert.assertNotNull("No target operation detected", operation); + Assert.assertEquals("Wrong case-type detected", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType()); + Assert.assertEquals("Wrong method name detected", "marshalTo", operation.getMethodName()); + + Assert.assertNotNull("Empty request detected", operation.getRequest()); + Assert.assertEquals("Wrong Protocol detected", "http", operation.getRequest().getProtocol()); + Assert.assertEquals("Wrong port detected", port, operation.getRequest().getServerPort()); + Assert.assertEquals("Wrong Content-type detected", "text/plain", operation.getRequest().getContentType()); + Assert.assertEquals("Wrong Content-type detected", "data", operation.getRequest().getBody().toString()); + + Assert.assertNotNull("Empty response detected", operation.getResponse()); + Assert.assertEquals("Wrong port detected", "testing API", operation.getResponse().getResponseBody().toString()); + Assert.assertEquals("Wrong Content-type detected", "text/plain", operation.getResponse().getResponseContentType()); + + } + + @Test + public void testWithCSECHeader() throws IOException { + String headerValue = String.valueOf(UUID.randomUUID()); + makeRequest("GET", headerValue); + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertNotNull(operations); + Assert.assertEquals(1, operations.size()); + + RXSSOperation operation = (RXSSOperation) operations.get(0); + Assert.assertNotNull("No target operation detected", operation); + Assert.assertEquals("Wrong case-type detected", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType()); + Assert.assertEquals("Wrong method name detected", "marshalTo", operation.getMethodName()); + + Assert.assertNotNull("Empty request detected", operation.getRequest()); + Assert.assertEquals("Wrong Protocol detected", "http", operation.getRequest().getProtocol()); + Assert.assertEquals("Wrong port detected", port, operation.getRequest().getServerPort()); + Assert.assertEquals("Wrong Content-type detected", StringUtils.EMPTY, operation.getRequest().getContentType()); + + Assert.assertNotNull("Empty response detected", operation.getResponse()); + Assert.assertEquals("Wrong port detected", "testing API", operation.getResponse().getResponseBody().toString()); + Assert.assertEquals("Wrong Content-type detected", "text/plain", operation.getResponse().getResponseContentType()); + + Map headers = operation.getRequest().getHeaders(); + Assert.assertTrue( + String.format("Missing header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), + headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID) + ); + Assert.assertEquals( + String.format("Invalid header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), + headerValue, + headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID) + ); + Assert.assertTrue( + String.format("Missing header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), + headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase()) + ); + Assert.assertEquals( + String.format("Invalid header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), + headerValue, + headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase()) + ); + Assert.assertTrue( + String.format("Missing header: %s", GenericHelper.CSEC_PARENT_ID), + headers.containsKey(GenericHelper.CSEC_PARENT_ID.toLowerCase()) + ); + Assert.assertEquals( + String.format("Invalid header value for: %s", GenericHelper.CSEC_PARENT_ID), + headerValue, + headers.get(GenericHelper.CSEC_PARENT_ID.toLowerCase()) + ); + } + + private static int getRandomPort(){ + try (ServerSocket socket = new ServerSocket(0)){ + return port = socket.getLocalPort(); + } catch (IOException e) { + throw new RuntimeException("Unable to allocate ephemeral port"); + } + } + + private void makeRequest(String method, String csecHeaders) throws IOException { + String ENDPOINT = String.format("http://localhost:%d/test", port); + HttpURLConnection conn = (HttpURLConnection) new URL(ENDPOINT).openConnection(); + conn.setRequestMethod(method); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", "text/plain"); + if (method.equalsIgnoreCase("POST")) { + conn.getOutputStream().write("data".getBytes()); + } + if (!csecHeaders.isEmpty()) { + conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, csecHeaders); + conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, csecHeaders); + conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, csecHeaders); + } + conn.connect(); + System.out.println("response status: " + conn.getResponseCode()); + } +} diff --git a/instrumentation-security/spray-http-1.3.1/src/test/scala/com/nr/agent/security/instrumentation/spray/http/HttpServer.scala b/instrumentation-security/spray-http-1.3.1/src/test/scala/com/nr/agent/security/instrumentation/spray/http/HttpServer.scala new file mode 100644 index 000000000..972012aea --- /dev/null +++ b/instrumentation-security/spray-http-1.3.1/src/test/scala/com/nr/agent/security/instrumentation/spray/http/HttpServer.scala @@ -0,0 +1,43 @@ +package com.nr.agent.security.instrumentation.spray.http + +import akka.actor.{Actor, ActorContext, ActorRef, ActorSystem, Props} +import akka.io.IO +import akka.pattern._ +import akka.util.Timeout +import org.junit.rules.ExternalResource +import spray.can.Http +import spray.routing.{HttpService, RequestContext, Route} + +import scala.concurrent.duration._ +import scala.concurrent.{Await, Future} +import scala.language.postfixOps + +class HttpServer(port: Int) extends ExternalResource { + private implicit val system: ActorSystem = ActorSystem() + private implicit val timeout: Timeout = 3 seconds + + private val handler: ActorRef = system.actorOf(Props[MainActor], name = "handler") + + private def start(port: Int): Future[Any] = Await.ready( + IO(Http) ? Http.Bind(handler, "localhost", port), + timeout.duration + ) + + private def stop(): Unit = { + IO(Http) ? Http.CloseAll + system.stop(handler) + system.shutdown() + } + override def before(): Unit = start(port) + override def after(): Unit = stop() +} + +object HttpServer { + def apply(port: Int) = new HttpServer(port) +} + +class MainActor extends Actor with HttpService { + def route: Route = path("test") { (ctx: RequestContext) => ctx.complete("testing API")} + def actorRefFactory: ActorContext = context + def receive: Receive = runRoute(route) +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 9015cbcb0..71d851ba8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -164,19 +164,19 @@ include 'instrumentation:grpc-1.4.0' include 'instrumentation:grpc-1.22.0' include 'instrumentation:grpc-1.40.0' -//include 'instrumentation:ning-async-http-client-1.1.0' -//include 'instrumentation:ning-async-http-client-1.6.1' -//include 'instrumentation:ning-async-http-client-1.0.0' -//include 'instrumentation:jersey-2' -//include 'instrumentation:jersey-2.16' -//include 'instrumentation:jersey-3' -//include 'instrumentation:spring-data-redis' -//include 'instrumentation:jcache-1.0.0' -//include 'instrumentation:lettuce-4.3' -//include 'instrumentation:lettuce-5.0' -//include 'instrumentation:spymemcached-2.12.0' -//include 'instrumentation:jetty-12' -//include 'instrumentation:mule-3.7' -//include 'instrumentation:mule-3.6' -include 'instrumentation:spray-can-1.3.1' +include 'instrumentation:ning-async-http-client-1.1.0' +include 'instrumentation:ning-async-http-client-1.6.1' +include 'instrumentation:ning-async-http-client-1.0.0' +include 'instrumentation:jersey-2' +include 'instrumentation:jersey-2.16' +include 'instrumentation:jersey-3' +include 'instrumentation:spring-data-redis' +include 'instrumentation:jcache-1.0.0' +include 'instrumentation:lettuce-4.3' +include 'instrumentation:lettuce-5.0' +include 'instrumentation:spymemcached-2.12.0' +include 'instrumentation:jetty-12' +include 'instrumentation:mule-3.7' +include 'instrumentation:mule-3.6' include 'instrumentation:spray-http-1.3.1' +include 'instrumentation:spray-can-1.3.1' \ No newline at end of file