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

[NR-234886] IAST replay header decryption #207

Merged
merged 4 commits into from Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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,82 @@
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) {
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)) {
lovesh-ap marked this conversation as resolved.
Show resolved Hide resolved
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