From 5c96721861870d9bd0c4c24e392a307da8571ded Mon Sep 17 00:00:00 2001 From: Monu Lakshkar Date: Mon, 11 Sep 2023 11:38:56 +0530 Subject: [PATCH 1/6] Jetty-12: added instrumentation --- .../jetty-12/build.gradle | 28 +++ .../jetty12/server/HttpServletHelper.java | 189 ++++++++++++++++++ .../RequestHandler_Instrumentation.java | 49 +++++ settings.gradle | 4 +- 4 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 instrumentation-security/jetty-12/build.gradle create mode 100644 instrumentation-security/jetty-12/src/main/java/com/nr/instrumentation/security/jetty12/server/HttpServletHelper.java create mode 100644 instrumentation-security/jetty-12/src/main/java/com/nr/instrumentation/security/jetty12/server/RequestHandler_Instrumentation.java 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/nr/instrumentation/security/jetty12/server/HttpServletHelper.java b/instrumentation-security/jetty-12/src/main/java/com/nr/instrumentation/security/jetty12/server/HttpServletHelper.java new file mode 100644 index 000000000..f65812f8f --- /dev/null +++ b/instrumentation-security/jetty-12/src/main/java/com/nr/instrumentation/security/jetty12/server/HttpServletHelper.java @@ -0,0 +1,189 @@ +package com.nr.instrumentation.security.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.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.isSecure()?"https":"http"); + + // 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)); + + securityAgentMetaData.setServiceTrace(Thread.currentThread().getStackTrace()); + 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/nr/instrumentation/security/jetty12/server/RequestHandler_Instrumentation.java b/instrumentation-security/jetty-12/src/main/java/com/nr/instrumentation/security/jetty12/server/RequestHandler_Instrumentation.java new file mode 100644 index 000000000..72f00872f --- /dev/null +++ b/instrumentation-security/jetty-12/src/main/java/com/nr/instrumentation/security/jetty12/server/RequestHandler_Instrumentation.java @@ -0,0 +1,49 @@ +package com.nr.instrumentation.security.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.WeaveAllConstructors; +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 = acquireServletLockIfPossible(); + if (isServletLockAcquired) { + HttpServletHelper.preprocessSecurityHook(request); + } + boolean result; + try { + result = Weaver.callOriginal(); + } finally { + if (isServletLockAcquired) { + releaseServletLock(); + } + } + if (isServletLockAcquired) { + HttpServletHelper.postProcessSecurityHook(request, response, this.getClass().getName(), HttpServletHelper.SERVICE_METHOD_NAME); + } + return result; + } + + private boolean acquireServletLockIfPossible() { + try { + return HttpServletHelper.acquireServletLockIfPossible(); + } catch (Throwable ignored) { + } + return false; + } + + private void releaseServletLock() { + try { + HttpServletHelper.releaseServletLock(); + } catch (Throwable e) { + } + } +} diff --git a/settings.gradle b/settings.gradle index 1837947c2..b88ba777c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -149,4 +149,6 @@ include 'instrumentation:commons-jxpath' //include 'instrumentation:spring-webmvc-5.3.0' //include 'instrumentation:apache-wicket-6.4' //include 'instrumentation:apache-wicket-7.0' -//include 'instrumentation:apache-wicket-8.0' \ No newline at end of file +//include 'instrumentation:apache-wicket-8.0' +include 'instrumentation:jetty-12' + From 461793fbdba98bbc74f46fca555e007e63fb104f Mon Sep 17 00:00:00 2001 From: Monu Lakshkar Date: Mon, 11 Sep 2023 11:39:27 +0530 Subject: [PATCH 2/6] Jetty-12: added unit test cases --- .../security/jetty12/test/MyServlet.java | 22 ++ .../security/jetty12/test/ServerTest.java | 339 ++++++++++++++++++ 2 files changed, 361 insertions(+) create mode 100644 instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/MyServlet.java create mode 100644 instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/ServerTest.java diff --git a/instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/MyServlet.java b/instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/MyServlet.java new file mode 100644 index 000000000..18b298794 --- /dev/null +++ b/instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/MyServlet.java @@ -0,0 +1,22 @@ +package com.nr.instrumentation.security.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/instrumentation/security/jetty12/test/ServerTest.java b/instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/ServerTest.java new file mode 100644 index 000000000..b8ce866d8 --- /dev/null +++ b/instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/ServerTest.java @@ -0,0 +1,339 @@ +package com.nr.instrumentation.security.jetty12.test; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.nr.instrumentation.security.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.server.handler.ContextHandler; +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.nr.instrumentation.security.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); + System.out.println(new ObjectMapper().writeValueAsString(operations)); + 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()); + } + + @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(); + 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 "); + } + } +} From 911143f03bf9de8078d78cce42a5d6369fb75ebc Mon Sep 17 00:00:00 2001 From: Monu Lakshkar Date: Thu, 21 Sep 2023 15:00:38 +0530 Subject: [PATCH 3/6] Renamed package --- .../instrumentation}/jetty12/server/HttpServletHelper.java | 2 +- .../jetty12/server/RequestHandler_Instrumentation.java | 3 +-- .../security/instrumentation}/jetty12/test/MyServlet.java | 2 +- .../security/instrumentation}/jetty12/test/ServerTest.java | 7 +++---- 4 files changed, 6 insertions(+), 8 deletions(-) rename instrumentation-security/jetty-12/src/main/java/com/{nr/instrumentation/security => newrelic/agent/security/instrumentation}/jetty12/server/HttpServletHelper.java (99%) rename instrumentation-security/jetty-12/src/main/java/com/{nr/instrumentation/security => newrelic/agent/security/instrumentation}/jetty12/server/RequestHandler_Instrumentation.java (93%) rename instrumentation-security/jetty-12/src/test/java/com/{nr/instrumentation/security => newrelic/agent/security/instrumentation}/jetty12/test/MyServlet.java (91%) rename instrumentation-security/jetty-12/src/test/java/com/{nr/instrumentation/security => newrelic/agent/security/instrumentation}/jetty12/test/ServerTest.java (98%) diff --git a/instrumentation-security/jetty-12/src/main/java/com/nr/instrumentation/security/jetty12/server/HttpServletHelper.java b/instrumentation-security/jetty-12/src/main/java/com/newrelic/agent/security/instrumentation/jetty12/server/HttpServletHelper.java similarity index 99% rename from instrumentation-security/jetty-12/src/main/java/com/nr/instrumentation/security/jetty12/server/HttpServletHelper.java rename to instrumentation-security/jetty-12/src/main/java/com/newrelic/agent/security/instrumentation/jetty12/server/HttpServletHelper.java index f65812f8f..3eb74e92a 100644 --- a/instrumentation-security/jetty-12/src/main/java/com/nr/instrumentation/security/jetty12/server/HttpServletHelper.java +++ b/instrumentation-security/jetty-12/src/main/java/com/newrelic/agent/security/instrumentation/jetty12/server/HttpServletHelper.java @@ -1,4 +1,4 @@ -package com.nr.instrumentation.security.jetty12.server; +package com.newrelic.agent.security.instrumentation.jetty12.server; import com.newrelic.api.agent.security.NewRelicSecurity; import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; diff --git a/instrumentation-security/jetty-12/src/main/java/com/nr/instrumentation/security/jetty12/server/RequestHandler_Instrumentation.java b/instrumentation-security/jetty-12/src/main/java/com/newrelic/agent/security/instrumentation/jetty12/server/RequestHandler_Instrumentation.java similarity index 93% rename from instrumentation-security/jetty-12/src/main/java/com/nr/instrumentation/security/jetty12/server/RequestHandler_Instrumentation.java rename to instrumentation-security/jetty-12/src/main/java/com/newrelic/agent/security/instrumentation/jetty12/server/RequestHandler_Instrumentation.java index 72f00872f..555e13dff 100644 --- a/instrumentation-security/jetty-12/src/main/java/com/nr/instrumentation/security/jetty12/server/RequestHandler_Instrumentation.java +++ b/instrumentation-security/jetty-12/src/main/java/com/newrelic/agent/security/instrumentation/jetty12/server/RequestHandler_Instrumentation.java @@ -1,9 +1,8 @@ -package com.nr.instrumentation.security.jetty12.server; +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.WeaveAllConstructors; import com.newrelic.api.agent.weaver.Weaver; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; diff --git a/instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/MyServlet.java b/instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/MyServlet.java similarity index 91% rename from instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/MyServlet.java rename to instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/MyServlet.java index 18b298794..8d0b8c7d2 100644 --- a/instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/MyServlet.java +++ b/instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/MyServlet.java @@ -1,4 +1,4 @@ -package com.nr.instrumentation.security.jetty12.test; +package com.newrelic.agent.security.instrumentation.jetty12.test; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; diff --git a/instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/ServerTest.java b/instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/ServerTest.java similarity index 98% rename from instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/ServerTest.java rename to instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/ServerTest.java index b8ce866d8..cd8a9141c 100644 --- a/instrumentation-security/jetty-12/src/test/java/com/nr/instrumentation/security/jetty12/test/ServerTest.java +++ b/instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/ServerTest.java @@ -1,4 +1,4 @@ -package com.nr.instrumentation.security.jetty12.test; +package com.newrelic.agent.security.instrumentation.jetty12.test; import com.fasterxml.jackson.databind.ObjectMapper; import com.newrelic.agent.security.introspec.InstrumentationTestConfig; @@ -13,12 +13,11 @@ import com.newrelic.security.test.marker.Java11IncompatibleTest; import com.newrelic.security.test.marker.Java8IncompatibleTest; import com.newrelic.security.test.marker.Java9IncompatibleTest; -import com.nr.instrumentation.security.jetty12.server.HttpServletHelper; +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.server.handler.ContextHandler; import org.eclipse.jetty.util.Callback; import org.junit.After; import org.junit.Assert; @@ -39,7 +38,7 @@ @Category({ Java8IncompatibleTest.class, Java9IncompatibleTest.class, Java11IncompatibleTest.class }) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RunWith(SecurityInstrumentationTestRunner.class) -@InstrumentationTestConfig(includePrefixes = {"com.nr.instrumentation.security.jetty12.server"}) +@InstrumentationTestConfig(includePrefixes = {"com.newrelic.agent.security.instrumentation.jetty12.server"}) public class ServerTest { public static int PORT = 0; public static String ENDPOINT = "http://localhost:%d/"; From 44f489a5a370a0f46245402f3a0774bcd7688956 Mon Sep 17 00:00:00 2001 From: idawda Date: Mon, 25 Sep 2023 14:43:01 +0530 Subject: [PATCH 4/6] Fix the issue of failing few UTs due to delay in response --- .../instrumentation/jetty12/test/ServerTest.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/ServerTest.java b/instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/ServerTest.java index cd8a9141c..ac11f2a3b 100644 --- a/instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/ServerTest.java +++ b/instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/ServerTest.java @@ -1,6 +1,5 @@ package com.newrelic.agent.security.instrumentation.jetty12.test; -import com.fasterxml.jackson.databind.ObjectMapper; import com.newrelic.agent.security.introspec.InstrumentationTestConfig; import com.newrelic.agent.security.introspec.SecurityInstrumentationTestRunner; import com.newrelic.agent.security.introspec.SecurityIntrospector; @@ -65,7 +64,7 @@ public void testHandle() throws Exception { List operations = introspector.getOperations(); Assert.assertTrue("No operations detected", operations.size() > 0); - System.out.println(new ObjectMapper().writeValueAsString(operations)); + RXSSOperation operation = (RXSSOperation) operations.get(0); Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType()); @@ -310,6 +309,7 @@ private void serviceWithoutHeaders() throws Exception { conn.connect(); System.out.println(conn.getResponseCode()); + waitForProcessing(); } @Trace(dispatcher = true) @@ -325,6 +325,7 @@ private String serviceWithHeaders() throws Exception { conn.connect(); conn.getResponseCode(); + waitForProcessing(); return headerValue; } @@ -335,4 +336,12 @@ private static int getRandomPort() { throw new RuntimeException("Unable to allocate ephemeral port "); } } + + private static void waitForProcessing() { + try { + Thread.sleep(50); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } } From dc96a4d96dcb06e385100938df686cf1f0610434 Mon Sep 17 00:00:00 2001 From: Monu Lakshkar Date: Mon, 25 Sep 2023 17:40:13 +0530 Subject: [PATCH 5/6] Renamed unit test package --- .../agent/security/instrumentation/jetty12/test/MyServlet.java | 2 +- .../agent/security/instrumentation/jetty12/test/ServerTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename instrumentation-security/jetty-12/src/test/java/com/{newrelic => nr}/agent/security/instrumentation/jetty12/test/MyServlet.java (91%) rename instrumentation-security/jetty-12/src/test/java/com/{newrelic => nr}/agent/security/instrumentation/jetty12/test/ServerTest.java (99%) diff --git a/instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/MyServlet.java b/instrumentation-security/jetty-12/src/test/java/com/nr/agent/security/instrumentation/jetty12/test/MyServlet.java similarity index 91% rename from instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/MyServlet.java rename to instrumentation-security/jetty-12/src/test/java/com/nr/agent/security/instrumentation/jetty12/test/MyServlet.java index 8d0b8c7d2..e5c4af3a1 100644 --- a/instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/MyServlet.java +++ b/instrumentation-security/jetty-12/src/test/java/com/nr/agent/security/instrumentation/jetty12/test/MyServlet.java @@ -1,4 +1,4 @@ -package com.newrelic.agent.security.instrumentation.jetty12.test; +package com.nr.agent.security.instrumentation.jetty12.test; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; diff --git a/instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/ServerTest.java b/instrumentation-security/jetty-12/src/test/java/com/nr/agent/security/instrumentation/jetty12/test/ServerTest.java similarity index 99% rename from instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/ServerTest.java rename to instrumentation-security/jetty-12/src/test/java/com/nr/agent/security/instrumentation/jetty12/test/ServerTest.java index ac11f2a3b..71bcdb51c 100644 --- a/instrumentation-security/jetty-12/src/test/java/com/newrelic/agent/security/instrumentation/jetty12/test/ServerTest.java +++ b/instrumentation-security/jetty-12/src/test/java/com/nr/agent/security/instrumentation/jetty12/test/ServerTest.java @@ -1,4 +1,4 @@ -package com.newrelic.agent.security.instrumentation.jetty12.test; +package com.nr.agent.security.instrumentation.jetty12.test; import com.newrelic.agent.security.introspec.InstrumentationTestConfig; import com.newrelic.agent.security.introspec.SecurityInstrumentationTestRunner; From 801c228d3b5c959c99944f459abe409ae9faa387 Mon Sep 17 00:00:00 2001 From: Monu Lakshkar Date: Tue, 21 Nov 2023 15:55:00 +0530 Subject: [PATCH 6/6] jetty-12: request protocol extraction and minor changes --- .../jetty12/server/HttpServletHelper.java | 6 ++++-- .../RequestHandler_Instrumentation.java | 19 ++----------------- 2 files changed, 6 insertions(+), 19 deletions(-) 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 index 3eb74e92a..71ea403d1 100644 --- 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 @@ -15,6 +15,7 @@ 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; @@ -150,7 +151,7 @@ public static void preprocessSecurityHook(Request request) { securityMetaData.setTracingHeaderValue(HttpServletHelper.getTraceHeader(securityRequest.getHeaders())); - securityRequest.setProtocol(request.isSecure()?"https":"http"); + securityRequest.setProtocol(request.getHttpURI().getScheme()); // TODO: Create OutBoundHttp data here : Skipping for now. @@ -160,7 +161,8 @@ public static void preprocessSecurityHook(Request request) { } securityRequest.setContentType(request.getHeaders().get(HttpHeader.CONTENT_TYPE)); - securityAgentMetaData.setServiceTrace(Thread.currentThread().getStackTrace()); + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 2, trace.length)); securityRequest.setRequestParsed(true); } catch (Throwable ignored) { } 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 index 555e13dff..20e5016e3 100644 --- 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 @@ -13,7 +13,7 @@ public class RequestHandler_Instrumentation { public boolean handle(Request request, Response response, Callback callback) { ServletHelper.registerUserLevelCode("jetty-handle"); - boolean isServletLockAcquired = acquireServletLockIfPossible(); + boolean isServletLockAcquired = HttpServletHelper.acquireServletLockIfPossible(); if (isServletLockAcquired) { HttpServletHelper.preprocessSecurityHook(request); } @@ -22,7 +22,7 @@ public boolean handle(Request request, Response response, Callback callback) { result = Weaver.callOriginal(); } finally { if (isServletLockAcquired) { - releaseServletLock(); + HttpServletHelper.releaseServletLock(); } } if (isServletLockAcquired) { @@ -30,19 +30,4 @@ public boolean handle(Request request, Response response, Callback callback) { } return result; } - - private boolean acquireServletLockIfPossible() { - try { - return HttpServletHelper.acquireServletLockIfPossible(); - } catch (Throwable ignored) { - } - return false; - } - - private void releaseServletLock() { - try { - HttpServletHelper.releaseServletLock(); - } catch (Throwable e) { - } - } }