Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NR-277771] Response interception & Route Detection in sun-net-httpserver #297

Merged
merged 7 commits into from
Nov 5, 2024
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));
}
}
}
Loading