Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Deserialisation Event Generation : vulnerability detection #395

Open
wants to merge 21 commits into
base: feature/include-http-response
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7bd7d3b
Initial changes for proof backed deserialization attack detection
AnupamJuniwal Sep 6, 2023
a8bff23
Merge branch 'develop' into task/deserialisation_improvement_poc
AnupamJuniwal Sep 7, 2023
cac032e
This contains code cleanup and multiple fixes as required to gather m…
AnupamJuniwal Sep 13, 2023
81345c0
Merge branch 'develop' into task/deserialisation_improvement_poc
AnupamJuniwal Sep 26, 2023
9fff3b7
Code cleanup
AnupamJuniwal Sep 26, 2023
c17d614
This contains changes for deserializationInfo to be managed with one …
AnupamJuniwal Oct 18, 2023
58fa096
minor fix for NPE handling
AnupamJuniwal Oct 30, 2023
f58a5c8
Merge branch 'develop' into task/deserialisation_improvement_poc
AnupamJuniwal Nov 21, 2023
4cd631f
Refactoring for java deserialization hook
AnupamJuniwal Nov 27, 2023
ee8d3f3
Added todo note for object deserialiation
AnupamJuniwal Jan 13, 2025
93d10f0
Merge branch 'refs/heads/main' into task/deserialisation_improvement_poc
lovesh-ap Jan 13, 2025
12559be
Deserialization POC without reflection
lovesh-ap Jan 15, 2025
1e115e0
Merge branch 'refs/heads/main' into task/deserialisation_improvement_poc
lovesh-ap Feb 4, 2025
d72a9ac
change parameter schema for Deserialization event
lovesh-ap Feb 7, 2025
bfb2acd
Bump json version to 1.2.11
IshikaDawda Feb 11, 2025
fd3d572
Prodcutisation of DeserializationInfo
lovesh-ap Feb 24, 2025
9ef161e
Merge branch 'refs/heads/feature/include-http-response' into task/des…
lovesh-ap Feb 25, 2025
4577300
Add UNSAFE_DESERIALIZATION as a new category
lovesh-ap Feb 26, 2025
90f0324
Introduce Deserialization Vulnerability detection and
lovesh-ap Apr 11, 2025
6540e78
Deserialization event size reduction
lovesh-ap Apr 11, 2025
f077121
update json version to 1.2.11
lovesh-ap Apr 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -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

Expand Down
16 changes: 16 additions & 0 deletions instrumentation-security/deserialisation/build.gradle
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package java.io;

public class ObjectInputStreamHelper {
public static final String METHOD_NAME_READ_OBJECT = "readObject";

public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "UNSAFE-DESERIALISATION-LOCK-JAVA-IO-%s-";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
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.*;
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.BaseClass, originalName = "java.io.ObjectInputStream")
public abstract class ObjectInputStream_Instrumentation {

private void readSerialData(Object obj, ObjectStreamClass desc)
throws IOException {
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();
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) {
processResolveClass(desc, returnValue);
GenericHelper.releaseLock(String.format(ObjectInputStreamHelper.NR_SEC_CUSTOM_ATTRIB_NAME, "resolve"));
}
}
return returnValue;
}

private DeserializationInfo preProcessSecurityHook(Object obj) {
DeserializationInfo dInfo = new DeserializationInfo(obj.getClass().getName(), obj);
NewRelicSecurity.getAgent().getSecurityMetaData().addToDeserializationRoot(dInfo);
return dInfo;
}


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(),
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);
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"));
}
}
}

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(String operation) {
return GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.UNSAFE_DESERIALIZATION, String.format(ObjectInputStreamHelper.NR_SEC_CUSTOM_ATTRIB_NAME, operation));
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
}
16 changes: 16 additions & 0 deletions instrumentation-security/java-reflection/build.gradle
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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) {
if (operation == null || !NewRelicSecurity.isHookProcessingActive() ||
NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() || GenericHelper.skipExistsEvent()
) {
return;
}
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<String> 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;
}
}
Loading
Loading