diff --git a/Changelog.md b/Changelog.md index 2f50c7c7f..14e7ceda6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,7 +4,7 @@ Noteworthy changes to the agent are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.7.0] - TBD +## [1.7.0] - 2025-4-25 ### Adds - [PR-395](https://github.com/newrelic/csec-java-agent/pull/395) **Support for Deserialization Vulnerability Detection**: Implemented mechanisms to detect vulnerabilities arising from unsafe deserialization processes. - [PR-395](https://github.com/newrelic/csec-java-agent/pull/395) **Support for Vulnerability Detection of Remote Code Invocation via Reflection**: Enhanced capability to identify security risks associated with remote code execution through reflection. @@ -16,8 +16,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [PR-403](https://github.com/newrelic/csec-java-agent/pull/403) GraphQL Supported Version Range: Restricted the supported version range for GraphQL due to the release of a new version on April 7th, 2025 ### Fixes -- [PR-372](https://github.com/newrelic/csec-java-agent/pull/372) **Repeat IAST Request Relay Commands**: Reconfigured logic to repeat IAST control commands until the endpoint is confirmed. +- [PR-372](https://github.com/newrelic/csec-java-agent/pull/372) **Repeat IAST Request Replay Commands**: Reconfigured logic to repeat IAST control commands until the endpoint is confirmed. +### Note +- The instrumentation for the module `com.newrelic.instrumentation.security.java-reflection` is disabled by default. This is due to its impact on CPU utilization, which can significantly increase when the module is active. +- **Action Required**: To detect unsafe reflection vulnerabilities effectively, enable the `com.newrelic.instrumentation.security.java-reflection` module. ## [1.6.1] - 2025-3-1 ### Adds diff --git a/gradle.properties b/gradle.properties index 8be8f55a6..1fe2c6d50 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # The agent version. -agentVersion=1.6.1 +agentVersion=1.7.0 jsonVersion=1.2.11 # Updated exposed NR APM API version. nrAPIVersion=8.12.0 diff --git a/instrumentation-security/java-reflection/build.gradle b/instrumentation-security/java-reflection/build.gradle index d41d6b414..c62f5cc26 100644 --- a/instrumentation-security/java-reflection/build.gradle +++ b/instrumentation-security/java-reflection/build.gradle @@ -8,7 +8,7 @@ dependencies { jar { - manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.java-reflection' } + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.java-reflection', 'Enabled': 'false' } } verifyInstrumentation { diff --git a/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java b/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java index ff327152a..4f4263940 100644 --- a/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java +++ b/instrumentation-security/jersey-2.16/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java @@ -44,7 +44,7 @@ public abstract class ContainerResponse_Instrumentation { public void close() { boolean isLockAcquired = false; try { - isLockAcquired = GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.REFLECTED_XSS, SERVLET_GET_IS_OPERATION_LOCK); + isLockAcquired = GenericHelper.acquireLockIfPossible(SERVLET_GET_IS_OPERATION_LOCK); if(isLockAcquired && GenericHelper.isLockAcquired(HttpRequestHelper.getNrSecCustomAttribForPostProcessing())) { HttpRequestHelper.postProcessSecurityHook(this.getClass().getName(), getWrappedMessageContext()); } diff --git a/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java b/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java index b31cdb629..a39c776f1 100644 --- a/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java +++ b/instrumentation-security/jersey-2/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java @@ -43,7 +43,7 @@ public abstract class ContainerResponse_Instrumentation { public void close() { boolean isLockAcquired = false; try { - isLockAcquired = GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.REFLECTED_XSS, SERVLET_GET_IS_OPERATION_LOCK); + isLockAcquired = GenericHelper.acquireLockIfPossible(SERVLET_GET_IS_OPERATION_LOCK); if(isLockAcquired && GenericHelper.isLockAcquired(HttpRequestHelper.getNrSecCustomAttribForPostProcessing())) { HttpRequestHelper.postProcessSecurityHook(this.getClass().getName(), getWrappedMessageContext()); } diff --git a/instrumentation-security/jersey-3/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java b/instrumentation-security/jersey-3/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java index b31cdb629..a39c776f1 100644 --- a/instrumentation-security/jersey-3/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java +++ b/instrumentation-security/jersey-3/src/main/java/com/newrelic/agent/security/instrumentation/jersey2/ContainerResponse_Instrumentation.java @@ -43,7 +43,7 @@ public abstract class ContainerResponse_Instrumentation { public void close() { boolean isLockAcquired = false; try { - isLockAcquired = GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.REFLECTED_XSS, SERVLET_GET_IS_OPERATION_LOCK); + isLockAcquired = GenericHelper.acquireLockIfPossible(SERVLET_GET_IS_OPERATION_LOCK); if(isLockAcquired && GenericHelper.isLockAcquired(HttpRequestHelper.getNrSecCustomAttribForPostProcessing())) { HttpRequestHelper.postProcessSecurityHook(this.getClass().getName(), getWrappedMessageContext()); } diff --git a/instrumentation-security/mule-3.6/src/main/java/org/mule/module/http/internal/domain/response/HttpResponseBuilder_Instrumentation.java b/instrumentation-security/mule-3.6/src/main/java/org/mule/module/http/internal/domain/response/HttpResponseBuilder_Instrumentation.java index 2bd4cb9e0..d82f70731 100644 --- a/instrumentation-security/mule-3.6/src/main/java/org/mule/module/http/internal/domain/response/HttpResponseBuilder_Instrumentation.java +++ b/instrumentation-security/mule-3.6/src/main/java/org/mule/module/http/internal/domain/response/HttpResponseBuilder_Instrumentation.java @@ -19,8 +19,8 @@ @Weave(type = MatchType.ExactClass, originalName = "org.mule.module.http.internal.domain.response.HttpResponseBuilder") public class HttpResponseBuilder_Instrumentation { - private final ResponseStatus responseStatus = Weaver.callOriginal(); - private final HttpEntity body = Weaver.callOriginal(); + private ResponseStatus responseStatus = Weaver.callOriginal(); + private HttpEntity body = Weaver.callOriginal(); public HttpResponse build() { HttpResponse response = Weaver.callOriginal(); diff --git a/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/domain/response/HttpResponseBuilder_Instrumentation.java b/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/domain/response/HttpResponseBuilder_Instrumentation.java index ef15c71b3..b660d0e80 100644 --- a/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/domain/response/HttpResponseBuilder_Instrumentation.java +++ b/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/domain/response/HttpResponseBuilder_Instrumentation.java @@ -19,8 +19,8 @@ @Weave(type = MatchType.ExactClass, originalName = "org.mule.module.http.internal.domain.response.HttpResponseBuilder") public class HttpResponseBuilder_Instrumentation { - private final ResponseStatus responseStatus = Weaver.callOriginal(); - private final HttpEntity body = Weaver.callOriginal(); + private ResponseStatus responseStatus = Weaver.callOriginal(); + private HttpEntity body = Weaver.callOriginal(); public HttpResponse build() { HttpResponse response = Weaver.callOriginal(); diff --git a/instrumentation-security/servlet-2.4/src/main/java/javax/servlet/http/HttpServletResponse_Instrumentation.java b/instrumentation-security/servlet-2.4/src/main/java/javax/servlet/http/HttpServletResponse_Instrumentation.java index 3537ce958..931617e8e 100644 --- a/instrumentation-security/servlet-2.4/src/main/java/javax/servlet/http/HttpServletResponse_Instrumentation.java +++ b/instrumentation-security/servlet-2.4/src/main/java/javax/servlet/http/HttpServletResponse_Instrumentation.java @@ -37,6 +37,9 @@ public void addCookie(Cookie cookie){ releaseLock(cookie.hashCode()); } } + if (isOwaspHookEnabled) { + registerExitOperation(isLockAcquired, operation); + } } private AbstractOperation preprocessSecurityHook(Cookie cookie, String className, String methodName) { @@ -78,6 +81,17 @@ private AbstractOperation preprocessSecurityHook(Cookie cookie, String className return null; } + private 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 e) { + NewRelicSecurity.getAgent().log(LogLevel.FINEST, String.format(GenericHelper.EXIT_OPERATION_EXCEPTION_MESSAGE, HttpServletHelper.SERVLET_2_4, e.getMessage()), e, HttpServletResponse_Instrumentation.class.getName()); + } + } + private void releaseLock(int hashCode) { GenericHelper.releaseLock(ServletHelper.NR_SEC_HTTP_SERVLET_RESPONSE_ATTRIB_NAME, hashCode); } diff --git a/instrumentation-security/servlet-2.4/src/test/java/com/nr/agent/security/instrumentation/servlet24/HttpSessionTest.java b/instrumentation-security/servlet-2.4/src/test/java/com/nr/agent/security/instrumentation/servlet24/HttpSessionTest.java index a5fe5e80d..b5f9a3840 100644 --- a/instrumentation-security/servlet-2.4/src/test/java/com/nr/agent/security/instrumentation/servlet24/HttpSessionTest.java +++ b/instrumentation-security/servlet-2.4/src/test/java/com/nr/agent/security/instrumentation/servlet24/HttpSessionTest.java @@ -35,7 +35,6 @@ public void testSessionSetAttribute() throws IOException, URISyntaxException { SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); List operations = introspector.getOperations(); Assert.assertTrue("No operations detected", operations.size() > 0); - Assert.assertTrue("Unexpected operation count detected", operations.size() == 2 || operations.size() == 3); TrustBoundaryOperation targetOperation = null; int i=0; for (AbstractOperation operation : operations) { @@ -65,7 +64,6 @@ public void testSessionPutValue() throws IOException, URISyntaxException { SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); List operations = introspector.getOperations(); Assert.assertFalse(operations.isEmpty()); - Assert.assertTrue("Unexpected operation count detected", operations.size() == 2 || operations.size() == 3); TrustBoundaryOperation targetOperation = null; for (AbstractOperation operation : operations) { if (operation instanceof TrustBoundaryOperation) @@ -86,7 +84,6 @@ public void testAddCookie() throws IOException, URISyntaxException { SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); List operations = introspector.getOperations(); Assert.assertTrue("No operations detected", operations.size() > 0); - Assert.assertTrue("Unexpected operation count detected", operations.size() == 1 || operations.size() == 2); SecureCookieOperationSet targetOperation = null; targetOperation = verifySecureCookieOp(operations); diff --git a/newrelic-security-agent/build.gradle b/newrelic-security-agent/build.gradle index ddbfe0edc..f96d9bf14 100644 --- a/newrelic-security-agent/build.gradle +++ b/newrelic-security-agent/build.gradle @@ -203,6 +203,9 @@ tasks.register('generate-sbom') { def parsedJson = new JsonSlurper().parseText(req.getInputStream().getText()) try { + if (!project.buildDir.exists()) { + mkdir project.buildDir; + } def reportsDir = Paths.get("$buildDir", "reports") def sbomFile = new File("$buildDir/reports", "SBOM.json") if (Files.exists(reportsDir) || Files.createDirectory(reportsDir)) { diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/AgentConfig.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/AgentConfig.java index 547d3627e..9ab68002f 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/AgentConfig.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/AgentConfig.java @@ -318,7 +318,7 @@ private String applyRequiredLogLevel() { if(value instanceof Boolean) { logLevel = IUtilConstants.OFF; } else { - logLevel = NewRelic.getAgent().getConfig().getValue(IUtilConstants.NR_LOG_LEVEL, LogLevel.FINEST.name()); + logLevel = NewRelic.getAgent().getConfig().getValue(IUtilConstants.NR_LOG_LEVEL, IUtilConstants.INFO); } try { diff --git a/newrelic-security-api-test-impl/src/main/java/com/newrelic/api/agent/security/Agent.java b/newrelic-security-api-test-impl/src/main/java/com/newrelic/api/agent/security/Agent.java index 1c46b4f66..edfccade7 100644 --- a/newrelic-security-api-test-impl/src/main/java/com/newrelic/api/agent/security/Agent.java +++ b/newrelic-security-api-test-impl/src/main/java/com/newrelic/api/agent/security/Agent.java @@ -8,6 +8,7 @@ import com.newrelic.api.agent.security.schema.ServerConnectionConfiguration; import com.newrelic.api.agent.security.schema.operation.FileIntegrityOperation; import com.newrelic.api.agent.security.schema.operation.FileOperation; +import com.newrelic.api.agent.security.schema.operation.SecureCookieOperationSet; import com.newrelic.api.agent.security.schema.policy.AgentPolicy; import com.newrelic.api.agent.security.schema.policy.IastDetectionCategory; import com.newrelic.api.agent.security.utils.logging.LogLevel; @@ -85,6 +86,8 @@ public void registerOperation(AbstractOperation operation) { return; } operation.setApiID(apiId); + String executionId = "dummy-exec-id"; + operation.setExecutionId(executionId); operation.setStartTime(Instant.now().toEpochMilli()); StackTraceElement[] trace = Thread.currentThread().getStackTrace(); operation.setStackTrace(Arrays.copyOfRange(trace, 1, trace.length)); @@ -93,6 +96,9 @@ public void registerOperation(AbstractOperation operation) { @Override public void registerExitEvent(AbstractOperation operation) { + if (operation instanceof SecureCookieOperationSet) { + this.getSecurityMetaData().getCustomAttribute(OPERATIONS, List.class).add(operation); + } this.getSecurityMetaData().getCustomAttribute(EXIT_OPERATIONS, List.class).add(operation); }