@hclsoftware/secagent
Version:
IAST agent
195 lines (175 loc) • 6.14 kB
JavaScript
//IASTIGNORE
/*
* ****************************************************
* Licensed Materials - Property of HCL.
* (c) Copyright HCL Technologies Ltd. 2017, 2025.
* Note to U.S. Government Users *Restricted Rights.
* ****************************************************
*/
'use strict'
const Utils = require('./Utils/Utils');
const {ConfigInfo} = require("./ConfigFile/ConfigInfo");
const TaintTracker = require("./TaintTracker");
const StackTraceUtils = require("./Utils/StackTraceUtils");
const maxParamLength = 300;
const maxParamLengthModificationInfo = 50;
const NON_PRINTABLE_REGEX = /[\x00-\x08\x0E-\x1F\x7F\uFFFD]/;
class StackInfo {
constructor(type, parameters, vulnerability, rawStack, propagatorTargetAsString) {
this.type = type
if (vulnerability != null) {
this.from_vulnerability = vulnerability
}
this.parameters = parameters
this['method-signature'] = this.parameters.methodSignature
if (rawStack != null) {
this.rawStack = rawStack.stack
}
else{
this.rawStack = null
}
this.stack = null
this.object = ''
this.arguments = []
this.return = ''
if (propagatorTargetAsString != null) {
this.propagatorTargetAsString = propagatorTargetAsString
}
}
updateHash(vulnerability) {
const hash = Utils.createHashObject()
hash.update(this.type)
// don't add method signature to the hash if stack is empty. In that case method signature is the request url
if (this.parameters['method-signature'] != null && this.stack != null) {
hash.update(this.parameters['method-signature'])
}
if (this.stack != null) {
for (const item of this.stack) {
hash.update(item)
}
}
return hash.produce()
}
static getSimpleParamsStringArray (origThat, simpleThat, methodDescriptor, args, ret) {
return {
that: this.asString(simpleThat),
ret: this.asString(ret),
methodSignature: this.asString(methodDescriptor),
methodArguments: (args == null) ? [] : Array.from(args, item => this.asString(item)),
}
}
static getParamsStringArray (origThat, simpleThat, methodDescriptor, args, ret) {
const res = this.getSimpleParamsStringArray(origThat, simpleThat, methodDescriptor, args, ret)
res.objects = {
that: simpleThat,
ret : ret,
args : args
}
return res
}
static getParamsStringArrayPostHook (origThat, simpleThat, methodDescriptor, args, ret) {
let res = StackInfo.getParamsStringArray(origThat, simpleThat, methodDescriptor, args, ret)
StackInfo.replacePasswordParams(res)
return res
}
static replacePasswordParams(params){
const objects = params.objects
if (ConfigInfo.ConfigInfo.hidePasswords) {
if (objects.that != null) {
StackInfo.replacePasswordValue(params, "that", objects.that);
}
if (objects.ret != null) {
StackInfo.replacePasswordValue(params, "ret", objects.ret);
}
if (objects.args != null) {
for (let i = 0; i < objects.args.length; i++) {
if (objects.args[i] != null){
StackInfo.replacePasswordValue(params, "methodArguments", objects.args[i], i);
}
}
}
}
}
static replacePasswordValue(params, key, obj, index = undefined) {
if (StackInfo.hasPasswordFlow(obj)) {
if (index == null) {
params[key] = Utils.PASSWORD_TEXT
}
else {
params[key][index] = Utils.PASSWORD_TEXT
}
}
}
static hasPasswordFlow(obj) {
let taintData = TaintTracker.getTaintedData(obj)
if (taintData != null) {
for (const flow of taintData.flows) {
if (flow.entity.isPassword) {
return true
}
}
}
return false
}
static asString(obj, forModificationInfo = false) {
let result = ''
const limit = forModificationInfo ? maxParamLengthModificationInfo : maxParamLength
if (typeof obj === 'string') {
result = obj
}
else if (obj instanceof RegExp || obj instanceof String) {
result = obj.toString()
}
else if (Buffer.isBuffer(obj)) {
// Create a string representation of the buffer content
const bufferContent = obj.origToString();
const displayContent = this.isNonPrintableText(bufferContent) ? "[binary data]" : bufferContent;
return `<buffer: ${displayContent.length > limit ? displayContent.origSubstring(0, limit) + '...' : displayContent} (${obj.length} bytes)>`;
}
else {
try {
result = JSON.origStringify(obj)
} catch (err) {
console.origLog(err)
console.origLog(obj)
}
if (result === null || result === undefined || result === "" || result === "{}") {
return undefined
}
}
if (result == null || result.length <= limit)
return result
return result.origSubstring(0, limit) + '...'
}
static isNonPrintableText(data){
// If it contains the Unicode replacement character (�) or non-printable characters, it's probably not text content
return NON_PRINTABLE_REGEX.test(data);
}
updateStack() {
if (this.stack == null && this.rawStack != null) {
this.stack = StackTraceUtils.getStackTraceArray(this.rawStack);
}
this.object = (this.parameters.that == null || this.parameters.that === "null") ? '' : this.parameters.that
this.arguments = this.parameters.methodArguments == null ? [] : this.parameters.methodArguments
this.return = (this.parameters.ret == null || this.parameters.ret === "null") ? '' : this.parameters.ret
}
// called by JSON.stringify, we use this function to ignore specific StackInfo properties.
toJSON() {
const excludedKeys = new Set(["parameters"])
return Object.fromEntries(Object.entries(this).filter(([key]) => !excludedKeys.has(key)))
}
getStackTraceString() {
if (this.stack == null) {
this.updateStack()
}
// if stack is still null
if (this.stack == null) {
return ''
}
return this.stack.toString()
}
setStackStr(stackStr) {
this.stack = stackStr;
}
}
module.exports = StackInfo