diff --git a/Changelog.md b/Changelog.md index fae1604..2d28558 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,14 @@ # Changelog +## [v1.2.0] - 2024-04-12 +### Features +* IAST replay header decryption due to Security Findings. +* Json Version bump to 1.2.0 +### Miscellaneous chores +* Prepended the vulnerability case type with apiId. +* Updated time interval for IAST pull request. +* Bumped golang.org/x/net from v0.17.0 to v0.23.0 + ## [v1.1.0] - 2024-03-26 ### Features * Functionality to report API endpoints of the application diff --git a/go.mod b/go.mod index 953c553..0f7b684 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/dlclark/regexp2 v1.9.0 github.com/gorilla/websocket v1.5.0 github.com/k2io/hookingo v1.0.5 + golang.org/x/crypto v0.22.0 ) retract v0.5.0 // backward compatibility error corrected in v0.5.1 \ No newline at end of file diff --git a/go.sum b/go.sum index fba221e..8eec0cf 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/k2io/hookingo v1.0.3 h1:9rJMlAKzhBLTEn3jmpmt6AsyHmXONPvRgCRxzvxS89Y= github.com/k2io/hookingo v1.0.3/go.mod h1:GfmXAKuiFd8/UafviDs8nnciGQ89QvHIzQQUaAmvRJ4= +github.com/k2io/hookingo v1.0.5 h1:MAuYIjpOf2IFs7UqEDrHntNBswWg7z7/I2XMQHogEio= +github.com/k2io/hookingo v1.0.5/go.mod h1:2L1jdNjdB3NkbzSVv9Q5fq7SJhRkWyAhe65XsAp5iXk= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -16,8 +18,15 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= +golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/instrumentation/csec_grpc/go.mod b/instrumentation/csec_grpc/go.mod index d27be12..dd2134a 100644 --- a/instrumentation/csec_grpc/go.mod +++ b/instrumentation/csec_grpc/go.mod @@ -10,7 +10,7 @@ require ( ) require( - golang.org/x/net v0.17.0 + golang.org/x/net v0.23.0 ) exclude( diff --git a/internal/security_utils/config.go b/internal/security_utils/config.go index 5218d80..1d3724b 100644 --- a/internal/security_utils/config.go +++ b/internal/security_utils/config.go @@ -4,8 +4,8 @@ package security_utils const ( - CollectorVersion = "1.1.0" - JsonVersion = "1.1.1" + CollectorVersion = "1.2.0" + JsonVersion = "1.2.0" CollectorType = "GOLANG" - BuildNumber = "158" + BuildNumber = "159" ) diff --git a/internal/security_utils/encryptorUtils.go b/internal/security_utils/encryptorUtils.go new file mode 100644 index 0000000..006080b --- /dev/null +++ b/internal/security_utils/encryptorUtils.go @@ -0,0 +1,118 @@ +package security_utils + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha1" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + + "golang.org/x/crypto/pbkdf2" +) + +const ( + ITERATION = 1024 + KEY_LEN = 32 // 256 bits + OFFSET = 16 + EMPTY_PASSWORD = "Empty Password provided" + DATA_TO_BE_DECRYPTED = "Data to be decrypted is Empty" + INCORRECT_SECRET = "Incorrect Password / salt provided: %s" + EMPTY_SECRET = "secretKey is empty" + ERROR_WHILE_DECRYPTION = "Error while decryption %s: %s" + ENCRYPTED_DATA_DECRYPTED_DATA = "Encrypted Data: %s, Decrypted data: %s" + ERROR_WHILE_GENERATING_REQUIRED_SALT_FROM_S_S = "Error while generating required salt from %s" + ERROR_WHILE_VERIFY_HASH_DATA = "Hash Data not macth %s: %s" +) + +func Decrypt(password, encryptedData, hashVerifier string) (string, error) { + if password == "" { + + return "", fmt.Errorf(EMPTY_PASSWORD) + } + if encryptedData == "" { + return "", fmt.Errorf(DATA_TO_BE_DECRYPTED) + } + + salt, err := generateSalt(password) + if err != nil { + return "", fmt.Errorf(ERROR_WHILE_GENERATING_REQUIRED_SALT_FROM_S_S, err) + } + + secretKey := deriveKey(password, salt) + if secretKey == nil { + return "", errors.New(EMPTY_SECRET) + } + + encryptedBytes, err := hex.DecodeString(encryptedData) + if err != nil { + return "", fmt.Errorf(INCORRECT_SECRET, err) + } + + block, err := aes.NewCipher(secretKey) + if err != nil { + return "", fmt.Errorf(INCORRECT_SECRET, err) + } + + decrypted := make([]byte, len(encryptedBytes)) + + cipher.NewCBCDecrypter(block, make([]byte, block.BlockSize())).CryptBlocks(decrypted, encryptedBytes) + decrypted = removePadding(decrypted) + decryptedData := string(decrypted[OFFSET:]) + + if verifyHashData(hashVerifier, decryptedData) { + return decryptedData, nil + } else { + return "", fmt.Errorf(ERROR_WHILE_VERIFY_HASH_DATA, hashVerifier, decryptedData) + } + +} + +func generateSalt(salt string) ([]byte, error) { + // Encode the first OFFSET characters of the salt as hexadecimal + if len([]byte(salt)) < OFFSET { + + return nil, errors.New("Error while generating required salt") + } + encoded := hex.EncodeToString([]byte(salt)[:OFFSET]) + + decoded, err := hex.DecodeString(encoded) + if err != nil { + return nil, err + } + + return decoded, nil +} + +func deriveKey(password string, salt []byte) []byte { + key, err := pbkdf2KeyDerivation([]byte(password), salt, ITERATION, KEY_LEN) + if err != nil { + return nil + } + return key +} + +func pbkdf2KeyDerivation(password, salt []byte, iterations, keyLen int) ([]byte, error) { + key := pbkdf2.Key(password, salt, iterations, keyLen, sha1.New) + return key, nil +} + +func verifyHashData(knownDecryptedDataHash, decryptedData string) bool { + return knownDecryptedDataHash == generateSHA256HexDigest(decryptedData) +} + +func generateSHA256HexDigest(data string) string { + digest := sha256.Sum256([]byte(data)) + return hex.EncodeToString(digest[:]) +} + +func removePadding(data []byte) []byte { + if i := len(data) - 1; i > 0 { + paddingLength := int(data[i]) + if j := len(data) - paddingLength; j > 0 { + return data[:j] + } + } + return data +} diff --git a/security_config/global_config.go b/security_config/global_config.go index 1db6778..15409b3 100644 --- a/security_config/global_config.go +++ b/security_config/global_config.go @@ -146,6 +146,16 @@ func (info *Info_struct) SetApiData(data Urlmappings) { return } +func (info *Info_struct) IastProbingInterval() int { + info.ploicyMutex.Lock() + defer info.ploicyMutex.Unlock() + if GlobalInfo.currentPolicy.VulnerabilityScan.IastScan.Probing.Interval <= 0 { + return 5 + } else { + return GlobalInfo.currentPolicy.VulnerabilityScan.IastScan.Probing.Interval + } +} + type metaData struct { linkingMetadata interface{} accountID string diff --git a/security_handlers/fuzz_request_handler.go b/security_handlers/fuzz_request_handler.go index 7893bbe..60ad808 100644 --- a/security_handlers/fuzz_request_handler.go +++ b/security_handlers/fuzz_request_handler.go @@ -95,7 +95,9 @@ func InitFuzzScheduler() { } eventGeneration.SendLogMessage("initializing fuzz request scheduler", "InitFuzzScheduler", "INFO") for { - time.Sleep(1 * time.Second) + iastProbingInterval := secConfig.GlobalInfo.IastProbingInterval() + logger.Debugln("iastProbingInterval SleepTime", iastProbingInterval) + time.Sleep(time.Duration(iastProbingInterval) * time.Second) if !secConfig.SecureWS.GetStatus() { logger.Debugln("WS not connected sleep FuzzScheduler for 5 sec") time.Sleep(5 * time.Second) diff --git a/security_implementation/implementation.go b/security_implementation/implementation.go index 343a3cd..a0b00b8 100644 --- a/security_implementation/implementation.go +++ b/security_implementation/implementation.go @@ -96,7 +96,7 @@ func (k Secureimpl) AssociateOutboundRequest(dest, dport, urlx string) { func (k Secureimpl) CalculateOutboundApiId() { request := getRequest(getID()) if request.VulnerabilityDetails.APIID == "" { - vulnerabilityDetails := presentStack(request.Request.Method) + vulnerabilityDetails := presentStack(request.Request.Method, "REFLECTED_XSS") request.VulnerabilityDetails = vulnerabilityDetails } } @@ -264,7 +264,7 @@ func sendEvent(eventId, category string, args interface{}) *secUtils.EventTracke if category == "REFLECTED_XSS" && (*req).VulnerabilityDetails.APIID != "" { vulnerabilityDetails = (*req).VulnerabilityDetails } else { - vulnerabilityDetails = presentStack((*req).Request.Method) + vulnerabilityDetails = presentStack((*req).Request.Method, category) } return eventGeneration.SendVulnerableEvent(req, category, args, vulnerabilityDetails, getEventID(eventId, id)) @@ -347,7 +347,7 @@ func isAgentReady() bool { return secConfig.SecureWS != nil } -func presentStack(method string) (vulnerabilityDetails secUtils.VulnerabilityDetails) { +func presentStack(method, caseType string) (vulnerabilityDetails secUtils.VulnerabilityDetails) { pc := make([]uintptr, 20) n := runtime.Callers(4, pc) frames := runtime.CallersFrames(pc[:n]) @@ -410,7 +410,7 @@ func presentStack(method string) (vulnerabilityDetails secUtils.VulnerabilityDet stackTrace = stackTrace[:120] } - vulnerabilityDetails.APIID = apiId + vulnerabilityDetails.APIID = caseType + "-" + apiId vulnerabilityDetails.Stacktrace = stackTrace return vulnerabilityDetails diff --git a/security_intercept/intercept.go b/security_intercept/intercept.go index ccdb29c..d74fc88 100644 --- a/security_intercept/intercept.go +++ b/security_intercept/intercept.go @@ -36,6 +36,7 @@ const ( NR_CSEC_TRACING_DATA = "NR-CSEC-TRACING-DATA" NR_CSEC_FUZZ_REQUEST_ID = "nr-csec-fuzz-request-id" NR_CSEC_PARENT_ID = "NR-CSEC-PARENT-ID" + COMMA_DELIMETER = "," ) /** @@ -562,9 +563,23 @@ func createFuzzFile(fuzzheaders string) (tmpFiles []string) { if DSON && fuzzheaders != "" { additionalData := strings.Split(fuzzheaders, IAST_SEP) logger.Debugln("additionalData:", additionalData) - if len(additionalData) >= 7 { - for i := 6; i < len(additionalData); i++ { - fileName := additionalData[i] + if len(additionalData) >= 8 { + encryptedData := additionalData[6] + hashVerifier := additionalData[7] + logger.Debugln("Encrypted file name : ", encryptedData) + filesToCreate, err := secUtils.Decrypt(secConfig.GlobalInfo.MetaData.GetEntityGuid(), encryptedData, hashVerifier) + + if err != nil { + logger.Errorln(err) + SendLogMessage(err.Error(), "createFuzzFile", "SEVERE") + return + } + + logger.Debugln("Decrypted file name : ", filesToCreate) + allFiles := strings.Split(filesToCreate, COMMA_DELIMETER) + + for i := range allFiles { + fileName := allFiles[i] dsFilePath := filepath.Join(secConfig.GlobalInfo.SecurityHomePath(), "nr-security-home", "tmp") fileName = strings.Replace(fileName, "{{NR_CSEC_VALIDATOR_HOME_TMP}}", dsFilePath, -1) fileName = strings.Replace(fileName, "%7B%7BNR_CSEC_VALIDATOR_HOME_TMP%7D%7D", dsFilePath, -1)