@hclsoftware/secagent
Version:
IAST agent
251 lines (228 loc) • 10.2 kB
JavaScript
//IASTIGNORE
/* eslint-disable no-new-wrappers */
/*
* ****************************************************
* Licensed Materials - Property of HCL.
* (c) Copyright HCL Technologies Ltd. 2017, 2025.
* Note to U.S. Government Users *Restricted Rights.
* ****************************************************
*/
const HookRuleFactory = require('../Rules/HookRules/HookRuleFactory')
const BeforeRuleFactory = require('../Rules/BeforeRules/BeforeRuleFactory')
const hookRule = require('../Rules/HookRules/HookRule')
const HookValues = require('./HookValues')
const logger = require('../Logger/IastLogger').eventLog
const regularHooks = require('./Hooks')
const requireHooks = require('./RequireHooks')
const readOnlyHooks = require('./ReadOnlyHooks')
const TaintTracker = require('../TaintTracker')
const IastProperties = require('./IastProperties')
module.exports.loadHooks = () => {
module.exports.hooksActive = false
setRegularHooks()
loadHooks(readOnlyHooks)
loadHooks(requireHooks)
module.exports.hooksActive = true
}
module.exports.hooksActive = true
const toActualHook = {}
/*
called at start up.
Populates an object with hook functions which are generated according to the hook list.
*/
function loadHooks(hooks) {
for (const moduleName in hooks) {
hooks[moduleName].forEach(hook => {
const fullNames = getFullMethodName(moduleName, hook)
for (const fullName of fullNames) {
const methodName = fullName.substring(fullName.lastIndexOf('.') + 1)
const hookFunction = parseHook(hook, methodName)
toActualHook[fullName] = hookFunction
}
})
}
}
/*
Given a module, it overrides the relevant original functions by appropriate hook functions which loaded at start up.
*/
module.exports.setRequireHook = (origModule, moduleName) => {
const hooks = moduleName in requireHooks ? requireHooks[moduleName] : []
hooks.forEach(hookObj => {
// get hook function from cache:
const fullNames = getFullMethodName(moduleName, hookObj)
for (const fullName of fullNames) {
const functionName = fullName.substring(fullName.lastIndexOf('.') + 1)
const hookFunction = toActualHook[fullName]
// need to fetch the hooked module in run time because it might be of different version in different requires:
const hookedModule = getHookedModule(hookObj, origModule)
if (hookedModule == null) { // module may not be present, e.g. fs.promises
//logger.debug(`skipping undefined module ${origModule}, scope: ${JSON.stringify(hookObj.scopes)}`)
return
}
const origFunction = hookedModule[functionName]
if (origFunction != null) { // we may hook functions that do not exist in certain library versions
setProxyOnFunction(hookedModule, moduleName, functionName, origFunction, hookFunction)
}
// else {
// logger.debug(`skipping undefined function ${moduleName}.${functionName}`)
// }
}
})
}
function setRegularHooks() {
for (const moduleName in regularHooks) {
const baseModule = moduleName === 'global' ? global : require(moduleName)
regularHooks[moduleName].forEach(hookObj => {
const moduleObj = getHookedModule(hookObj, baseModule)
if (moduleObj == null) { // module may not be present, e.g. fs.promises
//logger.debug(`skipping undefined module ${baseModule}, scope: ${JSON.stringify(hookObj.scopes)}`)
return
}
const functionNames = Array.isArray(hookObj.methodName) ? hookObj.methodName : [hookObj.methodName]
for (const functionName of functionNames) {
const hookFunction = parseHook(hookObj, functionName)
const origFunction = moduleObj[functionName]
if (origFunction != null) { // we may hook functions that do not exist in certain library versions
setProxyOnFunction(moduleObj, moduleName, functionName, origFunction, hookFunction)
}
// else {
// logger.debug(`skipping undefined function ${moduleName}.${functionName}`)
// }
}
})
}
}
/* This function is used for adding hooks by proxy. There are functions that we cannot override by our hook,
* but with proxy because they contain additional properties that would be removed in the regular overriding way.
*/
function setProxyOnFunction(module, moduleName, functionName, origFunction, hookFunction) {
// we store a flag for each function that indicates whether the function has already wrapped by a proxy, to prevent
// a proxy chain, which might make the program crash.
if (origFunction == null || (module[functionName] != null && module[functionName][IastProperties.property.FUNCTION_PROXY])) {
return
}
module[functionName] = new Proxy(origFunction, {
apply(target, thisArg, argArray) { // trap regular function call, e.g. Error(message)
const that = thisArg == null || (!Buffer.isBuffer(thisArg)) && (!Array.isArray(thisArg) && thisArg.toString() === '[object global]') ? moduleName : thisArg
return hookFunction(that, argArray, target)
},
construct(target, argArray, newTarget) { // trap constructor call, e.g. new Error(message)
return hookFunction('global', argArray, target, newTarget)
},
get(target, p, receiver) {
if (p === IastProperties.property.FUNCTION_PROXY) {
return true;
}
return Reflect.get(target, p, receiver)
}
})
}
function getHookedModule(hookObj, baseModule) {
let currentScope = baseModule
const scopes = 'scopes' in hookObj ? hookObj.scopes : []
scopes.forEach(scope => {
if (currentScope != null) {
currentScope = currentScope[scope]
}
})
return currentScope
}
function getFullMethodName(moduleName, hook) {
let fullNamesArray = []
const namespace = 'scopes' in hook ? `${moduleName}.${hook.scopes.join('.')}` : `${moduleName}`
if (Array.isArray(hook.methodName)) {
fullNamesArray = hook.methodName.map(name => namespace + '.' + name)
} else {
fullNamesArray.push(namespace + '.' + hook.methodName)
}
return fullNamesArray
}
module.exports.getHookFunctionFor = (fullMethodName) => {
return toActualHook[fullMethodName]
}
/*
This function Generates rules for required hook object.
It returns a function which would be called instead of the original method.
*/
function parseHook(hookObj, methodName) {
const beforeRules = hookObj.beforeRules != null ? parseRules(hookObj.beforeRules, BeforeRuleFactory) : []
const callbackRules = hookObj.callbackRules != null ? parseRules(hookObj.callbackRules, HookRuleFactory) : []
const hookRules = hookObj.rules != null ? parseRules(hookObj.rules, HookRuleFactory) : []
// this function would be called at run time instead of the original one:
return function (that, origArgs, method, constructorProxy=null) {
const hookValues = new HookValues(origArgs, that, method, methodName, hookObj.additionalInfo, callbackRules, constructorProxy)
return doRules(hookObj, beforeRules, hookRules, hookValues)
}
}
/*
Manages the hook execution:
1. executes before rules,
2 calls to the original method
3. executes the hook rules.
*/
function doRules(hookObj, beforeRules, hookRules, hookValues) {
let thrownError
try {
beforeRules.forEach(rule => {
try {
if (rule.runWhenInactive() || module.exports.hooksActive)
rule.doRule(hookValues)
} catch (e) {
logger.error(e)
}
})
if (hookValues.runOrigMethod) {
try {
if (hookValues.isConstructor) {
// hookValues.ret = new (Function.bind.apply(hookValues.origMethod, [null, ...hookValues.updatedArgs]))
hookValues.ret = Reflect.construct(hookValues.origMethod, hookValues.updatedArgs, hookValues.newTarget)
}
else {
hookValues.ret = hookValues.origMethod.apply(hookValues.updatedThat, hookValues.updatedArgs)
}
} catch (error) {
thrownError = error
}
}
if (module.exports.hooksActive && shouldRunHookRules(hookObj, hookRules, hookValues)) {
hookRules.forEach(rule => {
try {
rule.doHook(hookValues)
} catch (e) {
logger.error(e)
}
})
}
} catch (e) {
logger.error(e)
}
if (thrownError != null) {
throw thrownError
}
return hookValues.ret
}
function shouldRunHookRules(hookObj, hookRules, hookValues) {
if (hookRules.length === 0) {
return false
}
if (hookObj.taintCondition != null) {
return hookObj.taintCondition.some(p => isSpecifiedPositionTainted(p, hookValues))
}
return true
}
function isSpecifiedPositionTainted (position, hookValues) {
if (position === 'args') {
for (let i = 0; i < hookValues.args.length; i++) {
if (TaintTracker.isObjectTainted(hookRule.getActualParam(i, hookValues))) return true
}
return false
}
return TaintTracker.isObjectTainted(hookRule.getActualParam(position, hookValues))
}
function parseRules(rulesObj, ruleFactory) {
const rules = []
for (const ruleObj of rulesObj) {
rules.push(ruleFactory.createRule(ruleObj))
}
return rules
}