diff --git a/instrumentation-security/jetty-12/build.gradle b/instrumentation-security/jetty-12/build.gradle new file mode 100644 index 000000000..8501cd8aa --- /dev/null +++ b/instrumentation-security/jetty-12/build.gradle @@ -0,0 +1,28 @@ +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("org.eclipse.jetty:jetty-server:12.0.0") + testImplementation('jakarta.servlet:jakarta.servlet-api:6.0.0') + testImplementation("org.eclipse.jetty:jetty-servlet:11.0.16") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.jetty-12', 'Priority': '10' } +} + +verifyInstrumentation { + passesOnly 'org.eclipse.jetty:jetty-server:[12.0.0,)' + excludeRegex '.*(alpha|beta|rc).*' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +site { + title 'Jetty' + type 'Appserver' +} \ No newline at end of file diff --git a/instrumentation-security/jetty-12/src/main/java/com/newrelic/agent/security/instrumentation/jetty12/server/HttpServletHelper.java b/instrumentation-security/jetty-12/src/main/java/com/newrelic/agent/security/instrumentation/jetty12/server/HttpServletHelper.java new file mode 100644 index 000000000..71ea403d1 --- /dev/null +++ b/instrumentation-security/jetty-12/src/main/java/com/newrelic/agent/security/instrumentation/jetty12/server/HttpServletHelper.java @@ -0,0 +1,191 @@ +package com.newrelic.agent.security.instrumentation.jetty12.server; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.LowSeverityHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.AgentMetaData; +import com.newrelic.api.agent.security.schema.HttpRequest; +import com.newrelic.api.agent.security.schema.SecurityMetaData; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.RXSSOperation; +import com.newrelic.api.agent.security.schema.policy.AgentPolicy; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class HttpServletHelper { + + private static final String X_FORWARDED_FOR = "x-forwarded-for"; + private static final String EMPTY = ""; + public static final String QUESTION_MARK = "?"; + public static final String SERVICE_METHOD_NAME = "handle"; + public static final String SERVICE_ASYNC_METHOD_NAME = "handleAsync"; + + public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "JETTY_SERVLET_LOCK-"; + + public static void processHttpRequestHeader(Request request, HttpRequest securityRequest) { + HttpFields headers = request.getHeaders(); + if (headers!=null){ + Set headerKeys = headers.getFieldNamesCollection(); + Iterator headerKeysIterator = headerKeys.iterator(); + while(headerKeysIterator.hasNext()){ + boolean takeNextValue = false; + String headerKey = headerKeysIterator.next(); + if (headerKey != null) { + headerKey = headerKey.toLowerCase(); + } + AgentPolicy agentPolicy = NewRelicSecurity.getAgent().getCurrentPolicy(); + AgentMetaData agentMetaData = NewRelicSecurity.getAgent().getSecurityMetaData().getMetaData(); + if (agentPolicy != null + && agentPolicy.getProtectionMode().getEnabled() + && agentPolicy.getProtectionMode().getIpBlocking().getEnabled() + && agentPolicy.getProtectionMode().getIpBlocking().getIpDetectViaXFF() + && X_FORWARDED_FOR.equals(headerKey)) { + takeNextValue = true; + } else if (ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID.equals(headerKey)) { + // TODO: May think of removing this intermediate obj and directly create K2 Identifier. + NewRelicSecurity.getAgent().getSecurityMetaData().setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(request.getHeaders().get(headerKey))); + } else if(GenericHelper.CSEC_PARENT_ID.equals(headerKey)) { + NewRelicSecurity.getAgent().getSecurityMetaData() + .addCustomAttribute(GenericHelper.CSEC_PARENT_ID, request.getHeaders().get(headerKey)); + } + + String headerFullValue = EMPTY; + String headerValue = request.getHeaders().get(headerKey); + + if (headerValue != null && !headerValue.trim().isEmpty()) { + if (takeNextValue) { + agentMetaData.setClientDetectedFromXFF(true); + securityRequest.setClientIP(headerValue); + agentMetaData.getIps() + .add(securityRequest.getClientIP()); + securityRequest.setClientPort(EMPTY); + } + if (headerFullValue.trim().isEmpty()) { + headerFullValue = headerValue; + } else { + headerFullValue = String.join(";", headerFullValue, headerValue); + } + } + securityRequest.getHeaders().put(headerKey, headerFullValue); + } + } + } + + public static String getTraceHeader(Map headers) { + String data = EMPTY; + if (headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER) || headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase())) { + data = headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER); + if (data == null || data.trim().isEmpty()) { + data = headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase()); + } + } + return data; + } + + public static boolean isServletLockAcquired() { + try { + return NewRelicSecurity.isHookProcessingActive() && + Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); + } catch (Throwable ignored) { + } + return false; + } + + public static boolean acquireServletLockIfPossible() { + try { + if (NewRelicSecurity.isHookProcessingActive() && + !isServletLockAcquired()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); + return true; + } + } catch (Throwable ignored) { + } + return false; + } + + public static void releaseServletLock() { + try { + if (NewRelicSecurity.isHookProcessingActive()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); + } + } catch (Throwable ignored) { + } + } + + private static String getNrSecCustomAttribName() { + return NR_SEC_CUSTOM_ATTRIB_NAME; + } + + public static void preprocessSecurityHook(Request request) { + try { + if (!NewRelicSecurity.isHookProcessingActive() || request == null) { + return; + } + SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData(); + + HttpRequest securityRequest = securityMetaData.getRequest(); + if (securityRequest.isRequestParsed()) { + return; + } + + AgentMetaData securityAgentMetaData = securityMetaData.getMetaData(); + + securityRequest.setMethod(request.getMethod()); + securityRequest.setClientIP(Request.getRemoteAddr(request)); + securityRequest.setServerPort(Request.getLocalPort(request)); + + if (securityRequest.getClientIP() != null && !securityRequest.getClientIP().trim().isEmpty()) { + securityAgentMetaData.getIps().add(securityRequest.getClientIP()); + securityRequest.setClientPort(String.valueOf(Request.getRemotePort(request))); + } + + HttpServletHelper.processHttpRequestHeader(request, securityRequest); + + securityMetaData.setTracingHeaderValue(HttpServletHelper.getTraceHeader(securityRequest.getHeaders())); + + securityRequest.setProtocol(request.getHttpURI().getScheme()); + + // TODO: Create OutBoundHttp data here : Skipping for now. + + String url = request.getHttpURI().asString(); + if (url != null && !url.trim().isEmpty()) { + securityRequest.setUrl(url); + } + securityRequest.setContentType(request.getHeaders().get(HttpHeader.CONTENT_TYPE)); + + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 2, trace.length)); + securityRequest.setRequestParsed(true); + } catch (Throwable ignored) { + } + } + + public static void postProcessSecurityHook(Request request, Response response, String className, String methodName) { + try { + if (!NewRelicSecurity.isHookProcessingActive() + ) { + return; + } + //Add request URI hash to low severity event filter + LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest()); + RXSSOperation rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest(), + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse(), + className, methodName); + NewRelicSecurity.getAgent().registerOperation(rxssOperation); + ServletHelper.tmpFileCleanUp(NewRelicSecurity.getAgent().getSecurityMetaData().getFuzzRequestIdentifier().getTempFiles()); + } catch (Throwable e) { + if (e instanceof NewRelicSecurityException) { + e.printStackTrace(); + throw e; + } + } + } +} diff --git a/instrumentation-security/jetty-12/src/main/java/com/newrelic/agent/security/instrumentation/jetty12/server/RequestHandler_Instrumentation.java b/instrumentation-security/jetty-12/src/main/java/com/newrelic/agent/security/instrumentation/jetty12/server/RequestHandler_Instrumentation.java new file mode 100644 index 000000000..20e5016e3 --- /dev/null +++ b/instrumentation-security/jetty-12/src/main/java/com/newrelic/agent/security/instrumentation/jetty12/server/RequestHandler_Instrumentation.java @@ -0,0 +1,33 @@ +package com.newrelic.agent.security.instrumentation.jetty12.server; + +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +@Weave(type = MatchType.Interface, originalName = "org.eclipse.jetty.server.Request$Handler") +public class RequestHandler_Instrumentation { + + public boolean handle(Request request, Response response, Callback callback) { + ServletHelper.registerUserLevelCode("jetty-handle"); + boolean isServletLockAcquired = HttpServletHelper.acquireServletLockIfPossible(); + if (isServletLockAcquired) { + HttpServletHelper.preprocessSecurityHook(request); + } + boolean result; + try { + result = Weaver.callOriginal(); + } finally { + if (isServletLockAcquired) { + HttpServletHelper.releaseServletLock(); + } + } + if (isServletLockAcquired) { + HttpServletHelper.postProcessSecurityHook(request, response, this.getClass().getName(), HttpServletHelper.SERVICE_METHOD_NAME); + } + return result; + } +} diff --git a/instrumentation-security/jetty-12/src/test/java/com/nr/agent/security/instrumentation/jetty12/test/MyServlet.java b/instrumentation-security/jetty-12/src/test/java/com/nr/agent/security/instrumentation/jetty12/test/MyServlet.java new file mode 100644 index 000000000..e5c4af3a1 --- /dev/null +++ b/instrumentation-security/jetty-12/src/test/java/com/nr/agent/security/instrumentation/jetty12/test/MyServlet.java @@ -0,0 +1,22 @@ +package com.nr.agent.security.instrumentation.jetty12.test; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +import java.io.BufferedReader; +import java.io.IOException; + +public class MyServlet extends Handler.Abstract { + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + System.out.println("Request completed!"); + callback.succeeded(); + return true; + } +} diff --git a/instrumentation-security/jetty-12/src/test/java/com/nr/agent/security/instrumentation/jetty12/test/ServerTest.java b/instrumentation-security/jetty-12/src/test/java/com/nr/agent/security/instrumentation/jetty12/test/ServerTest.java new file mode 100644 index 000000000..71bcdb51c --- /dev/null +++ b/instrumentation-security/jetty-12/src/test/java/com/nr/agent/security/instrumentation/jetty12/test/ServerTest.java @@ -0,0 +1,347 @@ +package com.nr.agent.security.instrumentation.jetty12.test; + +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.Trace; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.AgentMetaData; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; +import com.newrelic.api.agent.security.schema.operation.RXSSOperation; +import com.newrelic.security.test.marker.Java11IncompatibleTest; +import com.newrelic.security.test.marker.Java8IncompatibleTest; +import com.newrelic.security.test.marker.Java9IncompatibleTest; +import com.newrelic.agent.security.instrumentation.jetty12.server.HttpServletHelper; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.Callback; +import org.junit.After; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; + +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; + +@Category({ Java8IncompatibleTest.class, Java9IncompatibleTest.class, Java11IncompatibleTest.class }) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(SecurityInstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = {"com.newrelic.agent.security.instrumentation.jetty12.server"}) +public class ServerTest { + public static int PORT = 0; + public static String ENDPOINT = "http://localhost:%d/"; + + private Server server; + + @After + public void teardown() throws Exception { + if (server!=null&&server.isRunning()) { + server.stop(); + } + } + + @Test + public void testHandle() throws Exception { + startWithServlet(); + String headerValue = serviceWithHeaders(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + + AgentMetaData meta = introspector.getSecurityMetaData().getMetaData(); + Assert.assertNotNull("Service trace can not be empty/null.", meta.getServiceTrace()); + Assert.assertTrue("userLevelService method was not encountered.", meta.isUserLevelServiceMethodEncountered()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + + RXSSOperation operation = (RXSSOperation) operations.get(0); + + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", HttpServletHelper.SERVICE_METHOD_NAME, operation.getMethodName()); + Assert.assertEquals("Wrong client IP detected", "127.0.0.1", operation.getRequest().getClientIP()); + Assert.assertEquals("Wrong Protocol detected", "http", operation.getRequest().getProtocol()); + + Assert.assertEquals("Wrong method", "GET", operation.getRequest().getMethod()); + Assert.assertEquals("Wrong port detected", PORT, operation.getRequest().getServerPort()); + Assert.assertEquals("Wrong Content-type detected", "text/plain", operation.getRequest().getContentType()); + + 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()) + ); + } + + @Test + public void testHandle1() throws Exception { + startWithHandler(); + String headerValue = serviceWithHeaders(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + AgentMetaData meta = introspector.getSecurityMetaData().getMetaData(); + Assert.assertNotNull("Service trace can not be empty/null.", meta.getServiceTrace()); + Assert.assertTrue("userLevelService Method was not encountered.", meta.isUserLevelServiceMethodEncountered()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + RXSSOperation operation = (RXSSOperation) operations.get(0); + + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", HttpServletHelper.SERVICE_METHOD_NAME, operation.getMethodName()); + Assert.assertEquals("Wrong client IP detected", "127.0.0.1", operation.getRequest().getClientIP()); + Assert.assertEquals("Wrong Protocol detected", "http", operation.getRequest().getProtocol()); + + Assert.assertEquals("Wrong method", "GET", operation.getRequest().getMethod()); + Assert.assertEquals("Wrong port detected", PORT, operation.getRequest().getServerPort()); + Assert.assertEquals("Wrong Content-type detected", "text/plain", operation.getRequest().getContentType()); + + 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 K2 header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), + headerValue, + headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase()) + ); + } + + @Test + public void testHandle2() throws Exception { + startWithServlet(); + serviceWithoutHeaders(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + + AgentMetaData meta = introspector.getSecurityMetaData().getMetaData(); + Assert.assertNotNull("Service trace can not be empty/null.", meta.getServiceTrace()); + Assert.assertTrue("userLevelService method was not encountered.", meta.isUserLevelServiceMethodEncountered()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + RXSSOperation operation = (RXSSOperation) operations.get(0); + + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", HttpServletHelper.SERVICE_METHOD_NAME, operation.getMethodName()); + Assert.assertEquals("Wrong client IP detected", "127.0.0.1", operation.getRequest().getClientIP()); + Assert.assertEquals("Wrong Protocol detected", "http", operation.getRequest().getProtocol()); + + Assert.assertEquals("Wrong method", "GET", operation.getRequest().getMethod()); + Assert.assertEquals("Wrong port detected", PORT, operation.getRequest().getServerPort()); + Assert.assertEquals("Wrong Content-type detected", "text/plain", operation.getRequest().getContentType()); + } + + @Test + public void testHandle3() throws Exception { + startWithHandler(); + serviceWithoutHeaders(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + + AgentMetaData meta = introspector.getSecurityMetaData().getMetaData(); + Assert.assertNotNull("Service trace can not be empty/null.", meta.getServiceTrace()); + Assert.assertTrue("userLevelService method was not encountered.", meta.isUserLevelServiceMethodEncountered()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + RXSSOperation operation = (RXSSOperation) operations.get(0); + + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", HttpServletHelper.SERVICE_METHOD_NAME, operation.getMethodName()); + Assert.assertEquals("Wrong client IP detected", "127.0.0.1", operation.getRequest().getClientIP()); + Assert.assertEquals("Wrong Protocol detected", "http", operation.getRequest().getProtocol()); + + Assert.assertEquals("Wrong method", "GET", operation.getRequest().getMethod()); + Assert.assertEquals("Wrong port detected", PORT, operation.getRequest().getServerPort()); + Assert.assertEquals("Wrong Content-type detected", "text/plain", operation.getRequest().getContentType()); + } + + @Test + public void testHandle4() throws Exception { + startWithHandlerNonBlocking(); + serviceWithoutHeaders(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + + AgentMetaData meta = introspector.getSecurityMetaData().getMetaData(); + Assert.assertNotNull("Service trace can not be empty/null.", meta.getServiceTrace()); + Assert.assertTrue("userLevelService method was not encountered.", meta.isUserLevelServiceMethodEncountered()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + RXSSOperation operation = (RXSSOperation) operations.get(0); + + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", HttpServletHelper.SERVICE_METHOD_NAME, operation.getMethodName()); + Assert.assertEquals("Wrong client IP detected", "127.0.0.1", operation.getRequest().getClientIP()); + Assert.assertEquals("Wrong Protocol detected", "http", operation.getRequest().getProtocol()); + + Assert.assertEquals("Wrong method", "GET", operation.getRequest().getMethod()); + Assert.assertEquals("Wrong port detected", PORT, operation.getRequest().getServerPort()); + Assert.assertEquals("Wrong Content-type detected", "text/plain", operation.getRequest().getContentType()); + } + + @Test + public void testHandle5() throws Exception { + startWithHandlerNonBlocking(); + String headerValue = serviceWithHeaders(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + + AgentMetaData meta = introspector.getSecurityMetaData().getMetaData(); + Assert.assertNotNull("Service trace can not be empty/null.", meta.getServiceTrace()); + Assert.assertTrue("userLevelService method was not encountered.", meta.isUserLevelServiceMethodEncountered()); + + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + RXSSOperation operation = (RXSSOperation) operations.get(0); + + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType()); + Assert.assertEquals("Invalid executed method name.", HttpServletHelper.SERVICE_METHOD_NAME, operation.getMethodName()); + Assert.assertEquals("Wrong client IP detected", "127.0.0.1", operation.getRequest().getClientIP()); + Assert.assertEquals("Wrong Protocol detected", "http", operation.getRequest().getProtocol()); + + Assert.assertEquals("Wrong method", "GET", operation.getRequest().getMethod()); + Assert.assertEquals("Wrong port detected", PORT, operation.getRequest().getServerPort()); + Assert.assertEquals("Wrong Content-type detected", "text/plain", operation.getRequest().getContentType()); + + 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()) + ); + } + + private void startWithServlet() throws Exception { + PORT = getRandomPort(); + Server server = new Server(PORT); +// server.setHandler(new ContextHandler(new MyServlet(), "/testapp/something?ok=34")); + server.setHandler(new MyServlet()); + server.start(); + } + + private void startWithHandler() throws Exception { + PORT = getRandomPort(); + server = new Server(PORT); + server.setHandler( + new Handler.Abstract() { + @Override + public boolean handle (Request request, Response response, Callback callback) throws Exception { + System.out.println("Request 1 completed!"); + callback.succeeded(); + return true; + } + }); + server.start(); + } + + private void startWithHandlerNonBlocking() throws Exception { + PORT = getRandomPort(); + server = new Server(PORT); + server.setHandler( + new Handler.Abstract.NonBlocking() { + @Override + public boolean handle (Request request, Response response, Callback callback) throws Exception { + System.out.println("Request 2 completed!"); + callback.succeeded(); + return true; + } + }); + server.start(); + } + + @Trace(dispatcher = true) + private void serviceWithoutHeaders() throws Exception { + URL u = new URL(String.format(ENDPOINT, PORT)); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + + conn.setRequestProperty("content-type", "text/plain;charset=utf-8"); + conn.setRequestMethod("GET"); + conn.connect(); + + System.out.println(conn.getResponseCode()); + waitForProcessing(); + } + + @Trace(dispatcher = true) + private String serviceWithHeaders() throws Exception { + String headerValue = String.valueOf(UUID.randomUUID()); + URL u = new URL(String.format(ENDPOINT, PORT)+"testapp/something?ok=12"); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + + conn.setRequestProperty("content-type", "text/plain;charset=utf-8"); + conn.setRequestMethod("GET"); + conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue); + conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, headerValue); + conn.connect(); + + conn.getResponseCode(); + waitForProcessing(); + return headerValue; + } + + private static int getRandomPort() { + try (ServerSocket socket = new ServerSocket(0)){ + return socket.getLocalPort(); + } catch (IOException e) { + throw new RuntimeException("Unable to allocate ephemeral port "); + } + } + + private static void waitForProcessing() { + try { + Thread.sleep(50); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/settings.gradle b/settings.gradle index 19e86c4f8..42d6b1158 100644 --- a/settings.gradle +++ b/settings.gradle @@ -153,6 +153,7 @@ include 'instrumentation:commons-jxpath' //include 'instrumentation:apache-wicket-6.4' //include 'instrumentation:apache-wicket-7.0' //include 'instrumentation:apache-wicket-8.0' +include 'instrumentation:jetty-12' include 'instrumentation:async-http-client-2.0.0' include 'instrumentation:sun-net-httpserver' include 'instrumentation:tomcat-7'