Skip to content

Commit

Permalink
Merge pull request #297 from newrelic/support/httpserver-response-NR-…
Browse files Browse the repository at this point in the history
…277771

[NR-277771] Response interception & Route Detection in sun-net-httpserver
  • Loading branch information
IshikaDawda authored Nov 5, 2024
2 parents 2cca122 + 6bf93be commit eb782d3
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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.HttpResponse;
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;
Expand All @@ -25,6 +26,7 @@ public void doFilter (HttpExchange exchange, Filter.Chain chain) throws IOExcept
preprocessSecurityHook(exchange);
}
ServletHelper.registerUserLevelCode(HttpServerHelper.SUN_NET_HTTP_SERVER);
HttpServerHelper.detectRoute();
try{
Weaver.callOriginal();
} finally {
Expand Down Expand Up @@ -64,17 +66,13 @@ private void preprocessSecurityHook(HttpExchange exchange) {
securityRequest.setProtocol(HttpServerHelper.getProtocol(exchange));
securityRequest.setUrl(String.valueOf(exchange.getRequestURI()));

String queryString = exchange.getRequestURI().getQuery();
if (queryString != null && !queryString.trim().isEmpty()) {
securityRequest.setUrl(securityRequest.getUrl() + HttpServerHelper.QUESTION_MARK + queryString);
}

securityRequest.setContentType(HttpServerHelper.getContentType(exchange.getRequestHeaders()));
securityRequest.setContentType(HttpServerHelper.getContentType(securityRequest.getHeaders()));
securityRequest.setRequestParsed(true);
} catch (Throwable e){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HttpServerHelper.SUN_NET_HTTPSERVER, e.getMessage()), e, this.getClass().getName());
}
}

