Skip to content

Commit

Permalink
Merge pull request #207 from newrelic/hotfix/NR-234886
Browse files Browse the repository at this point in the history
[NR-234886] IAST replay header decryption
  • Loading branch information
lovesh-ap committed Mar 19, 2024
2 parents cb5c691 + b2c0f28 commit 1835bae
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 5 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
@@ -1,6 +1,6 @@
# The agent version.
agentVersion=1.1.2
jsonVersion=1.1.1
jsonVersion=1.2.0
# Updated exposed NR APM API version.
nrAPIVersion=8.4.0

Expand Down
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
@@ -0,0 +1,90 @@
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.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);
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(new byte[OFFSET]));

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


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

Please sign in to comment.