Skip to content

Commit

Permalink
Merge pull request #210 from newrelic/release/v1.1.3
Browse files Browse the repository at this point in the history
CSEC Java Agent Release Version 1.2.0
  • Loading branch information
lovesh-ap authored Mar 28, 2024
2 parents c9b7742 + 1951ac3 commit 26438d1
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 17 deletions.
9 changes: 9 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ Noteworthy changes to the agent are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.2.0] - 2024-3-28
### Changes
- Json Version bump to 1.2.0 due to [NR-235776](https://new-relic.atlassian.net/browse/NR-235776) implementation.
- [NR-234886](https://new-relic.atlassian.net/browse/NR-234886) IAST replay header decryption due to Security Findings [PR-207](https://github.com/newrelic/csec-java-agent/pull/207)

### Fixes
- [NR-253538](https://new-relic.atlassian.net/browse/NR-253538) Fix issue related to the instrumentation of the Rhino JavaScript Engine that occurred while reading the script. [PR-211](https://github.com/newrelic/csec-java-agent/pull/211)

## [1.1.2] - 2024-3-11
### Changes
- [NR-174177](https://new-relic.atlassian.net/browse/NR-174177) Ning Async HTTP client Support: The security agent now also supports com.ning:async-http-client 1.0.0 and above [PR-152](https://github.com/newrelic/csec-java-agent/pull/152), [PR-118](https://github.com/newrelic/csec-java-agent/pull/118), [PR-116](https://github.com/newrelic/csec-java-agent/pull/116)
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# The agent version.
agentVersion=1.1.2
jsonVersion=1.1.1
agentVersion=1.2.0
jsonVersion=1.2.0
# Updated exposed NR APM API version.
nrAPIVersion=8.4.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ private Object compileImpl(Scriptable scope, Reader sourceReader, String sourceS
if (sourceString!=null) {
newScript = new StringBuilder(sourceString);
} else if (sourceReader!=null && StringUtils.isBlank(newScript)) {
newScript = new StringBuilder("");
int data = sourceReader.read();
while (data != -1) {
newScript.append((char)data);
data = sourceReader.read();
}
//TODO can't read reader we need to instrument Reader class
}
} catch (Throwable e) {
if (e instanceof NewRelicSecurityException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,25 @@
import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException;
import com.newrelic.api.agent.security.schema.operation.JSInjectionOperation;
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 com.newrelic.agent.security.instrumentation.rhino.JSEngineUtils;

@Weave(originalName = "org.mozilla.javascript.ScriptRuntime")
@Weave(type = MatchType.ExactClass, originalName = "org.mozilla.javascript.ScriptRuntime")
public class ScriptRuntime_Instrumentation {

// TODO: changes for parameterized function calls in js script
public static Object doTopCall(Callable callable, Context_Instrumentation cx, Scriptable scope, Scriptable thisObj, Object[] args){
int code = cx.hashCode();
boolean isLockAcquired = acquireLockIfPossible(code);
boolean isLockAcquired = false;
int code = 0;
AbstractOperation operation = null;
if(isLockAcquired) {
operation = preprocessSecurityHook(code, JSEngineUtils.METHOD_EXEC, cx);
if(cx != null) {
code = cx.hashCode();
isLockAcquired = acquireLockIfPossible(code);
if (isLockAcquired) {
operation = preprocessSecurityHook(code, JSEngineUtils.METHOD_EXEC, cx);
}
}

Object returnVal = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ public static String getSHA256HexDigest(List<String> data) {
String input = StringUtils.join(data);
return getChecksum(input);
}
public static String getSHA256HexDigest(String data) {
String input = StringUtils.join(data);
return getChecksum(input);
}

/**
* Gets the xxHash64 hex digest.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.newrelic.agent.security.intcodeagent.utils;

import com.newrelic.agent.security.AgentInfo;
import com.newrelic.agent.security.instrumentator.utils.HashGenerator;
import com.newrelic.api.agent.security.NewRelicSecurity;
import com.newrelic.api.agent.security.utils.logging.LogLevel;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;

public class EncryptorUtils {
public static final String PBKDF_2_WITH_HMAC_SHA_1 = "PBKDF2WithHmacSHA1";
public static final String AES_CBC_PKCS_5_PADDING = "AES/CBC/PKCS5Padding";
public static final String AES = "AES";
private static final int ITERATION = 1024;
private static final int KEY_LEN = 256;
private static final int OFFSET = 16;
private static final String ERROR_WHILE_GENERATING_REQUIRED_SALT_FROM_S_S = "Error while generating required salt from %s : %s";
private static final String ERROR_WHILE_DECRYPTION = "Error while decryption %s : %s ";
private static final String ENCRYPTED_DATA_S_DECRYPTED_DATA_S = "Encrypted Data : %s , Decrypted data %s ";
public static final String INCORRECT_SECRET_PROVIDED_S_S = "Incorrect Password / salt provided : %s";
public static final String EMPTY_PASSWORD_PROVIDED_S = "Empty Password provided %s";
public static final String DATA_TO_BE_DECRYPTED_IS_EMPTY_S = "Data to be decrypted is Empty %s";

public static String decrypt(String password, String encryptedData) {
String decryptedData;
if (StringUtils.isBlank(password)){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(EMPTY_PASSWORD_PROVIDED_S, password), EncryptorUtils.class.getName());
return null;
}
if (StringUtils.isBlank(encryptedData)){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(DATA_TO_BE_DECRYPTED_IS_EMPTY_S, encryptedData), EncryptorUtils.class.getName());
return null;
}
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_2_WITH_HMAC_SHA_1);
KeySpec spec = new PBEKeySpec(password.toCharArray(), generateSalt(password), ITERATION, KEY_LEN);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), AES);

Cipher cipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING);
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[OFFSET];
secureRandom.nextBytes(iv);
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));

// Decrypt the content
byte[] decryptedBytes = cipher.doFinal(Hex.decodeHex(encryptedData));

decryptedData = new String(decryptedBytes, OFFSET, decryptedBytes.length - OFFSET);
NewRelicSecurity.getAgent().log(LogLevel.FINEST, String.format(ENCRYPTED_DATA_S_DECRYPTED_DATA_S, encryptedData, decryptedData), EncryptorUtils.class.getName());
return decryptedData;
} catch (DecoderException ignored) {

} catch (InvalidAlgorithmParameterException e) {
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(INCORRECT_SECRET_PROVIDED_S_S, e.getMessage()), EncryptorUtils.class.getName());
} catch (Exception e) {
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(ERROR_WHILE_DECRYPTION, encryptedData, e.getMessage()), EncryptorUtils.class.getName());
}
return null;
}

public static boolean verifyHashData(String knownDecryptedDataHash, String decryptedData) {
if (StringUtils.isBlank(decryptedData)){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format("Decrypted Data is empty %s", decryptedData), EncryptorUtils.class.getName());
return false;
}
if (StringUtils.isBlank(knownDecryptedDataHash)){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format("Known-Decrypted Data Hash is empty %s", knownDecryptedDataHash), EncryptorUtils.class.getName());
return false;
}
return StringUtils.equals(HashGenerator.getSHA256HexDigest(decryptedData), knownDecryptedDataHash);
}

private static byte[] generateSalt(String salt) throws DecoderException {
try {
return Hex.decodeHex(String.valueOf(Hex.encodeHex(StringUtils.left(salt, OFFSET).getBytes())));
} catch (DecoderException e) {
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(ERROR_WHILE_GENERATING_REQUIRED_SALT_FROM_S_S, salt, e.getMessage()), EncryptorUtils.class.getName());
NewRelicSecurity.getAgent().reportIncident(LogLevel.WARNING, String.format(ERROR_WHILE_GENERATING_REQUIRED_SALT_FROM_S_S, salt, e.getMessage()), e, EncryptorUtils.class.getName());
throw e;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.newrelic.agent.security.intcodeagent.constants.AgentServices;
import com.newrelic.agent.security.intcodeagent.filelogging.FileLoggerThreadPool;
import com.newrelic.agent.security.intcodeagent.filelogging.LogFileHelper;
import com.newrelic.agent.security.intcodeagent.utils.EncryptorUtils;
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;
Expand Down Expand Up @@ -657,4 +658,15 @@ public void retransformUninstrumentedClass(Class<?> classToRetransform) {
NewRelic.getAgent().getLogger().log(Level.FINER, "Class ", classToRetransform, " already instrumented.");
}
}

@Override
public String decryptAndVerify(String encryptedData, String hashVerifier) {
String decryptedData = EncryptorUtils.decrypt(AgentInfo.getInstance().getLinkingMetadata().get(INRSettingsKey.NR_ENTITY_GUID), encryptedData);
if(EncryptorUtils.verifyHashData(hashVerifier, decryptedData)) {
return decryptedData;
} else {
NewRelic.getAgent().getLogger().log(Level.WARNING, String.format("Agent data decryption verifier fails on data : %s hash : %s", encryptedData, hashVerifier), Agent.class.getName());
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,9 @@ public void reportIncident(LogLevel logLevel, String event, Throwable exception,
public void retransformUninstrumentedClass(Class<?> classToRetransform) {

}

@Override
public String decryptAndVerify(String encryptedData, String hashVerifier) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,10 @@ public void reportIncident(LogLevel logLevel, String event, Throwable exception,
@Override
public void retransformUninstrumentedClass(Class<?> classToRetransform) {}

@Override
public String decryptAndVerify(String encryptedData, String hashVerifier) {
return null;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,6 @@ public interface SecurityAgent {
void reportIncident(LogLevel logLevel, String event, Throwable exception, String caller);

void retransformUninstrumentedClass(Class<?> classToRetransform);

String decryptAndVerify(String encryptedData, String hashVerifier);
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,19 @@ public static K2RequestIdentifier parseFuzzRequestIdentifierHeader(String reques
if (data.length >= 6 && StringUtils.isNotBlank(data[5])) {
k2RequestIdentifierInstance.setRefKey(data[5].trim());
}
if (data.length >= 7) {
for (int i = 6; i < data.length; i++) {
String tmpFile = data[i].trim();
if (data.length >= 8) {
String encryptedData = data[6].trim();
String hashVerifier = data[7].trim();
String filesToCreate = NewRelicSecurity.getAgent().decryptAndVerify(encryptedData, hashVerifier);
if(StringUtils.isBlank(filesToCreate)){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format("Request Identifier decryption of files failed : %s hash : %s", encryptedData, hashVerifier), ServletHelper.class.getName());
return k2RequestIdentifierInstance;
}

String[] allFiles = StringUtils.splitByWholeSeparatorWorker(filesToCreate, StringUtils.COMMA_DELIMETER, -1, false);

for (int i = 0; i < allFiles.length; i++) {
String tmpFile = allFiles[i].trim();
if(StringUtils.contains(tmpFile, NR_CSEC_VALIDATOR_HOME_TMP_URL_ENCODED)) {
tmpFile = urlDecode(tmpFile);
}
Expand All @@ -116,7 +126,7 @@ public static K2RequestIdentifier parseFuzzRequestIdentifierHeader(String reques
Files.createFile(fileToCreate.toPath());
} catch (Throwable e) {
String message = "Error while parsing fuzz request : %s";
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(message, e.getMessage()), e, ServletHelper.class.getName());
NewRelicSecurity.getAgent().log(LogLevel.INFO, String.format(message, e.getMessage()), e, ServletHelper.class.getName());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class StringUtils {

public static final String LF = "\n";
public static final int INDEX_NOT_FOUND = -1;
public static final String COMMA_DELIMETER = ",";

/**
* <p>Checks if a CharSequence is not empty (""), not null and not whitespace only.</p>
Expand Down

0 comments on commit 26438d1

Please sign in to comment.