private void postProcessSecurityHook(HttpExchange exchange) {
try {
if(NewRelicSecurity.getAgent().getIastDetectionCategory().getRxssEnabled()){
Expand All @@ -83,7 +81,11 @@ private void postProcessSecurityHook(HttpExchange exchange) {
if (!NewRelicSecurity.isHookProcessingActive()) {
return;
}
NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseCode(exchange.getResponseCode());
HttpResponse securityResponse = NewRelicSecurity.getAgent().getSecurityMetaData().getResponse();
securityResponse.setResponseCode(exchange.getResponseCode());
HttpServerHelper.processHttpResponseHeaders(exchange.getResponseHeaders(), securityResponse);
securityResponse.setResponseContentType(HttpServerHelper.getContentType(securityResponse.getHeaders()));

ServletHelper.executeBeforeExitingTransaction();
//Add request URI hash to low severity event filter
LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import java.io.InputStream;

import static com.sun.net.httpserver.HttpServerHelper.SUN_NET_READER_OPERATION_LOCK;
import java.io.OutputStream;

@Weave(type = MatchType.BaseClass, originalName = "com.sun.net.httpserver.HttpExchange")
public class HttpExchange_Instrumentation {
Expand All @@ -16,14 +15,31 @@ public InputStream getRequestBody () {
boolean isLockAcquired = false;
InputStream stream;
try {
isLockAcquired = GenericHelper.acquireLockIfPossible(SUN_NET_READER_OPERATION_LOCK);
isLockAcquired = GenericHelper.acquireLockIfPossible(HttpServerHelper.SUN_NET_READER_OPERATION_LOCK);
stream = Weaver.callOriginal();
if (isLockAcquired && NewRelicSecurity.isHookProcessingActive() && stream != null) {
HttpServerHelper.registerInputStreamHashIfNeeded(stream.hashCode());
}
} finally {
if(isLockAcquired) {
GenericHelper.releaseLock(SUN_NET_READER_OPERATION_LOCK);
GenericHelper.releaseLock(HttpServerHelper.SUN_NET_READER_OPERATION_LOCK);
}
}
return stream;
}

public OutputStream getResponseBody () {
boolean isLockAcquired = false;
OutputStream stream;
try {
isLockAcquired = GenericHelper.acquireLockIfPossible(HttpServerHelper.SUN_NET_WRITER_OPERATION_LOCK);
stream = Weaver.callOriginal();
if (isLockAcquired && NewRelicSecurity.isHookProcessingActive() && stream != null) {
HttpServerHelper.registerOutputStreamHashIfNeeded(stream.hashCode());
}
} finally {
if(isLockAcquired) {
GenericHelper.releaseLock(HttpServerHelper.SUN_NET_WRITER_OPERATION_LOCK);
}
}
return stream;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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.HttpResponse;
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;
Expand All @@ -25,6 +26,7 @@ public void handle (HttpExchange exchange) throws IOException {
preprocessSecurityHook(exchange);
}
ServletHelper.registerUserLevelCode(HttpServerHelper.SUN_NET_HTTP_SERVER);
HttpServerHelper.detectRoute();
try{
Weaver.callOriginal();
} finally {
Expand Down Expand Up @@ -64,12 +66,7 @@ private void preprocessSecurityHook(HttpExchange exchange) {
securityRequest.setProtocol(HttpServerHelper.getProtocol(exchange));
securityRequest.setUrl(String.valueOf(exchange.getRequestURI()));

String queryString = exchange.getRequestURI().getQuery();
if (queryString != null && !queryString.trim().isEmpty()) {
securityRequest.setUrl(securityRequest.getUrl() + HttpServerHelper.QUESTION_MARK + queryString);
}

securityRequest.setContentType(HttpServerHelper.getContentType(exchange.getRequestHeaders()));
securityRequest.setContentType(HttpServerHelper.getContentType(securityRequest.getHeaders()));
securityRequest.setRequestParsed(true);
} catch (Throwable e){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HttpServerHelper.SUN_NET_HTTPSERVER, e.getMessage()), e, this.getClass().getName());
Expand All @@ -83,7 +80,11 @@ private void postProcessSecurityHook(HttpExchange exchange) {
if (!NewRelicSecurity.isHookProcessingActive()) {
return;
}
NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseCode(exchange.getResponseCode());
HttpResponse securityResponse = NewRelicSecurity.getAgent().getSecurityMetaData().getResponse();
securityResponse.setResponseCode(exchange.getResponseCode());
HttpServerHelper.processHttpResponseHeaders(exchange.getResponseHeaders(), securityResponse);
securityResponse.setResponseContentType(HttpServerHelper.getContentType(securityResponse.getHeaders()));

ServletHelper.executeBeforeExitingTransaction();
//Add request URI hash to low severity event filter
LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
import com.newrelic.api.agent.security.instrumentation.helpers.ICsecApiConstants;
import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper;
import com.newrelic.api.agent.security.schema.AgentMetaData;
import com.newrelic.api.agent.security.schema.Framework;
import com.newrelic.api.agent.security.schema.HttpRequest;
import com.newrelic.api.agent.security.schema.HttpResponse;
import com.newrelic.api.agent.security.schema.StringUtils;
import com.newrelic.api.agent.security.schema.policy.AgentPolicy;

import java.util.HashSet;
Expand All @@ -18,17 +21,29 @@ public class HttpServerHelper {
private static final String NR_SEC_CUSTOM_ATTRIB_NAME = "HTTPSERVER_LOCK-";
public static final String HANDLE_METHOD_NAME = "handle";
private static final String EMPTY = "";
private static final String CONTENT_TYPE = "Content-type";
private static final String CONTENT_TYPE = "content-type";
public static final String QUESTION_MARK = "?";
public static final String HTTP_PROTOCOL = "http";
public static final String HTTPS_PROTOCOL = "https";
private static final String REQUEST_INPUTSTREAM_HASH = "REQUEST_INPUTSTREAM_HASH";
private static final String RESPONSE_OUTPUTSTREAM_HASH = "RESPONSE_OUTPUTSTREAM_HASH";
public static final String SUN_NET_READER_OPERATION_LOCK = "SUN_NET_READER_OPERATION_LOCK-";
public static final String SUN_NET_WRITER_OPERATION_LOCK = "SUN_NET_WRIITER_OPERATION_LOCK-";
public static final String HTTP_METHOD = "*";
public static final String SUN_NET_HTTP_SERVER = "sun-net-http-server";
private static String route = StringUtils.EMPTY;


public static void setRoute(String route) {
if (StringUtils.isEmpty(HttpServerHelper.route)) {
HttpServerHelper.route = route;
}
}


public static void processHttpRequestHeaders(Headers headers, HttpRequest securityRequest){
for (String headerKey : headers.keySet()) {
String headerName = headerKey;
boolean takeNextValue = false;
if (headerKey != null) {
headerKey = headerKey.toLowerCase();
Expand All @@ -43,16 +58,16 @@ public static void processHttpRequestHeaders(Headers headers, HttpRequest securi
} 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(headers.getFirst(headerKey)));
.setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(headers.getFirst(headerName)));
} else if(GenericHelper.CSEC_PARENT_ID.equals(headerKey)) {
NewRelicSecurity.getAgent().getSecurityMetaData()
.addCustomAttribute(GenericHelper.CSEC_PARENT_ID, headers.getFirst(headerKey));
.addCustomAttribute(GenericHelper.CSEC_PARENT_ID, headers.getFirst(headerName));
} else if (ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST.equals(headerKey)) {
NewRelicSecurity.getAgent().getSecurityMetaData()
.addCustomAttribute(ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST, true);
}
String headerFullValue = EMPTY;
for (String headerValue : headers.get(headerKey)) {
for (String headerValue : headers.get(headerName)) {
if (headerValue != null && !headerValue.trim().isEmpty()) {
if (takeNextValue) {
agentMetaData.setClientDetectedFromXFF(true);
Expand All @@ -72,13 +87,23 @@ public static void processHttpRequestHeaders(Headers headers, HttpRequest securi
securityRequest.getHeaders().put(headerKey, headerFullValue);
}
}
public static String getContentType(Headers headers){

public static String getContentType(Map<String, String> headers){
String data = EMPTY;
if (headers.containsKey(CONTENT_TYPE)) {
data = headers.getFirst(CONTENT_TYPE);
data = headers.get(CONTENT_TYPE);
}
return data;
}

public static void detectRoute(){
if (NewRelicSecurity.isHookProcessingActive() && StringUtils.isEmpty(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().getRoute())) {
NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().setRoute(route);
route = StringUtils.EMPTY;
NewRelicSecurity.getAgent().getSecurityMetaData().getMetaData().setFramework(Framework.SUN_NET_HTTPSERVER);
}
}

public static String getTraceHeader(Map<String, String> headers) {
String data = EMPTY;
if (headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER) || headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase())) {
Expand All @@ -90,6 +115,17 @@ public static String getTraceHeader(Map<String, String> headers) {
return data;
}

public static void registerOutputStreamHashIfNeeded(int outputStreamHash){
try {
Set<Integer> hashSet = NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(RESPONSE_OUTPUTSTREAM_HASH, Set.class);
if (hashSet == null) {
hashSet = new HashSet<>();
NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(RESPONSE_OUTPUTSTREAM_HASH, hashSet);
}
hashSet.add(outputStreamHash);
} catch (Throwable ignored) {}
}

public static void registerInputStreamHashIfNeeded(int inputStreamHash){
try {
Set<Integer> hashSet = NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(REQUEST_INPUTSTREAM_HASH, Set.class);
Expand Down Expand Up @@ -134,4 +170,20 @@ public static String getProtocol(HttpExchange exchange){
}
return HTTP_PROTOCOL;
}

public static void processHttpResponseHeaders(Headers headers, HttpResponse securityRequest){
for (String headerKey : headers.keySet()) {
String headerFullValue = EMPTY;
for (String headerValue : headers.get(headerKey)) {
if (headerValue != null && !headerValue.trim().isEmpty()) {
if (headerFullValue.trim().isEmpty()) {
headerFullValue = headerValue;
} else {
headerFullValue = String.join(";", headerFullValue, headerValue);
}
}
}
securityRequest.getHeaders().put(headerKey.toLowerCase(), headerFullValue);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
package com.sun.net.httpserver;

import com.newrelic.api.agent.security.NewRelicSecurity;
import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper;
import com.newrelic.api.agent.security.instrumentation.helpers.URLMappingsHelper;
import com.newrelic.api.agent.security.schema.ApplicationURLMapping;
import com.newrelic.api.agent.security.utils.logging.LogLevel;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;


@Weave(originalName = "com.sun.net.httpserver.HttpServer", type = MatchType.BaseClass)
public class HttpServer_Instrumentation {

public HttpContext createContext (String path, HttpHandler handler){
HttpContext context;
HttpContext context = Weaver.callOriginal();
try {
context = Weaver.callOriginal();
} finally {
URLMappingsHelper.addApplicationURLMapping(new ApplicationURLMapping(HttpServerHelper.HTTP_METHOD, path, handler.getClass().getName()));
} catch (Exception e){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_WHILE_GETTING_APP_ENDPOINTS, HttpServerHelper.SUN_NET_HTTPSERVER, e.getMessage()), e, this.getClass().getName());
}
return context;
}

public void removeContext (String path) throws IllegalArgumentException {
Weaver.callOriginal();
try {
URLMappingsHelper.removeApplicationURLMapping(HttpServerHelper.HTTP_METHOD, path);
} catch (Exception e){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_WHILE_REMOVING_APP_ENDPOINTS, HttpServerHelper.SUN_NET_HTTPSERVER, e.getMessage()), e, this.getClass().getName());
}
}

public void removeContext (HttpContext context) {
Weaver.callOriginal();
try {
URLMappingsHelper.removeApplicationURLMapping(HttpServerHelper.HTTP_METHOD, context.getPath());
} catch (Exception e){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_WHILE_REMOVING_APP_ENDPOINTS, HttpServerHelper.SUN_NET_HTTPSERVER, e.getMessage()), e, this.getClass().getName());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package sun.net.httpserver;

import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import com.sun.net.httpserver.HttpServerHelper;

@Weave(originalName = "sun.net.httpserver.ContextList", type = MatchType.ExactClass)
class ContextList_Instrumentation {

synchronized HttpContextImpl findContext (String protocol, String path, boolean exact) {
HttpContextImpl result = Weaver.callOriginal();
if (result != null) {
HttpServerHelper.setRoute(result.getPath());
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class GenericHelper {
public static final String ERROR_GENERATING_HTTP_REQUEST = "Instrumentation library: %s , error while generating HTTP request : %s";
public static final String ERROR_PARSING_HTTP_REQUEST_DATA = "Instrumentation library: %s , error while parsing HTTP request data : %s";
public static final String ERROR_WHILE_GETTING_APP_ENDPOINTS = "Instrumentation library: %s , error while getting application API endpoints : %s";
public static final String ERROR_WHILE_REMOVING_APP_ENDPOINTS = "Instrumentation library: %s , error while removing application API endpoints : %s";
public static final String ERROR_PARSING_HTTP_RESPONSE = "Instrumentation library: %s , error while parsing HTTP Response data : %s";
public static final String ERROR_WHILE_DETECTING_USER_CLASS = "Instrumentation library: %s error while detecting user class";
public static final String ERROR_WHILE_GETTING_ROUTE_FOR_INCOMING_REQUEST = "Instrumentation library: %s , error while getting route for incoming request : %s";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,10 @@ public static int getSegmentCount(String path){
}
return i;
}

public static void removeApplicationURLMapping(String method, String path) {
if (!mappings.isEmpty()) {
mappings.remove(new ApplicationURLMapping(method, path));
}
}
}

0 comments on commit eb782d3

Please sign in to comment.