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 18 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
18 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
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,66 @@
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;
import com.newrelic.api.agent.weaver.Weaver;


@Weave(type = MatchType.ExactClass, 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);
}
try {
Weaver.callOriginal();
operation = postProcessSecurityHook(dInfo);
} finally {
if(isLockAcquired) {
finalProcessSecurityHook(dInfo);
GenericHelper.releaseLock(SecurityHelper.NR_SEC_CUSTOM_ATTRIB_NAME);
}
}
//TODO add register exit operation if required
}

private DeserializationInfo preProcessSecurityHook(Object obj) {
DeserializationInfo dInfo = new DeserializationInfo(obj.getClass().getName(), obj);
NewRelicSecurity.getAgent().getSecurityMetaData().addToDeserializationRoot(dInfo);
return 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.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);
}
}
Original file line number Diff line number Diff line change
@@ -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-";

}
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ public Object call() throws Exception {
SolrDbOperation solrDbOperation = (SolrDbOperation) operation;
eventBean = prepareSolrDbRequestEvent(eventBean, solrDbOperation);
break;
case UNSAFE_DESERIALIZATION:
prepareDeserializationEvent(eventBean, (DeserialisationOperation) operation);
default:

}
Expand Down Expand Up @@ -653,6 +655,20 @@ private static JavaAgentEventBean prepareSSRFEvent(JavaAgentEventBean eventBean,
return eventBean;
}

private static JavaAgentEventBean prepareDeserializationEvent(JavaAgentEventBean eventBean,
DeserialisationOperation deserialisationOperation) {
DeserializationInfo rootDeserializationInfo = deserialisationOperation.getRootDeserializationInfo();
JSONArray params = new JSONArray();
if(rootDeserializationInfo != null) {
JSONObject deserializationInfo = new JSONObject();
deserializationInfo.put("type", rootDeserializationInfo.getType());
deserializationInfo.put("value", GsonUtil.toJson(rootDeserializationInfo.getInstance()));
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++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = "";
Expand All @@ -25,6 +27,8 @@ public abstract class AbstractOperation {

private boolean isLowSeverityHook;

private DeserializationInfo deserializationInfo;

public AbstractOperation() {
this.className = EMPTY;
this.sourceMethod = EMPTY;
Expand All @@ -36,9 +40,16 @@ 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;
if (NewRelicSecurity.getAgent() != null &&
NewRelicSecurity.getAgent().getSecurityMetaData() != null &&
NewRelicSecurity.getAgent().getSecurityMetaData().peekDeserializationRoot() != null) {
this.deserializationInfo = NewRelicSecurity.getAgent().getSecurityMetaData()
.peekDeserializationRoot();
// this.deserializationInfo.computeObjectMap();
}
}

public String getClassName() {
Expand Down Expand Up @@ -135,4 +146,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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,5 @@ public SkipScanParameters getSkipScanParameters() {
public void setSkipScanParameters(SkipScanParameters skipScanParameters) {
this.skipScanParameters = skipScanParameters;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.newrelic.api.agent.security.schema;

import java.util.ArrayList;
import java.util.List;


public class DeserializationInfo {

private String type;
private List<DeserializationInfo> unlinkedChildren = new ArrayList<>();
private Object instance;

public DeserializationInfo(String type, Object instance) {
this.type = type;
this.instance = instance;
}

public DeserializationInfo(DeserializationInfo instance) {
if (instance == null) {
return;
}
this.type = instance.type;
// for(DeserializationInfo value: instance.unlinkedChildren){
// value.computeObjectMap();
// this.unlinkedChildren.add(new DeserializationInfo(value));
// }
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public Object getInstance() {
return instance;
}

public void setInstance(Object instance) {
this.instance = instance;
}

public List<DeserializationInfo> getUnlinkedChildren() {
return unlinkedChildren;
}

public void setUnlinkedChildren(List<DeserializationInfo> unlinkedChildren) {
this.unlinkedChildren = unlinkedChildren;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,20 @@ public void clearCustomAttr(){
customData.clear();
}

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);
}
}

public void resetDeserializationRoot() {
removeCustomAttribute("deserializationRoot");
}

public DeserializationInfo peekDeserializationRoot() {
return getCustomAttribute("deserializationRoot", DeserializationInfo.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ public enum VulnerabilityCaseType {

CACHING_DATA_STORE("CACHING_DATA_STORE"),

SOLR_DB_REQUEST("SOLR_DB_REQUEST");
SOLR_DB_REQUEST("SOLR_DB_REQUEST"),
/** Unsafe Deserialization */
UNSAFE_DESERIALIZATION("UNSAFE_DESERIALIZATION");


/** case type. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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<String, DeserializationInfo> params;
private DeserializationInfo rootDeserializationInfo;


public DeserialisationOperation(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);
}

public String getEntityName() {
return entityName;
}

public void setEntityName(String entityName) {
this.entityName = entityName;
}

public Map<String, DeserializationInfo> getParams() {
return params;
}

public void setParams(Map<String, DeserializationInfo> params) {
this.params = params;
}

public DeserializationInfo getRootDeserializationInfo() {
return rootDeserializationInfo;
}

public void setRootDeserializationInfo(DeserializationInfo rootDeserializationInfo) {
this.rootDeserializationInfo = rootDeserializationInfo;
}
}
Loading