From 7bd7d3b92b94144b1b008562f9cc5990a7bfb3fc Mon Sep 17 00:00:00 2001 From: Anupam Juniwal Date: Wed, 6 Sep 2023 12:20:28 +0530 Subject: [PATCH 01/19] Initial changes for proof backed deserialization attack detection --- .../deserialisation/build.gradle | 16 ++ .../csec/validator/scanner/DeltaClass.java | 22 ++ .../src/main/java/java/io/SecurityHelper.java | 9 + .../java/io/Serializable_Instrumentation.java | 100 +++++++++ .../instrumentator/dispatcher/Dispatcher.java | 1 + .../security/schema/AbstractOperation.java | 28 ++- .../agent/security/schema/AgentMetaData.java | 11 + .../security/schema/DeserializationInfo.java | 207 ++++++++++++++++++ .../security/schema/SecurityMetaData.java | 29 +++ .../schema/VulnerabilityCaseType.java | 4 +- .../operation/DeserialisationOperation.java | 46 ++++ settings.gradle | 2 +- 12 files changed, 470 insertions(+), 5 deletions(-) create mode 100644 instrumentation-security/deserialisation/build.gradle create mode 100644 instrumentation-security/deserialisation/src/main/java/com/newrelic/csec/validator/scanner/DeltaClass.java create mode 100644 instrumentation-security/deserialisation/src/main/java/java/io/SecurityHelper.java create mode 100644 instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java create mode 100644 newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java create mode 100644 newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java diff --git a/instrumentation-security/deserialisation/build.gradle b/instrumentation-security/deserialisation/build.gradle new file mode 100644 index 000000000..40fb3a3e9 --- /dev/null +++ b/instrumentation-security/deserialisation/build.gradle @@ -0,0 +1,16 @@ +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") +} + +// This instrumentation module should not use the bootstrap classpath + + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.deserialisation' } +} + +verifyInstrumentation { + verifyClasspath = false // We don't want to verify classpath since these are JDK classes +} diff --git a/instrumentation-security/deserialisation/src/main/java/com/newrelic/csec/validator/scanner/DeltaClass.java b/instrumentation-security/deserialisation/src/main/java/com/newrelic/csec/validator/scanner/DeltaClass.java new file mode 100644 index 000000000..b578fde1e --- /dev/null +++ b/instrumentation-security/deserialisation/src/main/java/com/newrelic/csec/validator/scanner/DeltaClass.java @@ -0,0 +1,22 @@ +package com.newrelic.csec.validator.scanner; + +import java.io.Serializable; + +public class DeltaClass implements Serializable { + + private static final long serialVersionUID = 5220788560470736671L; + + public String field0; + + public DeltaClass(String field0) { + this.field0 = field0; + } + + public String getField0() { + return field0; + } + + public void setField0(String field0) { + this.field0 = field0; + } +} diff --git a/instrumentation-security/deserialisation/src/main/java/java/io/SecurityHelper.java b/instrumentation-security/deserialisation/src/main/java/java/io/SecurityHelper.java new file mode 100644 index 000000000..daed02acb --- /dev/null +++ b/instrumentation-security/deserialisation/src/main/java/java/io/SecurityHelper.java @@ -0,0 +1,9 @@ +package java.io; + +public class SecurityHelper { + public static final String METHOD_NAME_READ_OBJECT = "readObject"; + public static final String NULL_STRING = "null"; + + public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "UNSAFE-DESERIALISATION-LOCK-JAVA-IO-"; + +} \ No newline at end of file diff --git a/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java b/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java new file mode 100644 index 000000000..c160664f3 --- /dev/null +++ b/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java @@ -0,0 +1,100 @@ +package java.io; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.schema.DeserializationInfo; +import com.newrelic.api.agent.security.schema.operation.DeserialisationOperation; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + + +@Weave(type = MatchType.BaseClass, originalName = "java.io.ObjectStreamClass") +public abstract class Serializable_Instrumentation { + + void invokeReadObject(Object obj, ObjectInputStream in) + throws ClassNotFoundException, IOException, + UnsupportedOperationException { + if (NewRelicSecurity.isHookProcessingActive() && + !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { + try { + + NewRelicSecurity.getAgent().getSecurityMetaData().addToDeserializingObjectStack( + new DeserializationInfo(obj.getClass().getName(), obj) + ); + } catch (Exception e){ + e.printStackTrace(); + } + System.out.println("IN invokeReadObject"); + } + try { + System.out.println("CALLING original"); + Weaver.callOriginal(); + System.out.println("Will create DeserialisationOperation"); + DeserialisationOperation operation = new DeserialisationOperation( + this.getClass().getName(), + SecurityHelper.METHOD_NAME_READ_OBJECT + ); + System.out.println("Created DeserialisationOperation"); + NewRelicSecurity.getAgent().registerOperation(operation); + System.out.println("Registered DeserialisationOperation"); + + } finally { + if (NewRelicSecurity.isHookProcessingActive() && + !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { + NewRelicSecurity.getAgent().getSecurityMetaData().popFromDeserializingObjectStack(); + } + } +// registerExitOperation(isFileLockAcquired, operation); + } + + void invokeReadObjectNoData(Object obj) + throws IOException, UnsupportedOperationException{ + boolean isLockAcquired = acquireLockIfPossible(); + if (isLockAcquired && NewRelicSecurity.isHookProcessingActive() && + !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { + try { + + NewRelicSecurity.getAgent().getSecurityMetaData().addToDeserializingObjectStack( + new DeserializationInfo(obj.getClass().getName(), obj) + ); + } catch (Exception e){ + e.printStackTrace(); + } + } + try { + Weaver.callOriginal(); + + DeserialisationOperation operation = new DeserialisationOperation( + this.getClass().getName(), + SecurityHelper.METHOD_NAME_READ_OBJECT + ); + NewRelicSecurity.getAgent().registerOperation(operation); + + } finally { + if (isLockAcquired && NewRelicSecurity.isHookProcessingActive() && + !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { + NewRelicSecurity.getAgent().getSecurityMetaData().popFromDeserializingObjectStack(); + } + if (isLockAcquired){ + releaseLock(); + } + } +// registerExitOperation(isFileLockAcquired, operation); + } + + private void releaseLock() { + try { + GenericHelper.releaseLock(SecurityHelper.NR_SEC_CUSTOM_ATTRIB_NAME, this.hashCode()); + } catch (Throwable ignored) { + } + } + + private boolean acquireLockIfPossible() { + try { + return GenericHelper.acquireLockIfPossible(SecurityHelper.NR_SEC_CUSTOM_ATTRIB_NAME, this.hashCode()); + } catch (Throwable ignored) { + } + return false; + } +} \ No newline at end of file diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java index b1fc5202b..fd7e0b327 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java @@ -577,6 +577,7 @@ private JavaAgentEventBean setGenericProperties(AbstractOperation objectBean, Ja eventBean.setUserAPIInfo(operation.getUserClassEntity().getUserClassElement().getLineNumber(), operation.getUserClassEntity().getUserClassElement().getClassName(), operation.getUserClassEntity().getUserClassElement().getMethodName()); + eventBean.getMetaData().setDeserializationInfo(operation.getDeserializationInfo()); return eventBean; } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java index c7e1e3cbb..b0d3fe4c9 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java @@ -1,5 +1,7 @@ package com.newrelic.api.agent.security.schema; +import com.newrelic.api.agent.security.NewRelicSecurity; + public abstract class AbstractOperation { public static final String EMPTY = ""; @@ -25,6 +27,8 @@ public abstract class AbstractOperation { private boolean isLowSeverityHook; + private DeserializationInfo deserializationInfo; + public AbstractOperation() { this.className = EMPTY; this.sourceMethod = EMPTY; @@ -36,9 +40,19 @@ public AbstractOperation() { } public AbstractOperation(String className, String methodName){ - this.className = className; - this.methodName = methodName; - this.blockingEndTime = 0L; + this.className = className; + this.methodName = methodName; + this.blockingEndTime = 0L; + try { + SecurityMetaData meta = NewRelicSecurity.getAgent() + .getSecurityMetaData(); + if (meta != null && meta.peekDeserializingObjectStack() != null) { + this.deserializationInfo = meta.peekDeserializingObjectStack(); + this.deserializationInfo.computeAttributeFlatMap(); + } + }catch (Exception e){ + e.printStackTrace(); + } } public String getClassName() { @@ -135,4 +149,12 @@ public boolean isLowSeverityHook() { public void setLowSeverityHook(boolean lowSeverityHook) { this.isLowSeverityHook = lowSeverityHook; } + + public DeserializationInfo getDeserializationInfo() { + return deserializationInfo; + } + + public void setDeserializationInfo(DeserializationInfo deserializationInfo) { + this.deserializationInfo = deserializationInfo; + } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java index 110dc6fbe..2e31a0d59 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java @@ -38,6 +38,8 @@ public class AgentMetaData { @JsonIgnore private Set ips; + private DeserializationInfo deserializationInfo = new DeserializationInfo(); + public AgentMetaData() { this.rciMethodsCalls = new HashSet<>(); this.ips = new HashSet<>(); @@ -57,6 +59,7 @@ public AgentMetaData(AgentMetaData agentMetaData) { this.userDataTranslationMap = new HashMap<>(agentMetaData.userDataTranslationMap); this.userLevelServiceMethodEncountered = agentMetaData.userLevelServiceMethodEncountered; this.reflectedMetaData = agentMetaData.reflectedMetaData; + this.deserializationInfo = new DeserializationInfo(agentMetaData.deserializationInfo); } public boolean isTriggerViaRCI() { @@ -151,4 +154,12 @@ public boolean isUserLevelServiceMethodEncountered(String framework) { public void setUserLevelServiceMethodEncountered(boolean userLevelServiceMethodEncountered) { this.userLevelServiceMethodEncountered = userLevelServiceMethodEncountered; } + + public DeserializationInfo getDeserializationInfo() { + return deserializationInfo; + } + + public void setDeserializationInfo(DeserializationInfo deserializationInfo) { + this.deserializationInfo = new DeserializationInfo(deserializationInfo); + } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java new file mode 100644 index 000000000..b6f8e4fff --- /dev/null +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java @@ -0,0 +1,207 @@ +package com.newrelic.api.agent.security.schema; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.util.*; + +public class DeserializationInfo implements Serializable { + final static int MAX_DEPTH_POPULATION = 10; + final static Set PRIMITIVE_WRAPPERS = new HashSet() {{ + add(Boolean.class); + add(Character.class); + add(Byte.class); + add(Short.class); + add(Integer.class); + add(Long.class); + add(Float.class); + add(Double.class); + add(Void.class); + add(boolean.class); + add(char.class); + add(byte.class); + add(short.class); + add(int.class); + add(long.class); + add(float.class); + add(double.class); + add(void.class); + }}; + + private String type; + private boolean leaf; + private Map value; + private String leafValue; + private Object instance; + + public DeserializationInfo(String type, Object instance) { + this.type = type; + this.instance = instance; + this.leaf = false; + } + + public DeserializationInfo(String type, Object instance, Map value) { + this.type = type; + this.instance = instance; + this.leaf = false; + this.value = value; + } + + public DeserializationInfo(String type, Object instance, String value) { + this.type = type; + this.instance = instance; + this.leaf = true; + this.leafValue = value; + } + + public DeserializationInfo(DeserializationInfo instance) { + this.type = instance.type; + this.leaf = instance.leaf; + if (instance.leaf){ + this.leafValue = instance.leafValue; + } else { + this.value = new HashMap<>(); + this.value.putAll(instance.value); + } + } + public DeserializationInfo() { + this.type = ""; + this.leaf = false; + this.value = new HashMap<>(); + this.leafValue = ""; + } + + + public Map computeAttributeFlatMap() { + if (this.value == null || this.leafValue == null || this.leafValue.isEmpty()) { + try { + this.value = computeKeyValueMappingOnObject(this.instance, 0); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + return this.value; + } + + private Map computeKeyValueMappingOnObject(Object obj, int depth) throws IllegalAccessException { + Map valueMap = new HashMap<>(); + if (depth > MAX_DEPTH_POPULATION){ + return new HashMap<>(); + } + try { + Field[] fields = obj.getClass().getFields(); + for (Field field : fields) { + boolean accessibility = field.isAccessible(); + field.setAccessible(true); + Object val = field.get(obj); + populateForField(val, field.getName(), valueMap, depth++); + field.setAccessible(accessibility); + } + List fieldList = getAllPrivateFields(obj.getClass()); + for (Field field : fieldList) { + boolean accessibility = field.isAccessible(); + field.setAccessible(true); + Object val = field.get(obj); + populateForField(val, field.getName(), valueMap, depth++); + field.setAccessible(accessibility); + } + } catch (Exception e) { + e.printStackTrace(); + } + return valueMap; + } + + private void populateForField(Object obj, String name, Map valueMap, int depth) throws IllegalAccessException { + if (depth > MAX_DEPTH_POPULATION || obj == null || name == null || valueMap == null){ + return; + } + boolean primitiveObj = obj.getClass().isPrimitive() || PRIMITIVE_WRAPPERS.contains(obj.getClass()); + if (primitiveObj) { + return; + } + + if (obj.getClass() == String.class) { + DeserializationInfo entry = new DeserializationInfo(obj.getClass().getName(), obj, (String) obj); + valueMap.put(name, entry); + } else if (Collection.class.isAssignableFrom(obj.getClass())) { + Object col[] = ((Collection) obj).toArray(); + Map collectionMap = new HashMap<>(); + for (int elementIndex=0; elementIndex getAllPrivateFields(Class clz){ + List fields = new ArrayList<>(); + fields.addAll(Arrays.asList(clz.getDeclaredFields())); + Class superClass = clz.getSuperclass(); + if (superClass != null && (PRIMITIVE_WRAPPERS.contains(superClass) || superClass == Object.class)){ + fields.addAll(getAllPrivateFields(superClass)); + } + return fields; + } + + private Map getFlatMap(Map identifierMap){ + Map flatMap = new HashMap<>(); + for (Map.Entry entry : identifierMap.entrySet()) { + if (entry.getValue().getKey() == String.class.getName()) { + flatMap.put(entry.getKey(), (String) entry.getValue().getValue()); + } else if (Map.class.isAssignableFrom(entry.getValue().getValue().getClass())){ + Map valMap = (Map) entry.getValue().getValue(); + Map childFlatMap = getFlatMap(valMap); + for (Map.Entry childEntry : childFlatMap.entrySet()){ + String key = String.format("%s.%s", entry.getKey(), childEntry.getKey()); + flatMap.put(key, childEntry.getValue()); + } + } + } + return flatMap; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public boolean isLeaf() { + return leaf; + } + + public void setLeaf(boolean leaf) { + this.leaf = leaf; + } + + public Map getValue() { + return value; + } + + public void setValue(Map value) { + this.value = value; + } + + public String getLeafValue() { + return leafValue; + } + + public void setLeafValue(String leafValue) { + this.leafValue = leafValue; + } + + public Object getInstance() { + return instance; + } + + public void setInstance(Object instance) { + this.instance = instance; + } +} \ No newline at end of file diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SecurityMetaData.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SecurityMetaData.java index 93950bb7e..27742dcff 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SecurityMetaData.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SecurityMetaData.java @@ -1,6 +1,7 @@ package com.newrelic.api.agent.security.schema; +import com.newrelic.api.agent.security.schema.operation.DeserialisationOperation; import com.newrelic.api.agent.security.schema.operation.FileIntegrityOperation; import java.util.HashMap; @@ -111,4 +112,32 @@ public void removeCustomAttribute(String key) { this.customData.remove(key); } + public Map getDeserializingObjectStack() { + if (getCustomAttribute("deserializingObjectStack", Map.class) == null){ + addCustomAttribute("deserializingObjectStack", new HashMap()); + } + return getCustomAttribute("deserializingObjectStack", Map.class); + } + + public void addToDeserializingObjectStack(DeserializationInfo dinfo) { + Map deserializingObjectStack = getDeserializingObjectStack(); + int nextIndex = deserializingObjectStack.size(); + deserializingObjectStack.put(Integer.toString(nextIndex), dinfo); + } + + public void popFromDeserializingObjectStack() { + Map deserializingObjectStack = getDeserializingObjectStack(); + int nextIndex = deserializingObjectStack.size(); + if (nextIndex > 0) { + deserializingObjectStack.remove(Integer.toString(nextIndex-1)); + } + } + + public DeserializationInfo peekDeserializingObjectStack() { + Map deserializingObjectStack = getDeserializingObjectStack(); + if (deserializingObjectStack == null || deserializingObjectStack.isEmpty()) return null; + + int nextIndex = deserializingObjectStack.size(); + return deserializingObjectStack.get(Integer.toString(nextIndex-1)); + } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/VulnerabilityCaseType.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/VulnerabilityCaseType.java index f51be2edc..7bde5fdbc 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/VulnerabilityCaseType.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/VulnerabilityCaseType.java @@ -68,7 +68,9 @@ public enum VulnerabilityCaseType { /** JavaScript Injection */ JAVASCRIPT_INJECTION("JAVASCRIPT_INJECTION"), /** JavaScript Injection */ - XQUERY_INJECTION("XQUERY_INJECTION"); + XQUERY_INJECTION("XQUERY_INJECTION"), + /** Unsafe Deserialization */ + UNSAFE_DESERIALIZATION("UNSAFE_DESERIALIZATION"); /** case type. */ diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java new file mode 100644 index 000000000..0ded6560f --- /dev/null +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java @@ -0,0 +1,46 @@ +package com.newrelic.api.agent.security.schema.operation; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.DeserializationInfo; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; + +import java.util.Map; + +public class DeserialisationOperation extends AbstractOperation { + + private String entityName; + private Map params; + + + public DeserialisationOperation(String className, String methodName) { + super(className, methodName); + this.entityName = NewRelicSecurity.getAgent().getSecurityMetaData() + .peekDeserializingObjectStack().getType(); + this.params = NewRelicSecurity.getAgent().getSecurityMetaData() + .peekDeserializingObjectStack().computeAttributeFlatMap(); + this.setCaseType(VulnerabilityCaseType.UNSAFE_DESERIALIZATION); + } + + @Override + public boolean isEmpty() { + return this.params==null || this.params.isEmpty() || StringUtils.isEmpty(this.entityName); + } + + public String getEntityName() { + return entityName; + } + + public void setEntityName(String entityName) { + this.entityName = entityName; + } + + public Map getParams() { + return params; + } + + public void setParams(Map params) { + this.params = params; + } +} diff --git a/settings.gradle b/settings.gradle index d97187553..7775bc294 100644 --- a/settings.gradle +++ b/settings.gradle @@ -41,6 +41,7 @@ include 'instrumentation:java-lang' include 'instrumentation:java-io-stream' include 'instrumentation:java-io-inputstream-jdk8' include 'instrumentation:java-io-inputstream-jdk9' +include 'instrumentation:deserialisation' include 'instrumentation:file-operation' include 'instrumentation:servlet-2.4' include 'instrumentation:servlet-5.0' @@ -134,4 +135,3 @@ include 'instrumentation:jdbc-inet-merlia' include 'instrumentation:jdbc-inet-oranxo' include 'instrumentation:jdbc-sybase-6' include 'instrumentation:low-priority-instrumentation' - From cac032e27d850980fa21ed2d5148b86c4a462976 Mon Sep 17 00:00:00 2001 From: Anupam Juniwal Date: Wed, 13 Sep 2023 18:15:32 +0530 Subject: [PATCH 02/19] This contains code cleanup and multiple fixes as required to gather more information regarding deserialization attacks --- .../java/io/Serializable_Instrumentation.java | 55 +++---------------- .../instrumentator/dispatcher/Dispatcher.java | 9 +++ .../security/schema/AbstractOperation.java | 15 ++++- .../security/schema/DeserializationInfo.java | 24 +++++++- .../operation/DeserialisationOperation.java | 11 ++-- 5 files changed, 57 insertions(+), 57 deletions(-) diff --git a/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java b/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java index c160664f3..3a19fc309 100644 --- a/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java +++ b/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java @@ -9,76 +9,35 @@ import com.newrelic.api.agent.weaver.Weaver; -@Weave(type = MatchType.BaseClass, originalName = "java.io.ObjectStreamClass") +@Weave(type = MatchType.ExactClass, originalName = "java.io.ObjectInputStream") public abstract class Serializable_Instrumentation { - void invokeReadObject(Object obj, ObjectInputStream in) - throws ClassNotFoundException, IOException, - UnsupportedOperationException { - if (NewRelicSecurity.isHookProcessingActive() && - !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { - try { + private void readSerialData(Object obj, ObjectStreamClass desc) + throws IOException { - NewRelicSecurity.getAgent().getSecurityMetaData().addToDeserializingObjectStack( - new DeserializationInfo(obj.getClass().getName(), obj) - ); - } catch (Exception e){ - e.printStackTrace(); - } - System.out.println("IN invokeReadObject"); - } try { - System.out.println("CALLING original"); - Weaver.callOriginal(); - System.out.println("Will create DeserialisationOperation"); - DeserialisationOperation operation = new DeserialisationOperation( - this.getClass().getName(), - SecurityHelper.METHOD_NAME_READ_OBJECT - ); - System.out.println("Created DeserialisationOperation"); - NewRelicSecurity.getAgent().registerOperation(operation); - System.out.println("Registered DeserialisationOperation"); - - } finally { if (NewRelicSecurity.isHookProcessingActive() && !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { - NewRelicSecurity.getAgent().getSecurityMetaData().popFromDeserializingObjectStack(); - } - } -// registerExitOperation(isFileLockAcquired, operation); - } - - void invokeReadObjectNoData(Object obj) - throws IOException, UnsupportedOperationException{ - boolean isLockAcquired = acquireLockIfPossible(); - if (isLockAcquired && NewRelicSecurity.isHookProcessingActive() && - !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { - try { - NewRelicSecurity.getAgent().getSecurityMetaData().addToDeserializingObjectStack( new DeserializationInfo(obj.getClass().getName(), obj) ); - } catch (Exception e){ - e.printStackTrace(); } - } - try { Weaver.callOriginal(); DeserialisationOperation operation = new DeserialisationOperation( this.getClass().getName(), SecurityHelper.METHOD_NAME_READ_OBJECT ); + NewRelicSecurity.getAgent().registerOperation(operation); + } catch(Exception e){ + } finally { - if (isLockAcquired && NewRelicSecurity.isHookProcessingActive() && + if (NewRelicSecurity.isHookProcessingActive() && !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { NewRelicSecurity.getAgent().getSecurityMetaData().popFromDeserializingObjectStack(); } - if (isLockAcquired){ - releaseLock(); - } } // registerExitOperation(isFileLockAcquired, operation); } diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java index 52a9b6046..a30dc0c18 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java @@ -191,6 +191,8 @@ public Object call() throws Exception { XQueryOperation xQueryOperationalBean = (XQueryOperation) operation; eventBean = prepareXQueryInjectionEvent(eventBean, xQueryOperationalBean); break; + case UNSAFE_DESERIALIZATION: + prepareDeserializationEvent(eventBean, (DeserialisationOperation) operation); default: } @@ -498,6 +500,13 @@ private static JavaAgentEventBean prepareSSRFEvent(JavaAgentEventBean eventBean, return eventBean; } + private static JavaAgentEventBean prepareDeserializationEvent(JavaAgentEventBean eventBean, + DeserialisationOperation deserialisationOperation) { + JSONArray params = new JSONArray(); + eventBean.setParameters(params); + return eventBean; + } + private boolean allowedExtensionFileIO(JSONArray params, String sourceString, String url) { if (JAVA_IO_FILE_INPUTSTREAM_OPEN.equals(sourceString)) { for (int i = 0; i < params.size(); i++) { diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java index b0d3fe4c9..510d29719 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java @@ -2,6 +2,8 @@ import com.newrelic.api.agent.security.NewRelicSecurity; +import java.util.Map; + public abstract class AbstractOperation { public static final String EMPTY = ""; @@ -47,8 +49,17 @@ public AbstractOperation(String className, String methodName){ SecurityMetaData meta = NewRelicSecurity.getAgent() .getSecurityMetaData(); if (meta != null && meta.peekDeserializingObjectStack() != null) { - this.deserializationInfo = meta.peekDeserializingObjectStack(); - this.deserializationInfo.computeAttributeFlatMap(); + Map stack = meta.getDeserializingObjectStack(); + this.deserializationInfo = stack.get("0"); + DeserializationInfo nextElement = this.deserializationInfo; + for (DeserializationInfo element : stack.values()){ + element.computeAttributeFlatMap(); + if (element == this.deserializationInfo) { + continue; + } + nextElement.setNext(element); + nextElement = element; + } } }catch (Exception e){ e.printStackTrace(); diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java index b6f8e4fff..c367d3b4a 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java @@ -32,6 +32,7 @@ public class DeserializationInfo implements Serializable { private Map value; private String leafValue; private Object instance; + private DeserializationInfo next; public DeserializationInfo(String type, Object instance) { this.type = type; @@ -56,13 +57,17 @@ public DeserializationInfo(String type, Object instance, String value) { public DeserializationInfo(DeserializationInfo instance) { this.type = instance.type; this.leaf = instance.leaf; - if (instance.leaf){ + if (instance.leaf) { this.leafValue = instance.leafValue; } else { this.value = new HashMap<>(); this.value.putAll(instance.value); } +// if (instance.next != null) { +// this.next = new DeserializationInfo(instance.next); +// } } + public DeserializationInfo() { this.type = ""; this.leaf = false; @@ -122,8 +127,13 @@ private void populateForField(Object obj, String name, Map collectionMap = new HashMap<>(); for (int elementIndex=0; elementIndex Date: Tue, 26 Sep 2023 15:16:40 +0530 Subject: [PATCH 03/19] Code cleanup --- .../security/schema/AbstractOperation.java | 24 ++++--------- .../security/schema/DeserializationInfo.java | 35 ++----------------- .../operation/DeserialisationOperation.java | 2 +- 3 files changed, 9 insertions(+), 52 deletions(-) diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java index 510d29719..d056e802f 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java @@ -45,25 +45,13 @@ public AbstractOperation(String className, String methodName){ this.className = className; this.methodName = methodName; this.blockingEndTime = 0L; - try { - SecurityMetaData meta = NewRelicSecurity.getAgent() - .getSecurityMetaData(); - if (meta != null && meta.peekDeserializingObjectStack() != null) { - Map stack = meta.getDeserializingObjectStack(); - this.deserializationInfo = stack.get("0"); - DeserializationInfo nextElement = this.deserializationInfo; - for (DeserializationInfo element : stack.values()){ - element.computeAttributeFlatMap(); - if (element == this.deserializationInfo) { - continue; - } - nextElement.setNext(element); - nextElement = element; - } + if (NewRelicSecurity.getAgent() != null && + NewRelicSecurity.getAgent().getSecurityMetaData() != null && + NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializingObjectStack() != null) { + this.deserializationInfo = NewRelicSecurity.getAgent().getSecurityMetaData() + .peekDeserializingObjectStack(); + this.deserializationInfo.computeObjectMap(); } - }catch (Exception e){ - e.printStackTrace(); - } } public String getClassName() { diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java index c367d3b4a..3084701a7 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java @@ -32,7 +32,6 @@ public class DeserializationInfo implements Serializable { private Map value; private String leafValue; private Object instance; - private DeserializationInfo next; public DeserializationInfo(String type, Object instance) { this.type = type; @@ -63,9 +62,6 @@ public DeserializationInfo(DeserializationInfo instance) { this.value = new HashMap<>(); this.value.putAll(instance.value); } -// if (instance.next != null) { -// this.next = new DeserializationInfo(instance.next); -// } } public DeserializationInfo() { @@ -76,7 +72,7 @@ public DeserializationInfo() { } - public Map computeAttributeFlatMap() { + public Map computeObjectMap() { if (this.value == null || this.leafValue == null || this.leafValue.isEmpty()) { try { this.value = computeKeyValueMappingOnObject(this.instance, 0); @@ -109,9 +105,7 @@ private Map computeKeyValueMappingOnObject(Object o populateForField(val, field.getName(), valueMap, depth++); field.setAccessible(accessibility); } - } catch (Exception e) { - e.printStackTrace(); - } + } catch (Exception e) {} return valueMap; } @@ -158,23 +152,6 @@ private List getAllPrivateFields(Class clz){ return fields; } - private Map getFlatMap(Map identifierMap){ - Map flatMap = new HashMap<>(); - for (Map.Entry entry : identifierMap.entrySet()) { - if (entry.getValue().getKey() == String.class.getName()) { - flatMap.put(entry.getKey(), (String) entry.getValue().getValue()); - } else if (Map.class.isAssignableFrom(entry.getValue().getValue().getClass())){ - Map valMap = (Map) entry.getValue().getValue(); - Map childFlatMap = getFlatMap(valMap); - for (Map.Entry childEntry : childFlatMap.entrySet()){ - String key = String.format("%s.%s", entry.getKey(), childEntry.getKey()); - flatMap.put(key, childEntry.getValue()); - } - } - } - return flatMap; - } - public String getType() { return type; } @@ -214,12 +191,4 @@ public Object getInstance() { public void setInstance(Object instance) { this.instance = instance; } - - public DeserializationInfo getNext() { - return next; - } - - public void setNext(DeserializationInfo next) { - this.next = next; - } } \ No newline at end of file diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java index cd317c31b..6ae0f8061 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java @@ -21,7 +21,7 @@ public DeserialisationOperation(String className, String methodName) { this.entityName = NewRelicSecurity.getAgent().getSecurityMetaData() .peekDeserializingObjectStack().getType(); this.params = NewRelicSecurity.getAgent().getSecurityMetaData() - .peekDeserializingObjectStack().computeAttributeFlatMap(); + .peekDeserializingObjectStack().computeObjectMap(); } this.setCaseType(VulnerabilityCaseType.UNSAFE_DESERIALIZATION); } From c17d614506298a90d130497b82150cdeb036988e Mon Sep 17 00:00:00 2001 From: Anupam Juniwal Date: Wed, 18 Oct 2023 12:07:09 +0530 Subject: [PATCH 04/19] This contains changes for deserializationInfo to be managed with one node only instead of a stack. This also contains changes to include information based on object being deserialized in a parent child, but in an adhoc format, this would be helpful for cases where events are triggered due to deserialization being done. --- .../java/io/Serializable_Instrumentation.java | 32 ++++++++++-------- .../security/schema/AbstractOperation.java | 6 ++-- .../security/schema/DeserializationInfo.java | 19 +++++++++-- .../security/schema/SecurityMetaData.java | 33 ++++++------------- .../operation/DeserialisationOperation.java | 6 ++-- 5 files changed, 50 insertions(+), 46 deletions(-) diff --git a/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java b/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java index 3a19fc309..5470ceda5 100644 --- a/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java +++ b/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java @@ -14,31 +14,35 @@ public abstract class Serializable_Instrumentation { private void readSerialData(Object obj, ObjectStreamClass desc) throws IOException { - + DeserializationInfo dInfo = new DeserializationInfo(obj.getClass().getName(), obj); try { if (NewRelicSecurity.isHookProcessingActive() && - !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addToDeserializingObjectStack( - new DeserializationInfo(obj.getClass().getName(), obj) - ); + !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { + dInfo = new DeserializationInfo(obj.getClass().getName(), obj); + NewRelicSecurity.getAgent().getSecurityMetaData().addToDeserializationRoot(dInfo); } - Weaver.callOriginal(); - DeserialisationOperation operation = new DeserialisationOperation( - this.getClass().getName(), - SecurityHelper.METHOD_NAME_READ_OBJECT - ); + Weaver.callOriginal(); - NewRelicSecurity.getAgent().registerOperation(operation); + if (NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() == dInfo) { + DeserialisationOperation operation = new DeserialisationOperation( + this.getClass().getName(), + SecurityHelper.METHOD_NAME_READ_OBJECT + ); - } catch(Exception e){ + NewRelicSecurity.getAgent().registerOperation(operation); + } + } catch (Exception e) { } finally { if (NewRelicSecurity.isHookProcessingActive() && - !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { - NewRelicSecurity.getAgent().getSecurityMetaData().popFromDeserializingObjectStack(); + !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() && + NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() != null && + NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() == dInfo) { + NewRelicSecurity.getAgent().getSecurityMetaData().resetDeserializationRoot(); } } + // registerExitOperation(isFileLockAcquired, operation); } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java index d056e802f..c32f7d63e 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java @@ -2,8 +2,6 @@ import com.newrelic.api.agent.security.NewRelicSecurity; -import java.util.Map; - public abstract class AbstractOperation { public static final String EMPTY = ""; @@ -47,9 +45,9 @@ public AbstractOperation(String className, String methodName){ this.blockingEndTime = 0L; if (NewRelicSecurity.getAgent() != null && NewRelicSecurity.getAgent().getSecurityMetaData() != null && - NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializingObjectStack() != null) { + NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() != null) { this.deserializationInfo = NewRelicSecurity.getAgent().getSecurityMetaData() - .peekDeserializingObjectStack(); + .peekDeserializationRoot(); this.deserializationInfo.computeObjectMap(); } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java index 3084701a7..ea329b0e2 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java @@ -31,6 +31,7 @@ public class DeserializationInfo implements Serializable { private boolean leaf; private Map value; private String leafValue; + private List unlinkedChildren = new ArrayList<>(); private Object instance; public DeserializationInfo(String type, Object instance) { @@ -58,9 +59,15 @@ public DeserializationInfo(DeserializationInfo instance) { this.leaf = instance.leaf; if (instance.leaf) { this.leafValue = instance.leafValue; - } else { + } else if (instance.value != null){ this.value = new HashMap<>(); - this.value.putAll(instance.value); + for(Map.Entry entry: instance.value.entrySet()){ + this.value.put(entry.getKey(), new DeserializationInfo(entry.getValue())); + } + } + for(DeserializationInfo value: instance.unlinkedChildren){ + value.computeObjectMap(); + this.unlinkedChildren.add(new DeserializationInfo(value)); } } @@ -191,4 +198,12 @@ public Object getInstance() { public void setInstance(Object instance) { this.instance = instance; } + + public List getUnlinkedChildren() { + return unlinkedChildren; + } + + public void setUnlinkedChildren(List unlinkedChildren) { + this.unlinkedChildren = unlinkedChildren; + } } \ No newline at end of file diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SecurityMetaData.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SecurityMetaData.java index 27742dcff..3597544fa 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SecurityMetaData.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SecurityMetaData.java @@ -1,7 +1,6 @@ package com.newrelic.api.agent.security.schema; -import com.newrelic.api.agent.security.schema.operation.DeserialisationOperation; import com.newrelic.api.agent.security.schema.operation.FileIntegrityOperation; import java.util.HashMap; @@ -112,32 +111,20 @@ public void removeCustomAttribute(String key) { this.customData.remove(key); } - public Map getDeserializingObjectStack() { - if (getCustomAttribute("deserializingObjectStack", Map.class) == null){ - addCustomAttribute("deserializingObjectStack", new HashMap()); + public void addToDeserializationRoot(DeserializationInfo dinfo) { + if (getCustomAttribute("deserializationRoot", DeserializationInfo.class) == null){ + addCustomAttribute("deserializationRoot", dinfo); + } else { + DeserializationInfo root = getCustomAttribute("deserializationRoot", DeserializationInfo.class); + root.getUnlinkedChildren().add(dinfo); } - return getCustomAttribute("deserializingObjectStack", Map.class); } - public void addToDeserializingObjectStack(DeserializationInfo dinfo) { - Map deserializingObjectStack = getDeserializingObjectStack(); - int nextIndex = deserializingObjectStack.size(); - deserializingObjectStack.put(Integer.toString(nextIndex), dinfo); + public void resetDeserializationRoot() { + removeCustomAttribute("deserializationRoot"); } - public void popFromDeserializingObjectStack() { - Map deserializingObjectStack = getDeserializingObjectStack(); - int nextIndex = deserializingObjectStack.size(); - if (nextIndex > 0) { - deserializingObjectStack.remove(Integer.toString(nextIndex-1)); - } - } - - public DeserializationInfo peekDeserializingObjectStack() { - Map deserializingObjectStack = getDeserializingObjectStack(); - if (deserializingObjectStack == null || deserializingObjectStack.isEmpty()) return null; - - int nextIndex = deserializingObjectStack.size(); - return deserializingObjectStack.get(Integer.toString(nextIndex-1)); + public DeserializationInfo peekDeserializationRoot() { + return getCustomAttribute("deserializationRoot", DeserializationInfo.class); } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java index 6ae0f8061..da35c5529 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java @@ -17,11 +17,11 @@ public class DeserialisationOperation extends AbstractOperation { public DeserialisationOperation(String className, String methodName) { super(className, methodName); if (NewRelicSecurity.getAgent().getSecurityMetaData()!= null && - NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializingObjectStack()!=null) { + NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot()!=null) { this.entityName = NewRelicSecurity.getAgent().getSecurityMetaData() - .peekDeserializingObjectStack().getType(); + .peekDeserializationRoot().getType(); this.params = NewRelicSecurity.getAgent().getSecurityMetaData() - .peekDeserializingObjectStack().computeObjectMap(); + .peekDeserializationRoot().computeObjectMap(); } this.setCaseType(VulnerabilityCaseType.UNSAFE_DESERIALIZATION); } From 58fa09625e41c7a5d07e75cbecd7ee0e8e680c13 Mon Sep 17 00:00:00 2001 From: Anupam Juniwal Date: Mon, 30 Oct 2023 14:05:50 +0530 Subject: [PATCH 05/19] minor fix for NPE handling --- .../api/agent/security/schema/DeserializationInfo.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java index ea329b0e2..0f65906da 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java @@ -55,6 +55,9 @@ public DeserializationInfo(String type, Object instance, String value) { } public DeserializationInfo(DeserializationInfo instance) { + if (instance == null) { + return; + } this.type = instance.type; this.leaf = instance.leaf; if (instance.leaf) { From 4cd631fd17fcd23db8d2aaa6e1c05d8e3018f8f9 Mon Sep 17 00:00:00 2001 From: Anupam Juniwal Date: Mon, 27 Nov 2023 11:02:17 +0530 Subject: [PATCH 06/19] Refactoring for java deserialization hook --- .../java/io/Serializable_Instrumentation.java | 60 ++++++++----------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java b/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java index 5470ceda5..e5f5a7f48 100644 --- a/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java +++ b/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java @@ -1,7 +1,6 @@ package java.io; import com.newrelic.api.agent.security.NewRelicSecurity; -import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; import com.newrelic.api.agent.security.schema.DeserializationInfo; import com.newrelic.api.agent.security.schema.operation.DeserialisationOperation; import com.newrelic.api.agent.weaver.MatchType; @@ -14,50 +13,41 @@ public abstract class Serializable_Instrumentation { private void readSerialData(Object obj, ObjectStreamClass desc) throws IOException { - DeserializationInfo dInfo = new DeserializationInfo(obj.getClass().getName(), obj); + DeserializationInfo dInfo = preProcessSecurityHook(obj); try { - if (NewRelicSecurity.isHookProcessingActive() && - !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { - dInfo = new DeserializationInfo(obj.getClass().getName(), obj); - NewRelicSecurity.getAgent().getSecurityMetaData().addToDeserializationRoot(dInfo); - } - Weaver.callOriginal(); - - if (NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() == dInfo) { - DeserialisationOperation operation = new DeserialisationOperation( - this.getClass().getName(), - SecurityHelper.METHOD_NAME_READ_OBJECT - ); - - NewRelicSecurity.getAgent().registerOperation(operation); - } - - } catch (Exception e) { + postProcessSecurityHook(dInfo); } finally { - if (NewRelicSecurity.isHookProcessingActive() && - !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() && - NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() != null && - NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() == dInfo) { - NewRelicSecurity.getAgent().getSecurityMetaData().resetDeserializationRoot(); - } + finalProcessSecurityHook(dInfo); } + } -// registerExitOperation(isFileLockAcquired, operation); + private DeserializationInfo preProcessSecurityHook(Object obj) { + if (NewRelicSecurity.isHookProcessingActive() && + !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { + DeserializationInfo dInfo = new DeserializationInfo(obj.getClass().getName(), obj); + NewRelicSecurity.getAgent().getSecurityMetaData().addToDeserializationRoot(dInfo); + return dInfo; + } + return null; } - private void releaseLock() { - try { - GenericHelper.releaseLock(SecurityHelper.NR_SEC_CUSTOM_ATTRIB_NAME, this.hashCode()); - } catch (Throwable ignored) { + private void postProcessSecurityHook(DeserializationInfo dInfo) { + if (dInfo != null && NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() == dInfo) { + DeserialisationOperation operation = new DeserialisationOperation( + this.getClass().getName(), + SecurityHelper.METHOD_NAME_READ_OBJECT + ); + NewRelicSecurity.getAgent().registerOperation(operation); } } - private boolean acquireLockIfPossible() { - try { - return GenericHelper.acquireLockIfPossible(SecurityHelper.NR_SEC_CUSTOM_ATTRIB_NAME, this.hashCode()); - } catch (Throwable ignored) { + private void finalProcessSecurityHook(DeserializationInfo dInfo) { + if (dInfo != null && NewRelicSecurity.isHookProcessingActive() && + !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() && + NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() != null && + NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() == dInfo) { + NewRelicSecurity.getAgent().getSecurityMetaData().resetDeserializationRoot(); } - return false; } } \ No newline at end of file From ee8d3f3862f889bb508775551f949049f4fb1f92 Mon Sep 17 00:00:00 2001 From: ajuniwal Date: Mon, 13 Jan 2025 10:16:39 +0530 Subject: [PATCH 07/19] Added todo note for object deserialiation --- .../newrelic/api/agent/security/schema/DeserializationInfo.java | 1 + 1 file changed, 1 insertion(+) diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java index 0f65906da..c34ecd02f 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java @@ -98,6 +98,7 @@ private Map computeKeyValueMappingOnObject(Object o if (depth > MAX_DEPTH_POPULATION){ return new HashMap<>(); } + // TODO: Update this to ObjectMapper.readObject to parse complete deseriaized object and return json str. try { Field[] fields = obj.getClass().getFields(); for (Field field : fields) { From 12559be40be31bfa1525c8c90073aa249be3d623 Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Wed, 15 Jan 2025 10:18:37 +0530 Subject: [PATCH 08/19] Deserialization POC without reflection --- .../instrumentator/dispatcher/Dispatcher.java | 5 +++++ .../security/schema/AbstractOperation.java | 2 +- .../security/schema/DeserializationInfo.java | 11 +++++++---- .../operation/DeserialisationOperation.java | 17 ++++++++++++++--- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java index 290996f13..f0275b75e 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java @@ -657,7 +657,12 @@ private static JavaAgentEventBean prepareSSRFEvent(JavaAgentEventBean eventBean, private static JavaAgentEventBean prepareDeserializationEvent(JavaAgentEventBean eventBean, DeserialisationOperation deserialisationOperation) { + DeserializationInfo rootDeserializationInfo = deserialisationOperation.getRootDeserializationInfo(); JSONArray params = new JSONArray(); + if(rootDeserializationInfo != null) { + eventBean.getMetaData().setDeserializationInfo(rootDeserializationInfo); + params.add(GsonUtil.toJson(rootDeserializationInfo.getInstance())); + } eventBean.setParameters(params); return eventBean; } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java index c32f7d63e..abf87b4ba 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java @@ -48,7 +48,7 @@ public AbstractOperation(String className, String methodName){ NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() != null) { this.deserializationInfo = NewRelicSecurity.getAgent().getSecurityMetaData() .peekDeserializationRoot(); - this.deserializationInfo.computeObjectMap(); +// this.deserializationInfo.computeObjectMap(); } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java index c34ecd02f..c70583d45 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java @@ -1,5 +1,7 @@ package com.newrelic.api.agent.security.schema; +import com.newrelic.api.agent.security.NewRelicSecurity; + import java.io.Serializable; import java.lang.reflect.Field; import java.util.*; @@ -68,10 +70,10 @@ public DeserializationInfo(DeserializationInfo instance) { this.value.put(entry.getKey(), new DeserializationInfo(entry.getValue())); } } - for(DeserializationInfo value: instance.unlinkedChildren){ - value.computeObjectMap(); - this.unlinkedChildren.add(new DeserializationInfo(value)); - } +// for(DeserializationInfo value: instance.unlinkedChildren){ +// value.computeObjectMap(); +// this.unlinkedChildren.add(new DeserializationInfo(value)); +// } } public DeserializationInfo() { @@ -98,6 +100,7 @@ private Map computeKeyValueMappingOnObject(Object o if (depth > MAX_DEPTH_POPULATION){ return new HashMap<>(); } + // TODO: Update this to ObjectMapper.readObject to parse complete deseriaized object and return json str. try { Field[] fields = obj.getClass().getFields(); diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java index da35c5529..901c48475 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java @@ -12,6 +12,7 @@ public class DeserialisationOperation extends AbstractOperation { private String entityName; private Map params; + private DeserializationInfo rootDeserializationInfo; public DeserialisationOperation(String className, String methodName) { @@ -20,15 +21,17 @@ public DeserialisationOperation(String className, String methodName) { NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot()!=null) { this.entityName = NewRelicSecurity.getAgent().getSecurityMetaData() .peekDeserializationRoot().getType(); - this.params = NewRelicSecurity.getAgent().getSecurityMetaData() - .peekDeserializationRoot().computeObjectMap(); +// this.params = NewRelicSecurity.getAgent().getSecurityMetaData() +// .peekDeserializationRoot().computeObjectMap(); + this.rootDeserializationInfo = NewRelicSecurity.getAgent().getSecurityMetaData() + .peekDeserializationRoot(); } this.setCaseType(VulnerabilityCaseType.UNSAFE_DESERIALIZATION); } @Override public boolean isEmpty() { - return this.params==null || this.params.isEmpty() || StringUtils.isEmpty(this.entityName); + return this.rootDeserializationInfo==null || StringUtils.isEmpty(this.entityName); } public String getEntityName() { @@ -46,4 +49,12 @@ public Map getParams() { public void setParams(Map params) { this.params = params; } + + public DeserializationInfo getRootDeserializationInfo() { + return rootDeserializationInfo; + } + + public void setRootDeserializationInfo(DeserializationInfo rootDeserializationInfo) { + this.rootDeserializationInfo = rootDeserializationInfo; + } } From d72a9acfcf52ab19d60e5c4f16dcb51d3284d45e Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Fri, 7 Feb 2025 15:30:08 +0530 Subject: [PATCH 09/19] change parameter schema for Deserialization event --- .../security/instrumentator/dispatcher/Dispatcher.java | 6 ++++-- .../newrelic/api/agent/security/schema/AgentMetaData.java | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java index f0275b75e..28bf09a76 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java @@ -660,8 +660,10 @@ private static JavaAgentEventBean prepareDeserializationEvent(JavaAgentEventBean DeserializationInfo rootDeserializationInfo = deserialisationOperation.getRootDeserializationInfo(); JSONArray params = new JSONArray(); if(rootDeserializationInfo != null) { - eventBean.getMetaData().setDeserializationInfo(rootDeserializationInfo); - params.add(GsonUtil.toJson(rootDeserializationInfo.getInstance())); + JSONObject deserializationInfo = new JSONObject(); + deserializationInfo.put("type", rootDeserializationInfo.getType()); + deserializationInfo.put("value", GsonUtil.toJson(rootDeserializationInfo.getInstance())); + params.add(deserializationInfo); } eventBean.setParameters(params); return eventBean; diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java index f53fa2524..1d087f33a 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java @@ -49,6 +49,7 @@ public class AgentMetaData { private AppServerInfo appServerInfo; + @JsonIgnore private DeserializationInfo deserializationInfo = new DeserializationInfo(); public AgentMetaData() { From bfb2acd5c1c96b065ab5419e03b7ff6c26e75dfc Mon Sep 17 00:00:00 2001 From: idawda Date: Tue, 11 Feb 2025 14:32:40 +0530 Subject: [PATCH 10/19] Bump json version to 1.2.11 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 220b28d15..4bd015446 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # The agent version. agentVersion=1.6.0 -jsonVersion=1.2.9 +jsonVersion=1.2.11 # Updated exposed NR APM API version. nrAPIVersion=8.12.0 From fd3d5729042fe5136dab6a09ce89df97ccc4d8d1 Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Mon, 24 Feb 2025 23:29:37 +0530 Subject: [PATCH 11/19] Prodcutisation of DeserializationInfo --- ...=> ObjectInputStream_Instrumentation.java} | 41 +++-- .../instrumentator/dispatcher/Dispatcher.java | 1 - .../agent/security/schema/AgentMetaData.java | 11 -- .../security/schema/DeserializationInfo.java | 170 +----------------- 4 files changed, 30 insertions(+), 193 deletions(-) rename instrumentation-security/deserialisation/src/main/java/java/io/{Serializable_Instrumentation.java => ObjectInputStream_Instrumentation.java} (53%) diff --git a/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java b/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java similarity index 53% rename from instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java rename to instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java index e5f5a7f48..e4037bd38 100644 --- a/instrumentation-security/deserialisation/src/main/java/java/io/Serializable_Instrumentation.java +++ b/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java @@ -1,7 +1,10 @@ package java.io; import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; import com.newrelic.api.agent.security.schema.DeserializationInfo; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; import com.newrelic.api.agent.security.schema.operation.DeserialisationOperation; import com.newrelic.api.agent.weaver.MatchType; import com.newrelic.api.agent.weaver.Weave; @@ -9,45 +12,55 @@ @Weave(type = MatchType.ExactClass, originalName = "java.io.ObjectInputStream") -public abstract class Serializable_Instrumentation { +public abstract class ObjectInputStream_Instrumentation { private void readSerialData(Object obj, ObjectStreamClass desc) throws IOException { - DeserializationInfo dInfo = preProcessSecurityHook(obj); + boolean isLockAcquired = acquireLockIfPossible(); + AbstractOperation operation = null; + DeserializationInfo dInfo = null; + if(isLockAcquired) { + dInfo = preProcessSecurityHook(obj); + } try { Weaver.callOriginal(); - postProcessSecurityHook(dInfo); + operation = postProcessSecurityHook(dInfo); } finally { - finalProcessSecurityHook(dInfo); + if(isLockAcquired) { + finalProcessSecurityHook(dInfo); + GenericHelper.releaseLock(SecurityHelper.NR_SEC_CUSTOM_ATTRIB_NAME); + } } + //TODO add register exit operation if required } private DeserializationInfo preProcessSecurityHook(Object obj) { - if (NewRelicSecurity.isHookProcessingActive() && - !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { - DeserializationInfo dInfo = new DeserializationInfo(obj.getClass().getName(), obj); - NewRelicSecurity.getAgent().getSecurityMetaData().addToDeserializationRoot(dInfo); - return dInfo; - } - return null; + DeserializationInfo dInfo = new DeserializationInfo(obj.getClass().getName(), obj); + NewRelicSecurity.getAgent().getSecurityMetaData().addToDeserializationRoot(dInfo); + return dInfo; } - private void postProcessSecurityHook(DeserializationInfo dInfo) { + private DeserialisationOperation postProcessSecurityHook(DeserializationInfo dInfo) { if (dInfo != null && NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() == dInfo) { DeserialisationOperation operation = new DeserialisationOperation( this.getClass().getName(), SecurityHelper.METHOD_NAME_READ_OBJECT ); NewRelicSecurity.getAgent().registerOperation(operation); + return operation; } + return null; } private void finalProcessSecurityHook(DeserializationInfo dInfo) { - if (dInfo != null && NewRelicSecurity.isHookProcessingActive() && - !NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() && + if (dInfo != null && NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() != null && NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() == dInfo) { NewRelicSecurity.getAgent().getSecurityMetaData().resetDeserializationRoot(); } } + + private boolean acquireLockIfPossible() { + return GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.UNSAFE_DESERIALIZATION, SecurityHelper.NR_SEC_CUSTOM_ATTRIB_NAME); + } } \ No newline at end of file diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java index 28bf09a76..a9bc3cf9f 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java @@ -764,7 +764,6 @@ private JavaAgentEventBean setGenericProperties(AbstractOperation objectBean, Ja eventBean.setUserAPIInfo(operation.getUserClassEntity().getUserClassElement().getLineNumber(), operation.getUserClassEntity().getUserClassElement().getClassName(), operation.getUserClassEntity().getUserClassElement().getMethodName()); - eventBean.getMetaData().setDeserializationInfo(operation.getDeserializationInfo()); eventBean.getLinkingMetadata().put(NR_APM_TRACE_ID, securityMetaData.getCustomAttribute(NR_APM_TRACE_ID, String.class)); eventBean.getLinkingMetadata().put(NR_APM_SPAN_ID, securityMetaData.getCustomAttribute(NR_APM_SPAN_ID, String.class)); return eventBean; diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java index 1d087f33a..b05381bcd 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java @@ -49,9 +49,6 @@ public class AgentMetaData { private AppServerInfo appServerInfo; - @JsonIgnore - private DeserializationInfo deserializationInfo = new DeserializationInfo(); - public AgentMetaData() { this.rciMethodsCalls = new HashSet<>(); this.ips = new HashSet<>(); @@ -82,7 +79,6 @@ public AgentMetaData(AgentMetaData agentMetaData) { this.fromJumpRequiredInStackTrace = agentMetaData.getFromJumpRequiredInStackTrace(); this.framework = agentMetaData.framework; this.skipScanParameters = agentMetaData.skipScanParameters; - this.deserializationInfo = new DeserializationInfo(agentMetaData.deserializationInfo); } public boolean isTriggerViaRCI() { @@ -234,11 +230,4 @@ public void setSkipScanParameters(SkipScanParameters skipScanParameters) { this.skipScanParameters = skipScanParameters; } - public DeserializationInfo getDeserializationInfo() { - return deserializationInfo; - } - - public void setDeserializationInfo(DeserializationInfo deserializationInfo) { - this.deserializationInfo = new DeserializationInfo(deserializationInfo); - } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java index c70583d45..b23acc615 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java @@ -1,59 +1,18 @@ package com.newrelic.api.agent.security.schema; -import com.newrelic.api.agent.security.NewRelicSecurity; +import java.util.ArrayList; +import java.util.List; -import java.io.Serializable; -import java.lang.reflect.Field; -import java.util.*; -public class DeserializationInfo implements Serializable { - final static int MAX_DEPTH_POPULATION = 10; - final static Set PRIMITIVE_WRAPPERS = new HashSet() {{ - add(Boolean.class); - add(Character.class); - add(Byte.class); - add(Short.class); - add(Integer.class); - add(Long.class); - add(Float.class); - add(Double.class); - add(Void.class); - add(boolean.class); - add(char.class); - add(byte.class); - add(short.class); - add(int.class); - add(long.class); - add(float.class); - add(double.class); - add(void.class); - }}; +public class DeserializationInfo { private String type; - private boolean leaf; - private Map value; - private String leafValue; private List unlinkedChildren = new ArrayList<>(); private Object instance; public DeserializationInfo(String type, Object instance) { this.type = type; this.instance = instance; - this.leaf = false; - } - - public DeserializationInfo(String type, Object instance, Map value) { - this.type = type; - this.instance = instance; - this.leaf = false; - this.value = value; - } - - public DeserializationInfo(String type, Object instance, String value) { - this.type = type; - this.instance = instance; - this.leaf = true; - this.leafValue = value; } public DeserializationInfo(DeserializationInfo instance) { @@ -61,111 +20,12 @@ public DeserializationInfo(DeserializationInfo instance) { return; } this.type = instance.type; - this.leaf = instance.leaf; - if (instance.leaf) { - this.leafValue = instance.leafValue; - } else if (instance.value != null){ - this.value = new HashMap<>(); - for(Map.Entry entry: instance.value.entrySet()){ - this.value.put(entry.getKey(), new DeserializationInfo(entry.getValue())); - } - } // for(DeserializationInfo value: instance.unlinkedChildren){ // value.computeObjectMap(); // this.unlinkedChildren.add(new DeserializationInfo(value)); // } } - public DeserializationInfo() { - this.type = ""; - this.leaf = false; - this.value = new HashMap<>(); - this.leafValue = ""; - } - - - public Map computeObjectMap() { - if (this.value == null || this.leafValue == null || this.leafValue.isEmpty()) { - try { - this.value = computeKeyValueMappingOnObject(this.instance, 0); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - return this.value; - } - - private Map computeKeyValueMappingOnObject(Object obj, int depth) throws IllegalAccessException { - Map valueMap = new HashMap<>(); - if (depth > MAX_DEPTH_POPULATION){ - return new HashMap<>(); - } - - // TODO: Update this to ObjectMapper.readObject to parse complete deseriaized object and return json str. - try { - Field[] fields = obj.getClass().getFields(); - for (Field field : fields) { - boolean accessibility = field.isAccessible(); - field.setAccessible(true); - Object val = field.get(obj); - populateForField(val, field.getName(), valueMap, depth++); - field.setAccessible(accessibility); - } - List fieldList = getAllPrivateFields(obj.getClass()); - for (Field field : fieldList) { - boolean accessibility = field.isAccessible(); - field.setAccessible(true); - Object val = field.get(obj); - populateForField(val, field.getName(), valueMap, depth++); - field.setAccessible(accessibility); - } - } catch (Exception e) {} - return valueMap; - } - - private void populateForField(Object obj, String name, Map valueMap, int depth) throws IllegalAccessException { - if (depth > MAX_DEPTH_POPULATION || obj == null || name == null || valueMap == null){ - return; - } - boolean primitiveObj = obj.getClass().isPrimitive() || PRIMITIVE_WRAPPERS.contains(obj.getClass()); - if (primitiveObj) { - return; - } - - if (obj.getClass() == String.class) { - DeserializationInfo entry = new DeserializationInfo(obj.getClass().getName(), obj, (String) obj); - valueMap.put(name, entry); - } else if (Collection.class.isAssignableFrom(obj.getClass()) || obj.getClass().isArray()) { - Object col[]; - if (obj.getClass().isArray()) { - col = (Object []) obj; - } else { - col = ((Collection) obj).toArray(); - } - Map collectionMap = new HashMap<>(); - for (int elementIndex=0; elementIndex getAllPrivateFields(Class clz){ - List fields = new ArrayList<>(); - fields.addAll(Arrays.asList(clz.getDeclaredFields())); - Class superClass = clz.getSuperclass(); - if (superClass != null && (PRIMITIVE_WRAPPERS.contains(superClass) || superClass == Object.class)){ - fields.addAll(getAllPrivateFields(superClass)); - } - return fields; - } - public String getType() { return type; } @@ -174,30 +34,6 @@ public void setType(String type) { this.type = type; } - public boolean isLeaf() { - return leaf; - } - - public void setLeaf(boolean leaf) { - this.leaf = leaf; - } - - public Map getValue() { - return value; - } - - public void setValue(Map value) { - this.value = value; - } - - public String getLeafValue() { - return leafValue; - } - - public void setLeafValue(String leafValue) { - this.leafValue = leafValue; - } - public Object getInstance() { return instance; } From 4577300fa3152d85fcc6d55785ae47533ef9357d Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Wed, 26 Feb 2025 10:45:43 +0530 Subject: [PATCH 12/19] Add UNSAFE_DESERIALIZATION as a new category --- .../instrumentation/helpers/GenericHelper.java | 3 +++ .../schema/policy/IastDetectionCategory.java | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/GenericHelper.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/GenericHelper.java index 6bbaca47c..6cc61f8f6 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/GenericHelper.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/GenericHelper.java @@ -104,6 +104,9 @@ private static boolean isLockAcquirePossible(VulnerabilityCaseType caseType) { case HASH: enabled = NewRelicSecurity.getAgent().getIastDetectionCategory().getInsecureSettingsEnabled(); break; + case UNSAFE_DESERIALIZATION: + enabled = NewRelicSecurity.getAgent().getIastDetectionCategory().getUnsafeDeserializationEnabled(); + break; default: break; } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/policy/IastDetectionCategory.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/policy/IastDetectionCategory.java index 25d0439d4..a8e23c963 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/policy/IastDetectionCategory.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/policy/IastDetectionCategory.java @@ -16,6 +16,7 @@ public class IastDetectionCategory { Boolean javascriptInjectionEnabled = false; Boolean xpathInjectionEnabled = false; Boolean ssrfEnabled = false; + Boolean unsafeDeserializationEnabled = false; @JsonIgnore private String disabledCategoriesCSV; @@ -157,6 +158,10 @@ public void generateDisabledCategoriesCSV() { disabledCategoriesCSVBuilder.append(VulnerabilityCaseType.HTTP_REQUEST); disabledCategoriesCSVBuilder.append(STR_COMMA); } + if (unsafeDeserializationEnabled) { + disabledCategoriesCSVBuilder.append(VulnerabilityCaseType.UNSAFE_DESERIALIZATION); + disabledCategoriesCSVBuilder.append(STR_COMMA); + } if (disabledCategoriesCSVBuilder.length() > 0) { disabledCategoriesCSVBuilder.deleteCharAt(disabledCategoriesCSVBuilder.length() - 1); } @@ -166,4 +171,12 @@ public void generateDisabledCategoriesCSV() { public String getDisabledCategoriesCSV() { return disabledCategoriesCSV; } + + public Boolean getUnsafeDeserializationEnabled() { + return unsafeDeserializationEnabled; + } + + public void setUnsafeDeserializationEnabled(Boolean unsafeDeserializationEnabled) { + this.unsafeDeserializationEnabled = unsafeDeserializationEnabled; + } } From 90f03247f816a8d1fdc5a2ee4053932e5c6131e6 Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Fri, 11 Apr 2025 11:41:02 +0530 Subject: [PATCH 13/19] Introduce Deserialization Vulnerability detection and Reflection detection --- gradle.properties | 2 +- ...lper.java => ObjectInputStreamHelper.java} | 6 +- .../io/ObjectInputStream_Instrumentation.java | 126 +++++++-- .../io/ObjectStreamClass_Instrumentation.java | 27 ++ .../java-reflection/build.gradle | 16 ++ .../lang/reflect/Method_Instrumentation.java | 68 +++++ .../instrumentator/dispatcher/Dispatcher.java | 248 ++++++++++++------ .../intcodeagent/utils/SerializerUtil.java | 183 +++++++++++++ .../intcodeagent/websocket/WSClient.java | 2 + .../newrelic/api/agent/security/Agent.java | 34 ++- .../newrelic/api/agent/security/Agent.java | 2 - .../helpers/IASTSerialisationVerifier.java | 35 +++ .../helpers/InstrumentationConstants.java | 7 + .../serializer/ObjectInstanceFactory.java | 24 ++ .../security/schema/AbstractOperation.java | 20 +- .../agent/security/schema/AgentMetaData.java | 47 ++++ .../schema/DeserialisationContext.java | 36 +++ .../security/schema/DeserializationInfo.java | 22 +- .../schema/DeserializationInvocation.java | 89 +++++++ .../security/schema/FiledDefinition.java | 87 ++++++ .../security/schema/SecurityMetaData.java | 17 +- .../agent/security/schema/Serializable.java | 70 +++++ .../schema/SerializableClassDefinition.java | 50 ++++ .../schema/VulnerabilityCaseType.java | 4 +- ...ion.java => DeserializationOperation.java} | 29 +- .../operation/JavaReflectionOperation.java | 73 ++++++ .../security}/utils/ExecutionIDGenerator.java | 2 +- settings.gradle | 3 +- 28 files changed, 1157 insertions(+), 172 deletions(-) rename instrumentation-security/deserialisation/src/main/java/java/io/{SecurityHelper.java => ObjectInputStreamHelper.java} (56%) create mode 100644 instrumentation-security/deserialisation/src/main/java/java/io/ObjectStreamClass_Instrumentation.java create mode 100644 instrumentation-security/java-reflection/build.gradle create mode 100644 instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java create mode 100644 newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/utils/SerializerUtil.java create mode 100644 newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/IASTSerialisationVerifier.java create mode 100644 newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/InstrumentationConstants.java create mode 100644 newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/serializer/ObjectInstanceFactory.java create mode 100644 newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserialisationContext.java create mode 100644 newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInvocation.java create mode 100644 newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/FiledDefinition.java create mode 100644 newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/Serializable.java create mode 100644 newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SerializableClassDefinition.java rename newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/{DeserialisationOperation.java => DeserializationOperation.java} (50%) create mode 100644 newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/JavaReflectionOperation.java rename {newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator => newrelic-security-api/src/main/java/com/newrelic/api/agent/security}/utils/ExecutionIDGenerator.java (86%) diff --git a/gradle.properties b/gradle.properties index 4bd015446..95e22a3e4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # The agent version. agentVersion=1.6.0 -jsonVersion=1.2.11 +jsonVersion=1.2.10 # Updated exposed NR APM API version. nrAPIVersion=8.12.0 diff --git a/instrumentation-security/deserialisation/src/main/java/java/io/SecurityHelper.java b/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStreamHelper.java similarity index 56% rename from instrumentation-security/deserialisation/src/main/java/java/io/SecurityHelper.java rename to instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStreamHelper.java index daed02acb..cefbab4f6 100644 --- a/instrumentation-security/deserialisation/src/main/java/java/io/SecurityHelper.java +++ b/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStreamHelper.java @@ -1,9 +1,7 @@ package java.io; -public class SecurityHelper { +public class ObjectInputStreamHelper { public static final String METHOD_NAME_READ_OBJECT = "readObject"; - public static final String NULL_STRING = "null"; - - public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "UNSAFE-DESERIALISATION-LOCK-JAVA-IO-"; + public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "UNSAFE-DESERIALISATION-LOCK-JAVA-IO-%s-"; } \ No newline at end of file diff --git a/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java b/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java index e4037bd38..f54f81d12 100644 --- a/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java +++ b/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java @@ -2,36 +2,54 @@ import com.newrelic.api.agent.security.NewRelicSecurity; import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; -import com.newrelic.api.agent.security.schema.AbstractOperation; -import com.newrelic.api.agent.security.schema.DeserializationInfo; -import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; -import com.newrelic.api.agent.security.schema.operation.DeserialisationOperation; +import com.newrelic.api.agent.security.schema.*; +import com.newrelic.api.agent.security.schema.Serializable; +import com.newrelic.api.agent.security.schema.operation.DeserializationOperation; import com.newrelic.api.agent.weaver.MatchType; import com.newrelic.api.agent.weaver.Weave; import com.newrelic.api.agent.weaver.Weaver; +import java.util.Arrays; -@Weave(type = MatchType.ExactClass, originalName = "java.io.ObjectInputStream") + +@Weave(type = MatchType.BaseClass, originalName = "java.io.ObjectInputStream") public abstract class ObjectInputStream_Instrumentation { private void readSerialData(Object obj, ObjectStreamClass desc) throws IOException { - boolean isLockAcquired = acquireLockIfPossible(); - AbstractOperation operation = null; - DeserializationInfo dInfo = null; - if(isLockAcquired) { - dInfo = preProcessSecurityHook(obj); - } + DeserializationInfo dInfo = preProcessSecurityHook(obj); + Weaver.callOriginal(); + } + + private void filterCheck(Class clazz, int arrayLength) + throws InvalidClassException { + boolean isLockAcquired = acquireLockIfPossible("filterCheck"); + boolean filterCheck = false; try { Weaver.callOriginal(); - operation = postProcessSecurityHook(dInfo); + filterCheck = true; + } finally { + if(isLockAcquired) { + processFilterCheck(clazz, filterCheck); + GenericHelper.releaseLock(String.format(ObjectInputStreamHelper.NR_SEC_CUSTOM_ATTRIB_NAME, "filterCheck")); + } + } + } + + protected Class resolveClass(ObjectStreamClass desc) + throws IOException, ClassNotFoundException + { + boolean isLockAcquired = acquireLockIfPossible("resolve"); + Class returnValue = null; + try { + returnValue = Weaver.callOriginal(); } finally { if(isLockAcquired) { - finalProcessSecurityHook(dInfo); - GenericHelper.releaseLock(SecurityHelper.NR_SEC_CUSTOM_ATTRIB_NAME); + processResolveClass(desc, returnValue); + GenericHelper.releaseLock(String.format(ObjectInputStreamHelper.NR_SEC_CUSTOM_ATTRIB_NAME, "resolve")); } } - //TODO add register exit operation if required + return returnValue; } private DeserializationInfo preProcessSecurityHook(Object obj) { @@ -40,27 +58,77 @@ private DeserializationInfo preProcessSecurityHook(Object obj) { return dInfo; } - private DeserialisationOperation postProcessSecurityHook(DeserializationInfo dInfo) { - if (dInfo != null && NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() == dInfo) { - DeserialisationOperation operation = new DeserialisationOperation( + + private final Object readObject(Class type) + throws IOException, ClassNotFoundException { + boolean isLockAcquired = acquireLockIfPossible("readObject"); + DeserializationInvocation deserializationInvocation = null; + DeserializationOperation operation = null; + + if(isLockAcquired) { + operation = new DeserializationOperation( this.getClass().getName(), - SecurityHelper.METHOD_NAME_READ_OBJECT + ObjectInputStreamHelper.METHOD_NAME_READ_OBJECT ); - NewRelicSecurity.getAgent().registerOperation(operation); - return operation; + DeserialisationContext deserialisationContext = new DeserialisationContext(type.getName(), Arrays.copyOfRange(Thread.currentThread().getStackTrace(), 1, 10)); + deserializationInvocation = new DeserializationInvocation(true, operation.getExecutionId(), deserialisationContext); + NewRelicSecurity.getAgent().getSecurityMetaData().setDeserializationInvocation(deserializationInvocation); + operation.setDeserializationInvocation(deserializationInvocation); +// NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(InstrumentationConstants.ACTIVE_DESERIALIZATION, true); + } + try { + return Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + if(NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() != null) { + operation.setRootDeserializationInfo(NewRelicSecurity.getAgent().getSecurityMetaData() + .peekDeserializationRoot()); + operation.setEntityName(operation.getRootDeserializationInfo().getType()); + } + NewRelicSecurity.getAgent().registerOperation(operation); + NewRelicSecurity.getAgent().getSecurityMetaData().setDeserializationInvocation(null); + NewRelicSecurity.getAgent().getSecurityMetaData().resetDeserializationRoot(); + GenericHelper.releaseLock(String.format(ObjectInputStreamHelper.NR_SEC_CUSTOM_ATTRIB_NAME, "readObject")); + } } - return null; } - private void finalProcessSecurityHook(DeserializationInfo dInfo) { - if (dInfo != null && - NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() != null && - NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() == dInfo) { - NewRelicSecurity.getAgent().getSecurityMetaData().resetDeserializationRoot(); + private void processFilterCheck(Class clazz, boolean filterCheck) { + System.out.println("lock acquired Filter check "); + DeserializationInvocation deserializationInvocation = NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation(); + System.out.println("Filter check deserializationInvocation : "+deserializationInvocation); + if(deserializationInvocation != null && clazz != null) { + System.out.println("Filter check for class : "+clazz.getName()+" is deserializable : "+filterCheck); + com.newrelic.api.agent.security.schema.Serializable serializable = deserializationInvocation.getEncounteredSerializableByName(clazz.getName()); + if(serializable == null) { + serializable = new Serializable(clazz.getName(), true); + serializable.setKlass(clazz); + deserializationInvocation.addEncounteredSerializable(serializable); +// serializable.setClassDefinition(getClassDefinition(ObjectStreamClass.lookup(clazz))); + } + if(!filterCheck) { + serializable.setDeserializable(false); + } + } + } + + private void processResolveClass(ObjectStreamClass desc, Class returnValue) { + DeserializationInvocation deserializationInvocation = NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation(); + if(deserializationInvocation != null) { + Serializable serializable = deserializationInvocation.getEncounteredSerializableByName(desc.getName()); + if(serializable == null) { + serializable = new Serializable(desc.getName(), true); + serializable.setKlass(returnValue); + deserializationInvocation.addEncounteredSerializable(serializable); +// serializable.setClassDefinition(getClassDefinition(desc)); + } + if(returnValue == null) { + serializable.setDeserializable(false); + } } } - private boolean acquireLockIfPossible() { - return GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.UNSAFE_DESERIALIZATION, SecurityHelper.NR_SEC_CUSTOM_ATTRIB_NAME); + private boolean acquireLockIfPossible(String operation) { + return GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.UNSAFE_DESERIALIZATION, String.format(ObjectInputStreamHelper.NR_SEC_CUSTOM_ATTRIB_NAME, operation)); } } \ No newline at end of file diff --git a/instrumentation-security/deserialisation/src/main/java/java/io/ObjectStreamClass_Instrumentation.java b/instrumentation-security/deserialisation/src/main/java/java/io/ObjectStreamClass_Instrumentation.java new file mode 100644 index 000000000..4e0fb5197 --- /dev/null +++ b/instrumentation-security/deserialisation/src/main/java/java/io/ObjectStreamClass_Instrumentation.java @@ -0,0 +1,27 @@ +package java.io; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.schema.DeserializationInvocation; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +@Weave(type = MatchType.BaseClass, originalName = "java.io.ObjectStreamClass") +public class ObjectStreamClass_Instrumentation { + + void invokeReadObject(Object obj, ObjectInputStream in) + throws ClassNotFoundException, IOException, + UnsupportedOperationException + { + if(NewRelicSecurity.isHookProcessingActive()) { + DeserializationInvocation deserializationInvocation = NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation(); + if (deserializationInvocation != null) { + deserializationInvocation.pushReadObjectInAction(obj.getClass().getName()); + } + Weaver.callOriginal(); + if (deserializationInvocation != null) { + deserializationInvocation.popReadObjectInAction(); + } + } + } +} diff --git a/instrumentation-security/java-reflection/build.gradle b/instrumentation-security/java-reflection/build.gradle new file mode 100644 index 000000000..d41d6b414 --- /dev/null +++ b/instrumentation-security/java-reflection/build.gradle @@ -0,0 +1,16 @@ +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") +} + +// This instrumentation module should not use the bootstrap classpath + + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.java-reflection' } +} + +verifyInstrumentation { + verifyClasspath = false // We don't want to verify classpath since these are JDK classes +} diff --git a/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java b/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java new file mode 100644 index 000000000..5afcb9204 --- /dev/null +++ b/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java @@ -0,0 +1,68 @@ +package java.lang.reflect; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.JavaReflectionOperation; +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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Weave(type = MatchType.ExactClass, originalName = "java.lang.reflect.Method") +public abstract class Method_Instrumentation { + + public abstract String getName(); + + public abstract Class getDeclaringClass(); + + public abstract Class[] getParameterTypes(); + + public Object invoke(Object obj, Object... args) { + AbstractOperation operation = null; + if(NewRelicSecurity.isHookProcessingActive()) { + operation = preprocessSecurityHook(obj, getDeclaringClass(), getParameterTypes(), getName(), args); + } + Object returnValue = Weaver.callOriginal(); + registerExitOperation(operation); + return returnValue; + } + + private void registerExitOperation(AbstractOperation operation) { + NewRelicSecurity.getAgent().registerExitEvent(operation); + } + + private AbstractOperation preprocessSecurityHook(Object obj, Class declaringClass, Class[] parameterTypes, String name, Object[] args) { + try { + if (NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation() != null && NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation().getActive()) { + if(StringUtils.isNotBlank(NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation().peekReadObjectInAction()) + && !StringUtils.equals(name, "readObject")) { + JavaReflectionOperation operation = new JavaReflectionOperation(this.getClass().getName(), "invoke", declaringClass.getName(), name, args, obj); + List methodNames = new ArrayList<>(); + for (Method method : declaringClass.getDeclaredMethods()) { + if(Arrays.equals(method.getParameterTypes(), parameterTypes)) { + methodNames.add(method.getName()); + } + } + operation.setDeclaredMethods(methodNames); + NewRelicSecurity.getAgent().registerOperation(operation); + return operation; + } + } + } catch (Throwable e) { + if(e instanceof NewRelicSecurityException){ + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, "JAVA-REFLECTION", e.getMessage()), e, Method_Instrumentation.class.getName()); + throw e; + } + NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, "JAVA-REFLECTION", e.getMessage()), e, Method_Instrumentation.class.getName()); + NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE , String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, "JAVA-REFLECTION", e.getMessage()), e, Method_Instrumentation.class.getName()); + } + return null; + } +} diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java index 3e66de5fd..b7bb70f5d 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java @@ -1,6 +1,9 @@ package com.newrelic.agent.security.instrumentator.dispatcher; +import com.fasterxml.jackson.core.JsonProcessingException; import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.newrelic.agent.security.AgentConfig; import com.newrelic.agent.security.AgentInfo; import com.newrelic.agent.security.instrumentator.helper.DynamoDBRequestConverter; @@ -32,6 +35,12 @@ import java.io.File; import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.io.ObjectStreamField; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; @@ -67,6 +76,8 @@ public class Dispatcher implements Callable { public static final String COOKIE_IS_SECURE = "isSecure"; public static final String COOKIE_IS_HTTP_ONLY = "isHttpOnly"; public static final String COOKIE_IS_SAME_SITE_STRICT = "isSameSiteStrict"; + private static final int MAX_DEPTH = 3; + public static final String CLASS_EXTENSION = ".class"; private ExitEventBean exitEventBean; private AbstractOperation operation; private SecurityMetaData securityMetaData; @@ -232,22 +243,27 @@ public Object call() throws Exception { eventBean = prepareSolrDbRequestEvent(eventBean, solrDbOperation); break; case UNSAFE_DESERIALIZATION: - prepareDeserializationEvent(eventBean, (DeserialisationOperation) operation); + prepareDeserializationEvent(eventBean, (DeserializationOperation) operation); + break; + case REFLECTION: + JavaReflectionOperation javaReflectionOperationalBean = (JavaReflectionOperation) operation; + eventBean = prepareReflectionEvent(eventBean, javaReflectionOperationalBean); + break; default: } - if (!VulnerabilityCaseType.FILE_INTEGRITY.equals(operation.getCaseType())) { - if (VulnerabilityCaseType.FILE_OPERATION.equals(operation.getCaseType()) - && ((FileOperation) operation).isGetBooleanAttributesCall()) { - eventBean = processStackTrace(eventBean, operation.getCaseType(), false); - } else { - eventBean = processStackTrace(eventBean, operation.getCaseType(), true); - } - if (eventBean == null) { - return null; - } - } +// if (!VulnerabilityCaseType.FILE_INTEGRITY.equals(operation.getCaseType())) { +// if (VulnerabilityCaseType.FILE_OPERATION.equals(operation.getCaseType()) +// && ((FileOperation) operation).isGetBooleanAttributesCall()) { +// eventBean = processStackTrace(eventBean, operation.getCaseType(), false); +// } else { +// eventBean = processStackTrace(eventBean, operation.getCaseType(), true); +// } +// if (eventBean == null) { +// return null; +// } +// } EventSendPool.getInstance().sendEvent(eventBean); if (!firstEventSent.get()) { @@ -264,6 +280,28 @@ public Object call() throws Exception { return null; } + private JavaAgentEventBean prepareReflectionEvent(JavaAgentEventBean eventBean, JavaReflectionOperation javaReflectionOperationalBean) { + JSONArray params = new JSONArray(); + JSONObject javaReflection = new JSONObject(); + javaReflection.put("declaringClass", javaReflectionOperationalBean.getDeclaringClass()); + javaReflection.put("method", javaReflectionOperationalBean.getNameOfMethod()); + javaReflection.put("declaredMethods", javaReflectionOperationalBean.getDeclaredMethods()); + try { + JSONArray arguments = new JSONArray(); + for (Object arg : javaReflectionOperationalBean.getArgs()) { + arguments.add(JsonConverter.getObjectMapper().writeValueAsString(arg)); + } + javaReflection.put("args", arguments); + } catch (Exception ignored) {} + try { + javaReflection.put("obj", JsonConverter.getObjectMapper().writeValueAsString(javaReflectionOperationalBean.getObj())); + } catch (JsonProcessingException ignored) { + } + params.add(javaReflection); + eventBean.setParameters(params); + return eventBean; + } + private boolean isReplayEndpointConfirmed() { Map applicationConnectionConfig = NewRelicSecurity.getAgent().getApplicationConnectionConfig(); for (Map.Entry connectionConfig : applicationConnectionConfig.entrySet()) { @@ -333,14 +371,30 @@ public boolean isPrimitiveType(Class clazz) { private JavaAgentEventBean processFileOperationEvent(JavaAgentEventBean eventBean, FileOperation fileOperationalBean) { prepareFileEvent(eventBean, fileOperationalBean); String URL = StringUtils.substringBefore(securityMetaData.getRequest().getUrl(), QUESTION_CHAR); + if(eventBean.getMetaData().isTriggerViaDeserialisation() && isClassLoadingOperation(fileOperationalBean.getFileName())) { + //Class loading event while deserialization, drop it. + logger.log(LogLevel.FINEST, String.format("Class loading event while deserialization, drop it : %s", fileOperationalBean.getFileName()), this.getClass().getName()); + return null; + } + if (!(AgentUtils.getInstance().getAgentPolicy().getVulnerabilityScan().getEnabled() && AgentUtils.getInstance().getAgentPolicy().getVulnerabilityScan().getIastScan().getEnabled()) && allowedExtensionFileIO(eventBean.getParameters(), eventBean.getSourceMethod(), URL)) { // Event is bypassed. Drop it. + logger.log(LogLevel.FINEST, String.format("File Event is bypassed. Drop it : %s", operation), this.getClass().getName()); return null; } return eventBean; } + private boolean isClassLoadingOperation(List fileName) { + for (String file : fileName) { + if (file.endsWith(CLASS_EXTENSION)) { + return true; + } + } + return false; + } + /** * Validate and send if required event for REFLECTED XSS @@ -656,100 +710,138 @@ private static JavaAgentEventBean prepareSSRFEvent(JavaAgentEventBean eventBean, } private static JavaAgentEventBean prepareDeserializationEvent(JavaAgentEventBean eventBean, - DeserialisationOperation deserialisationOperation) { - DeserializationInfo rootDeserializationInfo = deserialisationOperation.getRootDeserializationInfo(); + DeserializationOperation deserializationOperation) throws JsonProcessingException { + DeserializationInfo rootDeserializationInfo = deserializationOperation.getRootDeserializationInfo(); JSONArray params = new JSONArray(); + JSONObject deserializationInfo = new JSONObject(); if(rootDeserializationInfo != null) { - JSONObject deserializationInfo = new JSONObject(); deserializationInfo.put("type", rootDeserializationInfo.getType()); - deserializationInfo.put("value", GsonUtil.toJson(rootDeserializationInfo.getInstance())); - params.add(deserializationInfo); + deserializationInfo.put("value", JsonConverter.getObjectMapper().writeValueAsString(rootDeserializationInfo.getInstance())); } + Set fieldTypes = new HashSet<>(); + fieldTypes.addAll(deserializationOperation.getDeserializationInvocation().getEncounteredSerializable().keySet()); + gatherClassDefinitions(deserializationOperation.getDeserializationInvocation().getEncounteredSerializable(), fieldTypes); + deserializationInfo.put("classInfo", deserializationOperation.getDeserializationInvocation().getEncounteredSerializable()); + params.add(deserializationInfo); eventBean.setParameters(params); return eventBean; } - private boolean allowedExtensionFileIO(JSONArray params, String sourceString, String url) { - if (JAVA_IO_FILE_INPUTSTREAM_OPEN.equals(sourceString)) { - for (int i = 0; i < params.size(); i++) { - String filePath = params.get(i).toString(); - - if (StringUtils.containsIgnoreCase(filePath, File.separator)) { - filePath = StringUtils.substringAfterLast(filePath, File.separator); + private static void gatherClassDefinitions(Map encounteredSerializable, Set fieldTypes) { + for (Serializable serializable : encounteredSerializable.values()) { + if(serializable.getDeserializable()) { + ObjectStreamClass osc = ObjectStreamClass.lookup(serializable.getKlass()); + if(osc != null) { + serializable.setClassDefinition(getClassDefinition(osc, serializable.getKlass(), fieldTypes)); } + } + } + } - if (StringUtils.containsIgnoreCase(url, File.separator)) { - url = StringUtils.substringAfterLast(url, File.separator); + private static SerializableClassDefinition getClassDefinition(ObjectStreamClass desc, Class classType, Set fieldTypes) { + List filedDefinitions = new ArrayList<>(); + for (ObjectStreamField field : desc.getFields()) { + String name = field.getName(); + String type = field.getType().getName(); + boolean isPrimitive = field.isPrimitive(); + boolean isTransient = false; + List actualTypeArguments = null; + try { + Field genericField = classType.getDeclaredField(name); + Type genericType = genericField.getGenericType(); + if(genericType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericType; + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + actualTypeArguments = new ArrayList<>(); + for(Type typeArgument : typeArguments) { + actualTypeArguments.add(typeArgument.getTypeName()); + } } - - if (StringUtils.equals(url, filePath)) - return true; + isTransient = Modifier.isTransient(genericField.getModifiers()); + } catch (NoSuchFieldException ignored) { + } + boolean isSerializable = true; + FiledDefinition filedDefinition = new FiledDefinition(name, type, isPrimitive, isTransient, isSerializable); + filedDefinition.setParameterizedType(actualTypeArguments); + if(!isPrimitive && !fieldTypes.contains(type)) { + fieldTypes.add(type); + ObjectStreamClass osc = ObjectStreamClass.lookup(field.getType()); + if(osc != null) { + filedDefinition.setClassDefinition(getClassDefinition(osc, field.getType(), fieldTypes)); + } + } + if (type.equals(Class.class.getName()) || type.equals(Object.class.getName()) + || (filedDefinition.getClassDefinition() != null && !filedDefinition.getClassDefinition().getFields().isEmpty())) { + filedDefinitions.add(filedDefinition); } } - return false; + SerializableClassDefinition serializableClassDefinition = new SerializableClassDefinition(desc.getName(), classType.isInterface(), filedDefinitions); +// serializableClassDefinition.addAllSuperClasses(getAllInterfaces(classType)); +// serializableClassDefinition.addAllSuperClasses(getAllSuperClasses(classType)); + return serializableClassDefinition; } - private JavaAgentEventBean processStackTrace(JavaAgentEventBean eventBean, - VulnerabilityCaseType vulnerabilityCaseType, boolean deserialisationCheck) { - - String klassName = null; - for (int i = 0; i < operation.getStackTrace().length; i++) { - // TODO : check this sequence. Why this is being set from inside Deserialisation check. - - klassName = operation.getStackTrace()[i].getClassName(); - if (VulnerabilityCaseType.SYSTEM_COMMAND.equals(vulnerabilityCaseType) - || VulnerabilityCaseType.SQL_DB_COMMAND.equals(vulnerabilityCaseType) - || VulnerabilityCaseType.FILE_INTEGRITY.equals(vulnerabilityCaseType) - || VulnerabilityCaseType.NOSQL_DB_COMMAND.equals(vulnerabilityCaseType) - || VulnerabilityCaseType.FILE_OPERATION.equals(vulnerabilityCaseType) - || VulnerabilityCaseType.HTTP_REQUEST.equals(vulnerabilityCaseType) - || VulnerabilityCaseType.SYSTEM_EXIT.equals(vulnerabilityCaseType)) { - xxeTriggerCheck(i, eventBean, klassName); - if (deserialisationCheck) { - deserializationTriggerCheck(i, eventBean, klassName); - } + public static Set getAllInterfaces(Class clazz) { + Set interfaces = new HashSet<>(); + while (clazz != null) { + for (Class iface : clazz.getInterfaces()) { + interfaces.add(iface.getName()); + interfaces.addAll(getAllInterfaces(iface)); } + clazz = clazz.getSuperclass(); } - return eventBean; + return interfaces; } - private void xxeTriggerCheck(int i, JavaAgentEventBean eventBean, String klassName) { + public static List getAllSuperClasses(Class clazz) { + List superClasses = new ArrayList<>(); + Class currentClass = clazz.getSuperclass(); - if ((StringUtils.contains(klassName, XML_DOCUMENT_FRAGMENT_SCANNER_IMPL) - && StringUtils.equals(operation.getStackTrace()[i].getMethodName(), SCAN_DOCUMENT)) - || (StringUtils.contains(klassName, XML_ENTITY_MANAGER) - && StringUtils.equals(operation.getStackTrace()[i].getMethodName(), SETUP_CURRENT_ENTITY))) { - eventBean.getMetaData().setTriggerViaXXE(true); + while (currentClass != null) { + superClasses.add(currentClass.getName()); + currentClass = currentClass.getSuperclass(); } + + return superClasses; } - private void deserializationTriggerCheck(int index, JavaAgentEventBean eventBean, String klassName) { - if (!NewRelic.getAgent().getConfig().getValue(INRSettingsKey.SECURITY_DETECTION_DESERIALIZATION_ENABLED, true)) { - return; - } - if (ObjectInputStream.class.getName().equals(klassName) - && StringUtils.equals(operation.getStackTrace()[index].getMethodName(), READ_OBJECT)) { - eventBean.getMetaData().setTriggerViaDeserialisation(true); + private static JSONArray getUnlinkedChildrenJson(Set unlinkedChildren) { + JSONArray unlinkedChildrenJson = new JSONArray(); + for(DeserializationInfo deserializationInfo : unlinkedChildren) { + try { + JSONObject deserializationInfoJson = new JSONObject(); + deserializationInfoJson.put("type", deserializationInfo.getType()); + deserializationInfoJson.put("value", JsonConverter.getObjectMapper().writeValueAsString(deserializationInfo.getInstance())); + deserializationInfoJson.put("unlinkedChildren", getUnlinkedChildrenJson(deserializationInfo.getUnlinkedChildren())); + unlinkedChildrenJson.add(deserializationInfoJson); + } catch (Throwable e){ + logger.postLogMessageIfNecessary(LogLevel.WARNING, String.format("Unable to stringify the Object %s", deserializationInfo.getInstance()), e, + Dispatcher.class.getName()); + Agent.getInstance().reportIncident(LogLevel.WARNING, String.format("Unable to stringify the Object %s", deserializationInfo.getInstance()), e, + Dispatcher.class.getName()); + } } + return unlinkedChildrenJson; } - private void rciTriggerCheck(int index, JavaAgentEventBean eventBean, String klassName) { - if (!NewRelic.getAgent().getConfig().getValue(INRSettingsKey.SECURITY_DETECTION_RCI_ENABLED, true)) { - return; - } + private boolean allowedExtensionFileIO(JSONArray params, String sourceString, String url) { + if (JAVA_IO_FILE_INPUTSTREAM_OPEN.equals(sourceString)) { + for (int i = 0; i < params.size(); i++) { + String filePath = params.get(i).toString(); - if (operation.getStackTrace()[index].getLineNumber() <= 0 && index > 0 - && operation.getStackTrace()[index - 1].getLineNumber() > 0 && - StringUtils.isNotBlank(operation.getStackTrace()[index - 1].getFileName())) { - eventBean.getMetaData().setTriggerViaRCI(true); - eventBean.getMetaData().getRciMethodsCalls().add(AgentUtils.stackTraceElementToString(operation.getStackTrace()[index])); - eventBean.getMetaData().getRciMethodsCalls().add(AgentUtils.stackTraceElementToString(operation.getStackTrace()[index - 1])); - } - if (StringUtils.contains(klassName, REFLECT_NATIVE_METHOD_ACCESSOR_IMPL) - && StringUtils.equals(operation.getStackTrace()[index].getMethodName(), INVOKE_0) && index > 0) { - eventBean.getMetaData().setTriggerViaRCI(true); - eventBean.getMetaData().getRciMethodsCalls().add(AgentUtils.stackTraceElementToString(operation.getStackTrace()[index - 1])); + if (StringUtils.containsIgnoreCase(filePath, File.separator)) { + filePath = StringUtils.substringAfterLast(filePath, File.separator); + } + + if (StringUtils.containsIgnoreCase(url, File.separator)) { + url = StringUtils.substringAfterLast(url, File.separator); + } + + if (StringUtils.equals(url, filePath)) + return true; + } } + return false; } private JavaAgentEventBean setGenericProperties(AbstractOperation objectBean, JavaAgentEventBean eventBean) { diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/utils/SerializerUtil.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/utils/SerializerUtil.java new file mode 100644 index 000000000..af1d9c553 --- /dev/null +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/utils/SerializerUtil.java @@ -0,0 +1,183 @@ +package com.newrelic.agent.security.intcodeagent.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.newrelic.agent.security.intcodeagent.filelogging.FileLoggerThreadPool; +import com.newrelic.agent.security.intcodeagent.websocket.JsonConverter; +import com.newrelic.api.agent.security.utils.logging.LogLevel; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.*; + +/** + * Utility class for serializing and deserializing objects. + */ +public class SerializerUtil { + + private static final FileLoggerThreadPool logger = FileLoggerThreadPool.getInstance(); + + private static final Map, Object> dummyValues = new HashMap<>(); + + static { + dummyValues.put(int.class, 0); + dummyValues.put(Integer.class, 0); + dummyValues.put(long.class, 0L); + dummyValues.put(Long.class, 0L); + dummyValues.put(double.class, 0.0); + dummyValues.put(Double.class, 0.0); + dummyValues.put(float.class, 0.0f); + dummyValues.put(Float.class, 0.0f); + dummyValues.put(boolean.class, false); + dummyValues.put(Boolean.class, false); + dummyValues.put(String.class, "dummy"); + } + + /** + * Serializes an object to a byte array. + * + * @param obj the object to serialize + * @return the byte array representation of the object, or null if serialization fails + */ + public static byte[] serialize(Object obj) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(obj); + oos.flush(); + return bos.toByteArray(); + } catch (IOException e) { + logger.log(LogLevel.WARNING, String.format("Unable to serialize object : %s: %s", obj, e.getMessage()), CommonUtils.class.getName()); + logger.postLogMessageIfNecessary(LogLevel.WARNING, String.format("Unable to serialize object : %s", obj), e, CommonUtils.class.getName()); + } + return null; + } + + /** + * Instantiates an object from its JSON string representation. + * + * @param data the JSON string representation of the object + * @param klass the class of the object to instantiate + * @return the instantiated object, or null if instantiation fails + */ + public static Object instantiate(String data, Class klass) { + try { + return JsonConverter.getObjectMapper().readValue(data, klass); + } catch (JsonProcessingException e) { + logger.log(LogLevel.WARNING, String.format("Unable to instantiate object : %s: %s", klass, e.getMessage()), CommonUtils.class.getName()); + logger.postLogMessageIfNecessary(LogLevel.WARNING, String.format("Unable to instantiate object : %s", klass), e, CommonUtils.class.getName()); + } + return null; + } + + /** + * Encodes a byte array to a base64 string. + * + * @param data the byte array to encode + * @return the base64 string representation of the byte array + */ + public static String base64Encode(byte[] data) { + return java.util.Base64.getEncoder().encodeToString(data); + } + + + /** + * Retrieves the Class object associated with the class or interface with the given string name. + * + * @param className the fully qualified name of the desired class + * @return the Class object for the class with the specified name + * @throws ClassNotFoundException if the class cannot be located + */ + public static Class getClassByName(String className) throws ClassNotFoundException { + return Class.forName(className); + } + + /** + * Serializes an object to a byte array, then encodes the byte array to a base64 string. + * + * @param className the fully qualified name of the class of the object to serialize + * @param jsonData the JSON string representation of the object + * @return the base64 string representation of the serialized object, or null if serialization fails + */ + public static String base64SerializedPayload(String className, String jsonData) { + try { + Class clazz = getClassByName(className); + Object obj = instantiate(jsonData, clazz); + byte[] serialized = serialize(obj); + return base64Encode(serialized); + } catch (ClassNotFoundException e) { + logger.log(LogLevel.WARNING, String.format("Unable to base64 serialize object : %s: %s", className, e.getMessage()), CommonUtils.class.getName()); + logger.postLogMessageIfNecessary(LogLevel.WARNING, String.format("Unable to base64 serialize object : %s", className), e, CommonUtils.class.getName()); + } + return null; + } + + public static T createDummyObject(Class clazz, List fields) throws IllegalAccessException, InstantiationException { + T instance = clazz.newInstance(); + for (Field field : clazz.getDeclaredFields()) { + if(fields.contains(field.getName())) { + field.setAccessible(true); + setDummyValue(instance, field); + } + } + return instance; + } + + public static T createDummyObject(Class clazz) throws IllegalAccessException, InstantiationException { + T instance = clazz.newInstance(); + for (Field field : clazz.getDeclaredFields()) { + field.setAccessible(true); + setDummyValue(instance, field); + } + return instance; + } + + private static void setDummyValue(Object instance, Field field) throws IllegalAccessException, InstantiationException { + Class fieldType = field.getType(); + if (dummyValues.containsKey(fieldType)) { + field.set(instance, dummyValues.get(fieldType)); + } else if (List.class.isAssignableFrom(fieldType)) { + List dummyList = new ArrayList<>(); + dummyList.add(Runtime.class); + field.set(instance, dummyList); + } else if (Map.class.isAssignableFrom(fieldType)) { + Map dummyMap = new HashMap<>(); + dummyMap.put("dummyKey", Runtime.class); + field.set(instance, dummyMap); + } else if (Set.class.isAssignableFrom(fieldType)) { + Set dummySet = new HashSet<>(); + dummySet.add(Runtime.class); + field.set(instance, dummySet); + } else if (fieldType.isArray()) { + int dimensions = getArrayDimensions(fieldType); + Object arrayInstance = createArrayInstance(fieldType.getComponentType(), dimensions); + if (dimensions == 1) { + Array.set(arrayInstance, 0, dummyValues.getOrDefault(fieldType.getComponentType(), Runtime.class)); + } + field.set(instance, arrayInstance); + } else if (!fieldType.isPrimitive()) { + field.set(instance, createDummyObject(fieldType)); + } + } + + private static int getArrayDimensions(Class arrayClass) { + int dimensions = 0; + Class currentClass = arrayClass; + while (currentClass.isArray()) { + dimensions++; + currentClass = currentClass.getComponentType(); + } + return dimensions; + } + + private static Object createArrayInstance(Class componentType, int dimensions) { + int[] dimensionSizes = new int[dimensions]; + Arrays.fill(dimensionSizes, 1); // Initialize each dimension with size 1 + return Array.newInstance(componentType, dimensionSizes); + } + + + +} diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/websocket/WSClient.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/websocket/WSClient.java index 849ab40ee..50d80de36 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/websocket/WSClient.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/websocket/WSClient.java @@ -4,6 +4,7 @@ import com.newrelic.agent.security.AgentInfo; import com.newrelic.agent.security.instrumentator.dispatcher.DispatcherPool; import com.newrelic.agent.security.instrumentator.httpclient.RestRequestThreadPool; +import com.newrelic.agent.security.instrumentator.os.OsVariablesInstance; import com.newrelic.agent.security.instrumentator.utils.AgentUtils; import com.newrelic.agent.security.instrumentator.utils.INRSettingsKey; import com.newrelic.agent.security.intcodeagent.controlcommand.ControlCommandProcessor; @@ -158,6 +159,7 @@ private WSClient() throws URISyntaxException { this.addHeader("NR-CSEC-PROCESS-START-TIME", String.valueOf(ManagementFactory.getRuntimeMXBean().getStartTime())); this.addHeader("NR-CSEC-IAST-TEST-IDENTIFIER", AgentConfig.getInstance().getScanControllers().getIastTestIdentifier()); this.addHeader("NR-CSEC-IAST-SCAN-INSTANCE-COUNT", String.valueOf(AgentConfig.getInstance().getScanControllers().getScanInstanceCount())); + this.addHeader("NR-CSEC-VALIDATOR-HOME-TMP", OsVariablesInstance.getInstance().getOsVariables().getTmpDirectory()); Proxy proxy = proxyManager(); if(proxy != null) { this.setProxy(proxy); diff --git a/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java b/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java index 9e1e200f6..ff4c0b5f1 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java @@ -20,6 +20,7 @@ import com.newrelic.api.agent.security.instrumentation.helpers.*; import com.newrelic.api.agent.security.schema.operation.SecureCookieOperationSet; import com.newrelic.api.agent.security.schema.policy.IastDetectionCategory; +import com.newrelic.api.agent.security.utils.ExecutionIDGenerator; import com.newrelic.api.agent.security.utils.logging.LogLevel; import com.newrelic.agent.security.intcodeagent.logging.HealthCheckScheduleThread; import com.newrelic.agent.security.intcodeagent.logging.IAgentConstants; @@ -40,8 +41,6 @@ import java.io.IOException; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; -import java.net.HttpURLConnection; -import java.net.URL; import java.text.ParseException; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -405,20 +404,20 @@ public void registerOperation(AbstractOperation operation) { return; } - String executionId = ExecutionIDGenerator.getExecutionId(); - operation.setExecutionId(executionId); operation.setStartTime(Instant.now().toEpochMilli()); if (securityMetaData != null && securityMetaData.getFuzzRequestIdentifier().getK2Request()) { logger.log(LogLevel.FINEST, String.format("New Event generation with id %s of type %s", operation.getExecutionId(), operation.getClass().getSimpleName()), Agent.class.getName()); } - if (operation instanceof RXSSOperation) { - operation.setStackTrace(securityMetaData.getMetaData().getServiceTrace()); - securityMetaData.addCustomAttribute("RXSS_PROCESSED", true); - } else if (operation instanceof SecureCookieOperationSet) { - operation.setStackTrace(securityMetaData.getMetaData().getServiceTrace()); - } else { - StackTraceElement[] trace = Thread.currentThread().getStackTrace(); - operation.setStackTrace(Arrays.copyOfRange(trace, securityMetaData.getMetaData().getFromJumpRequiredInStackTrace(), trace.length)); + if (operation.getStackTrace() == null || operation.getStackTrace().length <= 0) { + if (operation instanceof RXSSOperation) { + operation.setStackTrace(securityMetaData.getMetaData().getServiceTrace()); + securityMetaData.addCustomAttribute("RXSS_PROCESSED", true); + } else if (operation instanceof SecureCookieOperationSet) { + operation.setStackTrace(securityMetaData.getMetaData().getServiceTrace()); + } else { + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + operation.setStackTrace(Arrays.copyOfRange(trace, securityMetaData.getMetaData().getFromJumpRequiredInStackTrace(), trace.length)); + } } // added to fetch request/response in case of grpc requests @@ -459,8 +458,15 @@ public void registerOperation(AbstractOperation operation) { operation.setUserClassEntity(setUserClassEntity(operation, securityMetaData)); } processStackTrace(operation); -// boolean blockNeeded = checkIfBlockingNeeded(operation.getApiID()); -// securityMetaData.getMetaData().setApiBlocked(blockNeeded); + if(securityMetaData.getDeserializationInvocation() != null && securityMetaData.getDeserializationInvocation().getActive()){ + securityMetaData.getMetaData().setDeserialisationContext(securityMetaData.getDeserializationInvocation().getDeserialisationContext()); + securityMetaData.getMetaData().addLinkedEventId(operation.getExecutionId()); + securityMetaData.getMetaData().setParentEventId(securityMetaData.getDeserializationInvocation().getEid()); + securityMetaData.getMetaData().setTriggerViaDeserialisation(true); + } else { + securityMetaData.getMetaData().setTriggerViaDeserialisation(false); + } + HttpRequest request = securityMetaData.getRequest(); Framework frameWork = Framework.UNKNOWN; if(!securityMetaData.getFuzzRequestIdentifier().getK2Request() && StringUtils.isNotBlank(securityMetaData.getMetaData().getFramework())) { 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 bf36265c9..446257917 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 @@ -69,12 +69,10 @@ public boolean deactivateSecurity() { @Override public void registerOperation(AbstractOperation operation) { System.out.println("Registering operation : " + operation.hashCode() + " : " + NewRelic.getAgent().getTransaction().hashCode()); - String executionId = "dummy-exec-id"; String apiId = "dummy-api-id"; if(operation instanceof FileIntegrityOperation && ((FileIntegrityOperation) operation).getFileName().endsWith(".new.class")){ return; } - operation.setExecutionId(executionId); operation.setApiID(apiId); operation.setStartTime(Instant.now().toEpochMilli()); StackTraceElement[] trace = Thread.currentThread().getStackTrace(); diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/IASTSerialisationVerifier.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/IASTSerialisationVerifier.java new file mode 100644 index 000000000..19632ea5a --- /dev/null +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/IASTSerialisationVerifier.java @@ -0,0 +1,35 @@ +package com.newrelic.api.agent.security.instrumentation.helpers; + +import java.io.Serializable; + +public class IASTSerialisationVerifier implements Serializable { + + private static final long serialVersionUID = 522078560470736671L; + + private String type; + private transient String name; + + public IASTSerialisationVerifier() { + } + + public IASTSerialisationVerifier(String type, String name) { + this.type = type; + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/InstrumentationConstants.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/InstrumentationConstants.java new file mode 100644 index 000000000..cd55aac00 --- /dev/null +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/InstrumentationConstants.java @@ -0,0 +1,7 @@ +package com.newrelic.api.agent.security.instrumentation.helpers; + +public class InstrumentationConstants { + + + public static final String ACTIVE_DESERIALIZATION = "active-deserialization"; +} diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/serializer/ObjectInstanceFactory.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/serializer/ObjectInstanceFactory.java new file mode 100644 index 000000000..0220e28af --- /dev/null +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/serializer/ObjectInstanceFactory.java @@ -0,0 +1,24 @@ +package com.newrelic.api.agent.security.instrumentation.helpers.serializer; + +import java.io.ObjectStreamClass; +import java.lang.reflect.Constructor; + +public class ObjectInstanceFactory { + + public static Object createInstance(Class clazz) throws Exception { + ObjectStreamClass osc = ObjectStreamClass.lookup(clazz); + if (osc == null) { + throw new IllegalArgumentException("Class not serializable: " + clazz.getName()); + } + + Constructor constructor = clazz.getDeclaredConstructor(); + return constructor.newInstance(); + } + + public static Class getClassByName(String className) throws ClassNotFoundException { + return Class.forName(className); + } + + + +} diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java index abf87b4ba..6c1b12e12 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AbstractOperation.java @@ -1,6 +1,6 @@ package com.newrelic.api.agent.security.schema; -import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.utils.ExecutionIDGenerator; public abstract class AbstractOperation { @@ -27,8 +27,6 @@ public abstract class AbstractOperation { private boolean isLowSeverityHook; - private DeserializationInfo deserializationInfo; - public AbstractOperation() { this.className = EMPTY; this.sourceMethod = EMPTY; @@ -43,13 +41,7 @@ public AbstractOperation(String className, String methodName){ this.className = className; this.methodName = methodName; this.blockingEndTime = 0L; - if (NewRelicSecurity.getAgent() != null && - NewRelicSecurity.getAgent().getSecurityMetaData() != null && - NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() != null) { - this.deserializationInfo = NewRelicSecurity.getAgent().getSecurityMetaData() - .peekDeserializationRoot(); -// this.deserializationInfo.computeObjectMap(); - } + this.executionId = ExecutionIDGenerator.getExecutionId(); } public String getClassName() { @@ -146,12 +138,4 @@ public boolean isLowSeverityHook() { public void setLowSeverityHook(boolean lowSeverityHook) { this.isLowSeverityHook = lowSeverityHook; } - - public DeserializationInfo getDeserializationInfo() { - return deserializationInfo; - } - - public void setDeserializationInfo(DeserializationInfo deserializationInfo) { - this.deserializationInfo = deserializationInfo; - } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java index b05381bcd..c16d448bd 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java @@ -49,6 +49,12 @@ public class AgentMetaData { private AppServerInfo appServerInfo; + private Set linkedEventIds; + + private String parentEventId; + + private DeserialisationContext deserialisationContext; + public AgentMetaData() { this.rciMethodsCalls = new HashSet<>(); this.ips = new HashSet<>(); @@ -57,6 +63,7 @@ public AgentMetaData() { this.appServerInfo = new AppServerInfo(); this.framework = StringUtils.EMPTY; this.skipScanParameters = new SkipScanParameters(); + this.linkedEventIds = new HashSet<>(); } public AgentMetaData(AgentMetaData agentMetaData) { @@ -79,6 +86,11 @@ public AgentMetaData(AgentMetaData agentMetaData) { this.fromJumpRequiredInStackTrace = agentMetaData.getFromJumpRequiredInStackTrace(); this.framework = agentMetaData.framework; this.skipScanParameters = agentMetaData.skipScanParameters; + this.linkedEventIds = new HashSet<>(agentMetaData.linkedEventIds); + this.parentEventId = agentMetaData.parentEventId; + if(deserialisationContext != null) { + this.deserialisationContext = new DeserialisationContext(agentMetaData.deserialisationContext); + } } public boolean isTriggerViaRCI() { @@ -230,4 +242,39 @@ public void setSkipScanParameters(SkipScanParameters skipScanParameters) { this.skipScanParameters = skipScanParameters; } + public Set getLinkedEventIds() { + return linkedEventIds; + } + + public boolean addLinkedEventId(String linkedEventId) { + return linkedEventIds.add(linkedEventId); + } + + public boolean removeLinkedEventId(String linkedEventId) { + return linkedEventIds.remove(linkedEventId); + } + + public void setLinkedEventIds(Set linkedEventIds) { + this.linkedEventIds = linkedEventIds; + } + + public void clearLinkedEventIds() { + linkedEventIds.clear(); + } + + public String getParentEventId() { + return parentEventId; + } + + public void setParentEventId(String parentEventId) { + this.parentEventId = parentEventId; + } + + public DeserialisationContext getDeserialisationContext() { + return deserialisationContext; + } + + public void setDeserialisationContext(DeserialisationContext deserialisationContext) { + this.deserialisationContext = deserialisationContext; + } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserialisationContext.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserialisationContext.java new file mode 100644 index 000000000..0ab2647ff --- /dev/null +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserialisationContext.java @@ -0,0 +1,36 @@ +package com.newrelic.api.agent.security.schema; + +import java.util.Arrays; + +public class DeserialisationContext { + + private String entityName; + + private StackTraceElement[] stacktrace; + + public DeserialisationContext(String entityName, StackTraceElement[] stacktrace) { + this.entityName = entityName; + this.stacktrace = stacktrace; + } + + public DeserialisationContext(DeserialisationContext deserialisationContext) { + this.entityName = deserialisationContext.getEntityName(); + this.stacktrace = Arrays.copyOf(deserialisationContext.getStacktrace(), deserialisationContext.getStacktrace().length); + } + + public String getEntityName() { + return entityName; + } + + public void setEntityName(String entityName) { + this.entityName = entityName; + } + + public StackTraceElement[] getStacktrace() { + return stacktrace; + } + + public void setStacktrace(StackTraceElement[] stacktrace) { + this.stacktrace = stacktrace; + } +} diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java index b23acc615..a9b93f07a 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInfo.java @@ -1,13 +1,12 @@ package com.newrelic.api.agent.security.schema; -import java.util.ArrayList; -import java.util.List; +import java.util.*; public class DeserializationInfo { private String type; - private List unlinkedChildren = new ArrayList<>(); + private Set unlinkedChildren = new HashSet<>(); private Object instance; public DeserializationInfo(String type, Object instance) { @@ -42,11 +41,24 @@ public void setInstance(Object instance) { this.instance = instance; } - public List getUnlinkedChildren() { + public Set getUnlinkedChildren() { return unlinkedChildren; } - public void setUnlinkedChildren(List unlinkedChildren) { + public void setUnlinkedChildren(Set unlinkedChildren) { this.unlinkedChildren = unlinkedChildren; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DeserializationInfo)) return false; + DeserializationInfo that = (DeserializationInfo) o; + return Objects.equals(type, that.type) && Objects.equals(instance, that.instance); + } + + @Override + public int hashCode() { + return Objects.hash(type, instance); + } } \ No newline at end of file diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInvocation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInvocation.java new file mode 100644 index 000000000..fc8fbbc2b --- /dev/null +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInvocation.java @@ -0,0 +1,89 @@ +package com.newrelic.api.agent.security.schema; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +public class DeserializationInvocation { + + private Boolean active; + + private String eid; + + private Stack readObjectInAction; + + private Map encounteredSerializable; + + private DeserialisationContext deserialisationContext; + + public DeserializationInvocation(boolean active, String executionId, DeserialisationContext deserialisationContext) { + this.active = active; + this.eid = executionId; + this.deserialisationContext = deserialisationContext; + this.readObjectInAction = new Stack<>(); + encounteredSerializable = new HashMap<>(); + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public String getEid() { + return eid; + } + + public void setEid(String eid) { + this.eid = eid; + } + + public Map getEncounteredSerializable() { + return encounteredSerializable; + } + + public void setEncounteredSerializable(Map encounteredSerializable) { + this.encounteredSerializable = encounteredSerializable; + } + + public Serializable addEncounteredSerializable(Serializable serializable) { + return this.encounteredSerializable.put(serializable.getNameOfClass(), serializable); + } + + public Serializable getEncounteredSerializableByName(String nameOfClass) { + return this.encounteredSerializable.get(nameOfClass); + } + + public Stack getReadObjectInAction() { + return readObjectInAction; + } + + public void setReadObjectInAction(Stack readObjectInAction) { + this.readObjectInAction = readObjectInAction; + } + + public void pushReadObjectInAction(String readObjectInAction) { + this.readObjectInAction.push(readObjectInAction); + } + + public String popReadObjectInAction() { + return this.readObjectInAction.pop(); + } + + public String peekReadObjectInAction() { + if(this.readObjectInAction.isEmpty()) { + return null; + } + return this.readObjectInAction.peek(); + } + + public DeserialisationContext getDeserialisationContext() { + return deserialisationContext; + } + + public void setDeserialisationContext(DeserialisationContext deserialisationContext) { + this.deserialisationContext = deserialisationContext; + } +} diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/FiledDefinition.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/FiledDefinition.java new file mode 100644 index 000000000..bc272935d --- /dev/null +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/FiledDefinition.java @@ -0,0 +1,87 @@ +package com.newrelic.api.agent.security.schema; + +import java.util.List; + +public class FiledDefinition { + + private String name; + + private String type; + + private List parameterizedType; + + private boolean isPrimitive; + + private boolean isTransient; + + private boolean isSerializable; + + private SerializableClassDefinition classDefinition; + + public FiledDefinition() { + } + + public FiledDefinition(String name, String type, boolean isPrimitive, boolean isTransient, boolean isSerializable) { + this.name = name; + this.type = type; + this.isPrimitive = isPrimitive; + this.isTransient = isTransient; + this.isSerializable = isSerializable; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public boolean getIsPrimitive() { + return isPrimitive; + } + + public void setIsPrimitive(boolean primitive) { + isPrimitive = primitive; + } + + public boolean getIsTransient() { + return isTransient; + } + + public void setIsTransient(boolean aTransient) { + isTransient = aTransient; + } + + public boolean getIsSerializable() { + return isSerializable; + } + + public void setIsSerializable(boolean serializable) { + isSerializable = serializable; + } + + public SerializableClassDefinition getClassDefinition() { + return classDefinition; + } + + public void setClassDefinition(SerializableClassDefinition classDefinition) { + this.classDefinition = classDefinition; + } + + public List getParameterizedType() { + return parameterizedType; + } + + public void setParameterizedType(List parameterizedType) { + this.parameterizedType = parameterizedType; + } +} diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SecurityMetaData.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SecurityMetaData.java index 2bf643609..d335900d0 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SecurityMetaData.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SecurityMetaData.java @@ -28,6 +28,8 @@ public class SecurityMetaData { private Set unregisteredOperations; + private DeserializationInvocation deserializationInvocation; + public SecurityMetaData() { request = new HttpRequest(); response = new HttpResponse(); @@ -125,20 +127,31 @@ public void clearCustomAttr(){ customData.clear(); } - public void addToDeserializationRoot(DeserializationInfo dinfo) { + public boolean addToDeserializationRoot(DeserializationInfo dinfo) { if (getCustomAttribute("deserializationRoot", DeserializationInfo.class) == null){ addCustomAttribute("deserializationRoot", dinfo); + return true; } else { DeserializationInfo root = getCustomAttribute("deserializationRoot", DeserializationInfo.class); root.getUnlinkedChildren().add(dinfo); + return false; } } public void resetDeserializationRoot() { - removeCustomAttribute("deserializationRoot"); + this.removeCustomAttribute("deserializationRoot"); + this.metaData.clearLinkedEventIds(); } public DeserializationInfo peekDeserializationRoot() { return getCustomAttribute("deserializationRoot", DeserializationInfo.class); } + + public DeserializationInvocation getDeserializationInvocation() { + return deserializationInvocation; + } + + public void setDeserializationInvocation(DeserializationInvocation deserializationInvocation) { + this.deserializationInvocation = deserializationInvocation; + } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/Serializable.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/Serializable.java new file mode 100644 index 000000000..a61716f16 --- /dev/null +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/Serializable.java @@ -0,0 +1,70 @@ +package com.newrelic.api.agent.security.schema; + +import com.newrelic.api.agent.security.schema.annotations.JsonIgnore; + +import java.util.Objects; + +public class Serializable { + + private String nameOfClass; + + @JsonIgnore + private Class klass; + + private Boolean deserializable; + + private SerializableClassDefinition classDefinition; + + public Serializable() { + } + + public Serializable(String nameOfClass, Boolean deserializable) { + this.nameOfClass = nameOfClass; + this.deserializable = deserializable; + } + + public String getNameOfClass() { + return nameOfClass; + } + + public void setNameOfClass(String nameOfClass) { + this.nameOfClass = nameOfClass; + } + + public Boolean getDeserializable() { + return deserializable; + } + + public void setDeserializable(Boolean deserializable) { + this.deserializable = deserializable; + } + + public SerializableClassDefinition getClassDefinition() { + return classDefinition; + } + + public void setClassDefinition(SerializableClassDefinition classDefinition) { + this.classDefinition = classDefinition; + } + + public Class getKlass() { + return klass; + } + + public void setKlass(Class klass) { + this.klass = klass; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Serializable)) return false; + Serializable that = (Serializable) o; + return Objects.equals(nameOfClass, that.nameOfClass); + } + + @Override + public int hashCode() { + return Objects.hashCode(nameOfClass); + } +} diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SerializableClassDefinition.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SerializableClassDefinition.java new file mode 100644 index 000000000..6378d05fd --- /dev/null +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/SerializableClassDefinition.java @@ -0,0 +1,50 @@ +package com.newrelic.api.agent.security.schema; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class SerializableClassDefinition { + + private String name; + + private boolean isInterface; + + private List fields; + + public SerializableClassDefinition(String name, boolean anInterface, List filedDefinitions) { + this.isInterface = anInterface; + this.name = name; + this.fields = filedDefinitions; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getFields() { + return fields; + } + + public void setFields(List fields) { + this.fields = fields; + } + + public boolean addFields(FiledDefinition field) { + return this.fields.add(field); + } + + public boolean getIsInterface() { + return isInterface; + } + + public void setIsInterface(boolean isInterface) { + this.isInterface = isInterface; + } + +} diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/VulnerabilityCaseType.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/VulnerabilityCaseType.java index d0ed563c3..8ab1e5b67 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/VulnerabilityCaseType.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/VulnerabilityCaseType.java @@ -73,7 +73,9 @@ public enum VulnerabilityCaseType { SOLR_DB_REQUEST("SOLR_DB_REQUEST"), /** Unsafe Deserialization */ - UNSAFE_DESERIALIZATION("UNSAFE_DESERIALIZATION"); + UNSAFE_DESERIALIZATION("UNSAFE_DESERIALIZATION"), + /** Reflection */ + REFLECTION("REFLECTION"); /** case type. */ diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserializationOperation.java similarity index 50% rename from newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java rename to newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserializationOperation.java index 901c48475..29792aeff 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserialisationOperation.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/DeserializationOperation.java @@ -1,37 +1,26 @@ package com.newrelic.api.agent.security.schema.operation; import com.newrelic.api.agent.security.NewRelicSecurity; -import com.newrelic.api.agent.security.schema.AbstractOperation; -import com.newrelic.api.agent.security.schema.DeserializationInfo; -import com.newrelic.api.agent.security.schema.StringUtils; -import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; +import com.newrelic.api.agent.security.schema.*; import java.util.Map; -public class DeserialisationOperation extends AbstractOperation { +public class DeserializationOperation extends AbstractOperation { private String entityName; private Map params; private DeserializationInfo rootDeserializationInfo; + private DeserializationInvocation deserializationInvocation; - public DeserialisationOperation(String className, String methodName) { + public DeserializationOperation(String className, String methodName) { super(className, methodName); - if (NewRelicSecurity.getAgent().getSecurityMetaData()!= null && - NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot()!=null) { - this.entityName = NewRelicSecurity.getAgent().getSecurityMetaData() - .peekDeserializationRoot().getType(); -// this.params = NewRelicSecurity.getAgent().getSecurityMetaData() -// .peekDeserializationRoot().computeObjectMap(); - this.rootDeserializationInfo = NewRelicSecurity.getAgent().getSecurityMetaData() - .peekDeserializationRoot(); - } this.setCaseType(VulnerabilityCaseType.UNSAFE_DESERIALIZATION); } @Override public boolean isEmpty() { - return this.rootDeserializationInfo==null || StringUtils.isEmpty(this.entityName); + return this.deserializationInvocation == null; } public String getEntityName() { @@ -57,4 +46,12 @@ public DeserializationInfo getRootDeserializationInfo() { public void setRootDeserializationInfo(DeserializationInfo rootDeserializationInfo) { this.rootDeserializationInfo = rootDeserializationInfo; } + + public DeserializationInvocation getDeserializationInvocation() { + return deserializationInvocation; + } + + public void setDeserializationInvocation(DeserializationInvocation deserializationInvocation) { + this.deserializationInvocation = deserializationInvocation; + } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/JavaReflectionOperation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/JavaReflectionOperation.java new file mode 100644 index 000000000..4a357c2d7 --- /dev/null +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/operation/JavaReflectionOperation.java @@ -0,0 +1,73 @@ +package com.newrelic.api.agent.security.schema.operation; + +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; + +import java.util.List; + +public class JavaReflectionOperation extends AbstractOperation { + + private String declaringClass; + + private String nameOfMethod; + + private Object[] args; + + private Object obj; + + private List declaredMethods; + + public JavaReflectionOperation(String className, String methodName, String declaringClass, String nameOfMethod,Object[] args, Object obj) { + super(className, methodName); + this.setCaseType(VulnerabilityCaseType.REFLECTION); + this.declaringClass = declaringClass; + this.nameOfMethod = nameOfMethod; + this.args = args; + this.obj = obj; + } + + public String getDeclaringClass() { + return declaringClass; + } + + public void setDeclaringClass(String declaringClass) { + this.declaringClass = declaringClass; + } + + public Object getObj() { + return obj; + } + + public void setObj(Object obj) { + this.obj = obj; + } + + public Object[] getArgs() { + return args; + } + + public void setArgs(Object[] args) { + this.args = args; + } + + public String getNameOfMethod() { + return nameOfMethod; + } + + public void setNameOfMethod(String nameOfMethod) { + this.nameOfMethod = nameOfMethod; + } + + public List getDeclaredMethods() { + return declaredMethods; + } + + public void setDeclaredMethods(List declaredMethods) { + this.declaredMethods = declaredMethods; + } + + @Override + public boolean isEmpty() { + return (declaringClass == null || declaringClass.trim().isEmpty()); + } +} diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/utils/ExecutionIDGenerator.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/utils/ExecutionIDGenerator.java similarity index 86% rename from newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/utils/ExecutionIDGenerator.java rename to newrelic-security-api/src/main/java/com/newrelic/api/agent/security/utils/ExecutionIDGenerator.java index 50f6b728a..700521681 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/utils/ExecutionIDGenerator.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/utils/ExecutionIDGenerator.java @@ -1,4 +1,4 @@ -package com.newrelic.agent.security.instrumentator.utils; +package com.newrelic.api.agent.security.utils; import java.util.concurrent.atomic.AtomicLong; diff --git a/settings.gradle b/settings.gradle index 7f9d0ddd5..ef175dd29 100644 --- a/settings.gradle +++ b/settings.gradle @@ -235,4 +235,5 @@ include 'instrumentation:http4s-ember-server-2.12_0.23' include 'instrumentation:http4s-ember-server-2.13_0.23' include 'instrumentation:http4s-ember-client-2.13_0.23' include 'instrumentation:http4s-ember-client-2.12_0.23' -include 'instrumentation:apache-pekko-http-core-2.13_1' \ No newline at end of file +include 'instrumentation:apache-pekko-http-core-2.13_1' +include 'instrumentation:java-reflection' \ No newline at end of file From 6540e78ca461929c6d40b0713088644281cc769c Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Fri, 11 Apr 2025 12:41:05 +0530 Subject: [PATCH 14/19] Deserialization event size reduction --- .../lang/reflect/Method_Instrumentation.java | 5 ++++ .../instrumentator/dispatcher/Dispatcher.java | 23 ++++++++++++++++++- .../security/schema/FiledDefinition.java | 5 ++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java b/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java index 5afcb9204..3cd202021 100644 --- a/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java +++ b/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java @@ -35,6 +35,11 @@ public Object invoke(Object obj, Object... args) { } private void registerExitOperation(AbstractOperation operation) { + if (operation == null || !NewRelicSecurity.isHookProcessingActive() || + NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() || GenericHelper.skipExistsEvent() + ) { + return; + } NewRelicSecurity.getAgent().registerExitEvent(operation); } diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java index b7bb70f5d..983a4691f 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java @@ -746,6 +746,7 @@ private static SerializableClassDefinition getClassDefinition(ObjectStreamClass boolean isPrimitive = field.isPrimitive(); boolean isTransient = false; List actualTypeArguments = null; + boolean containsClassOrObject = false; try { Field genericField = classType.getDeclaredField(name); Type genericType = genericField.getGenericType(); @@ -756,7 +757,25 @@ private static SerializableClassDefinition getClassDefinition(ObjectStreamClass for(Type typeArgument : typeArguments) { actualTypeArguments.add(typeArgument.getTypeName()); } + + containsClassOrObject = Arrays.stream(typeArguments) + .anyMatch(arg -> arg.getTypeName().equals(Class.class.getName()) || arg.getTypeName().equals(Object.class.getName())); + } + + Class fieldType = genericField.getType(); + + do { + if (fieldType.isArray()) { + Class componentType = fieldType.getComponentType(); + if (componentType.getName().equals(Class.class.getName()) || componentType.getName().equals(Object.class.getName())) { + containsClassOrObject = true; + } + } + fieldType = fieldType.getComponentType(); + } while (fieldType != null && fieldType.isArray()); + + isTransient = Modifier.isTransient(genericField.getModifiers()); } catch (NoSuchFieldException ignored) { } @@ -770,7 +789,9 @@ private static SerializableClassDefinition getClassDefinition(ObjectStreamClass filedDefinition.setClassDefinition(getClassDefinition(osc, field.getType(), fieldTypes)); } } - if (type.equals(Class.class.getName()) || type.equals(Object.class.getName()) + + + if (containsClassOrObject || type.equals(Class.class.getName()) || type.equals(Object.class.getName()) || (filedDefinition.getClassDefinition() != null && !filedDefinition.getClassDefinition().getFields().isEmpty())) { filedDefinitions.add(filedDefinition); } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/FiledDefinition.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/FiledDefinition.java index bc272935d..64229ca81 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/FiledDefinition.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/FiledDefinition.java @@ -1,5 +1,7 @@ package com.newrelic.api.agent.security.schema; +import com.newrelic.api.agent.security.schema.annotations.JsonIgnore; + import java.util.List; public class FiledDefinition { @@ -8,12 +10,15 @@ public class FiledDefinition { private String type; + @JsonIgnore private List parameterizedType; + @JsonIgnore private boolean isPrimitive; private boolean isTransient; + @JsonIgnore private boolean isSerializable; private SerializableClassDefinition classDefinition; From f077121158000e1ce9c24c9fed40440e0b9d75d5 Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Fri, 11 Apr 2025 12:42:16 +0530 Subject: [PATCH 15/19] update json version to 1.2.11 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 95e22a3e4..4bd015446 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # The agent version. agentVersion=1.6.0 -jsonVersion=1.2.10 +jsonVersion=1.2.11 # Updated exposed NR APM API version. nrAPIVersion=8.12.0 From 8e00a41469c68f0484a9d3b68259c3e9a0855192 Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Mon, 14 Apr 2025 12:25:36 +0530 Subject: [PATCH 16/19] Remove deserialization context and add category reflection to Skip Scan --- .../io/ObjectInputStream_Instrumentation.java | 3 +- .../newrelic/agent/security/AgentConfig.java | 2 ++ .../agent/security/util/IUtilConstants.java | 2 ++ .../newrelic/api/agent/security/Agent.java | 1 - .../helpers/GenericHelper.java | 3 ++ .../agent/security/schema/AgentMetaData.java | 12 ------- .../schema/DeserialisationContext.java | 36 ------------------- .../schema/DeserializationInvocation.java | 13 +------ .../schema/policy/IastDetectionCategory.java | 13 +++++++ 9 files changed, 22 insertions(+), 63 deletions(-) delete mode 100644 newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserialisationContext.java diff --git a/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java b/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java index f54f81d12..f40c2619b 100644 --- a/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java +++ b/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java @@ -70,8 +70,7 @@ private final Object readObject(Class type) this.getClass().getName(), ObjectInputStreamHelper.METHOD_NAME_READ_OBJECT ); - DeserialisationContext deserialisationContext = new DeserialisationContext(type.getName(), Arrays.copyOfRange(Thread.currentThread().getStackTrace(), 1, 10)); - deserializationInvocation = new DeserializationInvocation(true, operation.getExecutionId(), deserialisationContext); + deserializationInvocation = new DeserializationInvocation(true, operation.getExecutionId()); NewRelicSecurity.getAgent().getSecurityMetaData().setDeserializationInvocation(deserializationInvocation); operation.setDeserializationInvocation(deserializationInvocation); // NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(InstrumentationConstants.ACTIVE_DESERIALIZATION, true); 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 264e32c37..5c9d270fe 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 @@ -193,6 +193,8 @@ private void readSkipScan() throws RestrictionModeException { agentMode.getSkipScan().getIastDetectionCategory().setXpathInjectionEnabled(NewRelic.getAgent().getConfig().getValue(SKIP_XPATH_INJECTION, false)); agentMode.getSkipScan().getIastDetectionCategory().setSsrfEnabled(NewRelic.getAgent().getConfig().getValue(SKIP_SSRF, false)); agentMode.getSkipScan().getIastDetectionCategory().setRxssEnabled(NewRelic.getAgent().getConfig().getValue(SKIP_RXSS, false)); + agentMode.getSkipScan().getIastDetectionCategory().setUnsafeDeserializationEnabled(NewRelic.getAgent().getConfig().getValue(SKIP_UNSAFE_DESERIALIZATION, false)); + agentMode.getSkipScan().getIastDetectionCategory().setInsecureReflectionEnabled(NewRelic.getAgent().getConfig().getValue(SKIP_INSECURE_REFLECTION, false)); if(!agentMode.getSkipScan().getIastDetectionCategory().getRxssEnabled() && !NewRelic.getAgent().getConfig().getValue(REPORT_HTTP_RESPONSE_BODY, true)) { agentMode.getSkipScan().getIastDetectionCategory().setRxssEnabled(true); } diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/util/IUtilConstants.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/util/IUtilConstants.java index 503d1af93..befb0b4ef 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/util/IUtilConstants.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/util/IUtilConstants.java @@ -32,6 +32,8 @@ public interface IUtilConstants { String SKIP_XPATH_INJECTION = SKIP_IAST_SCAN_PARAMETERS_IAST_DETECTION_CATEGORY + ".xpath_injection"; String SKIP_SSRF = SKIP_IAST_SCAN_PARAMETERS_IAST_DETECTION_CATEGORY + ".ssrf"; String SKIP_RXSS = SKIP_IAST_SCAN_PARAMETERS_IAST_DETECTION_CATEGORY + ".rxss"; + String SKIP_UNSAFE_DESERIALIZATION = SKIP_IAST_SCAN_PARAMETERS_IAST_DETECTION_CATEGORY + ".unsafe_deserialization"; + String SKIP_INSECURE_REFLECTION = SKIP_IAST_SCAN_PARAMETERS_IAST_DETECTION_CATEGORY + ".insecure_reflection"; String RESTRICTION_CRITERIA_SCAN_TIME_SCHEDULE = "security.restriction_criteria.scan_time.schedule"; String RESTRICTION_CRITERIA_SCAN_TIME_DURATION = "security.restriction_criteria.scan_time.duration"; diff --git a/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java b/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java index ff4c0b5f1..5c34e801f 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java @@ -459,7 +459,6 @@ public void registerOperation(AbstractOperation operation) { } processStackTrace(operation); if(securityMetaData.getDeserializationInvocation() != null && securityMetaData.getDeserializationInvocation().getActive()){ - securityMetaData.getMetaData().setDeserialisationContext(securityMetaData.getDeserializationInvocation().getDeserialisationContext()); securityMetaData.getMetaData().addLinkedEventId(operation.getExecutionId()); securityMetaData.getMetaData().setParentEventId(securityMetaData.getDeserializationInvocation().getEid()); securityMetaData.getMetaData().setTriggerViaDeserialisation(true); diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/GenericHelper.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/GenericHelper.java index 6cc61f8f6..99910e5ea 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/GenericHelper.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/GenericHelper.java @@ -107,6 +107,9 @@ private static boolean isLockAcquirePossible(VulnerabilityCaseType caseType) { case UNSAFE_DESERIALIZATION: enabled = NewRelicSecurity.getAgent().getIastDetectionCategory().getUnsafeDeserializationEnabled(); break; + case REFLECTION: + enabled = NewRelicSecurity.getAgent().getIastDetectionCategory().getInsecureReflectionEnabled(); + break; default: break; } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java index c16d448bd..2011b696f 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/AgentMetaData.java @@ -53,8 +53,6 @@ public class AgentMetaData { private String parentEventId; - private DeserialisationContext deserialisationContext; - public AgentMetaData() { this.rciMethodsCalls = new HashSet<>(); this.ips = new HashSet<>(); @@ -88,9 +86,6 @@ public AgentMetaData(AgentMetaData agentMetaData) { this.skipScanParameters = agentMetaData.skipScanParameters; this.linkedEventIds = new HashSet<>(agentMetaData.linkedEventIds); this.parentEventId = agentMetaData.parentEventId; - if(deserialisationContext != null) { - this.deserialisationContext = new DeserialisationContext(agentMetaData.deserialisationContext); - } } public boolean isTriggerViaRCI() { @@ -270,11 +265,4 @@ public void setParentEventId(String parentEventId) { this.parentEventId = parentEventId; } - public DeserialisationContext getDeserialisationContext() { - return deserialisationContext; - } - - public void setDeserialisationContext(DeserialisationContext deserialisationContext) { - this.deserialisationContext = deserialisationContext; - } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserialisationContext.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserialisationContext.java deleted file mode 100644 index 0ab2647ff..000000000 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserialisationContext.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.newrelic.api.agent.security.schema; - -import java.util.Arrays; - -public class DeserialisationContext { - - private String entityName; - - private StackTraceElement[] stacktrace; - - public DeserialisationContext(String entityName, StackTraceElement[] stacktrace) { - this.entityName = entityName; - this.stacktrace = stacktrace; - } - - public DeserialisationContext(DeserialisationContext deserialisationContext) { - this.entityName = deserialisationContext.getEntityName(); - this.stacktrace = Arrays.copyOf(deserialisationContext.getStacktrace(), deserialisationContext.getStacktrace().length); - } - - public String getEntityName() { - return entityName; - } - - public void setEntityName(String entityName) { - this.entityName = entityName; - } - - public StackTraceElement[] getStacktrace() { - return stacktrace; - } - - public void setStacktrace(StackTraceElement[] stacktrace) { - this.stacktrace = stacktrace; - } -} diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInvocation.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInvocation.java index fc8fbbc2b..ad88c92c9 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInvocation.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/DeserializationInvocation.java @@ -14,12 +14,9 @@ public class DeserializationInvocation { private Map encounteredSerializable; - private DeserialisationContext deserialisationContext; - - public DeserializationInvocation(boolean active, String executionId, DeserialisationContext deserialisationContext) { + public DeserializationInvocation(boolean active, String executionId) { this.active = active; this.eid = executionId; - this.deserialisationContext = deserialisationContext; this.readObjectInAction = new Stack<>(); encounteredSerializable = new HashMap<>(); } @@ -78,12 +75,4 @@ public String peekReadObjectInAction() { } return this.readObjectInAction.peek(); } - - public DeserialisationContext getDeserialisationContext() { - return deserialisationContext; - } - - public void setDeserialisationContext(DeserialisationContext deserialisationContext) { - this.deserialisationContext = deserialisationContext; - } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/policy/IastDetectionCategory.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/policy/IastDetectionCategory.java index a8e23c963..89497f00d 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/policy/IastDetectionCategory.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/policy/IastDetectionCategory.java @@ -17,6 +17,7 @@ public class IastDetectionCategory { Boolean xpathInjectionEnabled = false; Boolean ssrfEnabled = false; Boolean unsafeDeserializationEnabled = false; + Boolean insecureReflection = false; @JsonIgnore private String disabledCategoriesCSV; @@ -162,6 +163,10 @@ public void generateDisabledCategoriesCSV() { disabledCategoriesCSVBuilder.append(VulnerabilityCaseType.UNSAFE_DESERIALIZATION); disabledCategoriesCSVBuilder.append(STR_COMMA); } + if (insecureReflection) { + disabledCategoriesCSVBuilder.append(VulnerabilityCaseType.REFLECTION); + disabledCategoriesCSVBuilder.append(STR_COMMA); + } if (disabledCategoriesCSVBuilder.length() > 0) { disabledCategoriesCSVBuilder.deleteCharAt(disabledCategoriesCSVBuilder.length() - 1); } @@ -179,4 +184,12 @@ public Boolean getUnsafeDeserializationEnabled() { public void setUnsafeDeserializationEnabled(Boolean unsafeDeserializationEnabled) { this.unsafeDeserializationEnabled = unsafeDeserializationEnabled; } + + public Boolean getInsecureReflectionEnabled() { + return insecureReflection; + } + + public void setInsecureReflectionEnabled(Boolean insecureReflection) { + this.insecureReflection = insecureReflection; + } } From 27391e2bf64826c34c77220cab38de6720ddb919 Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Tue, 15 Apr 2025 09:52:34 +0530 Subject: [PATCH 17/19] Remove hard check on deserialization value --- .../security/instrumentator/dispatcher/Dispatcher.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java index 983a4691f..31484ec8d 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/dispatcher/Dispatcher.java @@ -716,7 +716,11 @@ private static JavaAgentEventBean prepareDeserializationEvent(JavaAgentEventBean JSONObject deserializationInfo = new JSONObject(); if(rootDeserializationInfo != null) { deserializationInfo.put("type", rootDeserializationInfo.getType()); - deserializationInfo.put("value", JsonConverter.getObjectMapper().writeValueAsString(rootDeserializationInfo.getInstance())); + try { + deserializationInfo.put("value", JsonConverter.getObjectMapper().writeValueAsString(rootDeserializationInfo.getInstance())); + } catch (Exception ignored) { + logger.log(LogLevel.FINEST, String.format("Unable to stringify the Object %s", rootDeserializationInfo.getInstance()), Dispatcher.class.getName()); + } } Set fieldTypes = new HashSet<>(); fieldTypes.addAll(deserializationOperation.getDeserializationInvocation().getEncounteredSerializable().keySet()); From ef528f6b6a8c0bfa4ffd8ea2d179baa44848e27e Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Tue, 15 Apr 2025 16:39:56 +0530 Subject: [PATCH 18/19] Remove unused Class and sysouts --- .../csec/validator/scanner/DeltaClass.java | 22 ------------------- .../io/ObjectInputStream_Instrumentation.java | 8 +++---- .../lang/reflect/Method_Instrumentation.java | 2 +- 3 files changed, 5 insertions(+), 27 deletions(-) delete mode 100644 instrumentation-security/deserialisation/src/main/java/com/newrelic/csec/validator/scanner/DeltaClass.java diff --git a/instrumentation-security/deserialisation/src/main/java/com/newrelic/csec/validator/scanner/DeltaClass.java b/instrumentation-security/deserialisation/src/main/java/com/newrelic/csec/validator/scanner/DeltaClass.java deleted file mode 100644 index b578fde1e..000000000 --- a/instrumentation-security/deserialisation/src/main/java/com/newrelic/csec/validator/scanner/DeltaClass.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.newrelic.csec.validator.scanner; - -import java.io.Serializable; - -public class DeltaClass implements Serializable { - - private static final long serialVersionUID = 5220788560470736671L; - - public String field0; - - public DeltaClass(String field0) { - this.field0 = field0; - } - - public String getField0() { - return field0; - } - - public void setField0(String field0) { - this.field0 = field0; - } -} diff --git a/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java b/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java index f40c2619b..e57ac907a 100644 --- a/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java +++ b/instrumentation-security/deserialisation/src/main/java/java/io/ObjectInputStream_Instrumentation.java @@ -17,7 +17,9 @@ public abstract class ObjectInputStream_Instrumentation { private void readSerialData(Object obj, ObjectStreamClass desc) throws IOException { - DeserializationInfo dInfo = preProcessSecurityHook(obj); + if(NewRelicSecurity.isHookProcessingActive()) { + DeserializationInfo dInfo = preProcessSecurityHook(obj); + } Weaver.callOriginal(); } @@ -93,11 +95,9 @@ private final Object readObject(Class type) } private void processFilterCheck(Class clazz, boolean filterCheck) { - System.out.println("lock acquired Filter check "); + DeserializationInvocation deserializationInvocation = NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation(); - System.out.println("Filter check deserializationInvocation : "+deserializationInvocation); if(deserializationInvocation != null && clazz != null) { - System.out.println("Filter check for class : "+clazz.getName()+" is deserializable : "+filterCheck); com.newrelic.api.agent.security.schema.Serializable serializable = deserializationInvocation.getEncounteredSerializableByName(clazz.getName()); if(serializable == null) { serializable = new Serializable(clazz.getName(), true); diff --git a/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java b/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java index 3cd202021..dad0f7e78 100644 --- a/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java +++ b/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java @@ -45,7 +45,7 @@ private void registerExitOperation(AbstractOperation operation) { private AbstractOperation preprocessSecurityHook(Object obj, Class declaringClass, Class[] parameterTypes, String name, Object[] args) { try { - if (NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation() != null && NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation().getActive()) { + if (!NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() && NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation() != null && NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation().getActive()) { if(StringUtils.isNotBlank(NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation().peekReadObjectInAction()) && !StringUtils.equals(name, "readObject")) { JavaReflectionOperation operation = new JavaReflectionOperation(this.getClass().getName(), "invoke", declaringClass.getName(), name, args, obj); From 381d49a19d28569cff096ec9ff7c180c6d623ab7 Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Wed, 16 Apr 2025 17:34:17 +0530 Subject: [PATCH 19/19] Generate all reflection events --- .../lang/reflect/Method_Instrumentation.java | 26 +++++++++---------- .../agent/security/util/IUtilConstants.java | 2 +- .../helpers/GenericHelper.java | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java b/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java index dad0f7e78..ac7868d0f 100644 --- a/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java +++ b/instrumentation-security/java-reflection/src/main/java/java/lang/reflect/Method_Instrumentation.java @@ -4,6 +4,7 @@ import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; import com.newrelic.api.agent.security.schema.AbstractOperation; import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; import com.newrelic.api.agent.security.schema.operation.JavaReflectionOperation; import com.newrelic.api.agent.security.utils.logging.LogLevel; @@ -45,21 +46,20 @@ private void registerExitOperation(AbstractOperation operation) { private AbstractOperation preprocessSecurityHook(Object obj, Class declaringClass, Class[] parameterTypes, String name, Object[] args) { try { - if (!NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() && NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation() != null && NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation().getActive()) { - if(StringUtils.isNotBlank(NewRelicSecurity.getAgent().getSecurityMetaData().getDeserializationInvocation().peekReadObjectInAction()) - && !StringUtils.equals(name, "readObject")) { - JavaReflectionOperation operation = new JavaReflectionOperation(this.getClass().getName(), "invoke", declaringClass.getName(), name, args, obj); - List methodNames = new ArrayList<>(); - for (Method method : declaringClass.getDeclaredMethods()) { - if(Arrays.equals(method.getParameterTypes(), parameterTypes)) { - methodNames.add(method.getName()); - } - } - operation.setDeclaredMethods(methodNames); - NewRelicSecurity.getAgent().registerOperation(operation); - return operation; + if(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() || !GenericHelper.isLockAcquirePossible(VulnerabilityCaseType.REFLECTION)) { + return null; + } + + JavaReflectionOperation operation = new JavaReflectionOperation(this.getClass().getName(), "invoke", declaringClass.getName(), name, args, obj); + List methodNames = new ArrayList<>(); + for (Method method : declaringClass.getDeclaredMethods()) { + if(Arrays.equals(method.getParameterTypes(), parameterTypes)) { + methodNames.add(method.getName()); } } + operation.setDeclaredMethods(methodNames); + NewRelicSecurity.getAgent().registerOperation(operation); + return operation; } catch (Throwable e) { if(e instanceof NewRelicSecurityException){ NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, "JAVA-REFLECTION", e.getMessage()), e, Method_Instrumentation.class.getName()); diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/util/IUtilConstants.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/util/IUtilConstants.java index befb0b4ef..4c08bb3f9 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/util/IUtilConstants.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/util/IUtilConstants.java @@ -33,7 +33,7 @@ public interface IUtilConstants { String SKIP_SSRF = SKIP_IAST_SCAN_PARAMETERS_IAST_DETECTION_CATEGORY + ".ssrf"; String SKIP_RXSS = SKIP_IAST_SCAN_PARAMETERS_IAST_DETECTION_CATEGORY + ".rxss"; String SKIP_UNSAFE_DESERIALIZATION = SKIP_IAST_SCAN_PARAMETERS_IAST_DETECTION_CATEGORY + ".unsafe_deserialization"; - String SKIP_INSECURE_REFLECTION = SKIP_IAST_SCAN_PARAMETERS_IAST_DETECTION_CATEGORY + ".insecure_reflection"; + String SKIP_UNSAFE_REFLECTION = SKIP_IAST_SCAN_PARAMETERS_IAST_DETECTION_CATEGORY + ".unsafe_reflection"; String RESTRICTION_CRITERIA_SCAN_TIME_SCHEDULE = "security.restriction_criteria.scan_time.schedule"; String RESTRICTION_CRITERIA_SCAN_TIME_DURATION = "security.restriction_criteria.scan_time.duration"; diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/GenericHelper.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/GenericHelper.java index 99910e5ea..fdbc0bdbe 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/GenericHelper.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/GenericHelper.java @@ -52,7 +52,7 @@ public static boolean isLockAcquired(String nrSecCustomAttrName, int hashCode) { return false; } - private static boolean isLockAcquirePossible(VulnerabilityCaseType caseType) { + public static boolean isLockAcquirePossible(VulnerabilityCaseType caseType) { if (!NewRelicSecurity.isHookProcessingActive()){ return false; }