@hclsoftware/secagent
Version:
IAST agent
165 lines (143 loc) • 7.13 kB
JavaScript
//IASTIGNORE
/*
* ****************************************************
* Licensed Materials - Property of HCL.
* (c) Copyright HCL Technologies Ltd. 2017, 2025.
* Note to U.S. Government Users *Restricted Rights.
* ****************************************************
*/
const acorn = require('acorn')
const escodegen = require('escodegen')
const Utils = require("./Utils/Utils");
let updatedNode
/* eval might be invoked either directly or indirectly which results in a different functionality of eval.
* So we catch these calls and change the eval name to the appropriate global hook name, according to the call type (direct/indirect).
* more info about eval calls: http://perfectionkills.com/global-eval-what-are-the-options/
*/
class EvalNode {
static getNode() {
return updatedNode
}
// changes the given AST node if it represents an eval call and returns true, otherwise returns false.
static changeNodeIfEvalCall(node) {
const evalExpression = 'input.__iast_tainted_data == null? eval(input) : eval(input.toString())'
let isEvalCall = true
function setNode(node) {
updatedNode = node
}
function isMemberExpressionWithGlobalObject(node) {
return node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'global'
}
// global.eval
function hasEvalIdProperty(node) {
return node.property.type === 'Identifier' && node.property.name === 'eval'
}
// global['eval']
function hasEvalLiteralProperty(node) {
return node.property.type === 'Literal' && node.property.value === 'eval'
}
// returns true iff the this argument in the given AST node is the global module.
function hasGlobalArgAsThis(node) {
return node.arguments[0] && node.arguments[0].type === 'Identifier' && node.arguments[0].name === 'global'
}
// catch direct calls to eval, e.g. let res = eval('input')
if (node.type === 'CallExpression' && node.callee && node.callee.name === 'eval' && node.__iastDoNotReplace == null) {
node.callee.name = 'global.__iastEval'
const strOrigArg = escodegen.generate(node.arguments[0])
const actualEvalExpression = evalExpression.replace(/input/g, '(' + strOrigArg + ')')
const evalExpressionNode = acorn.parse(actualEvalExpression, Utils.acornOptions).body[0].expression
evalExpressionNode.consequent.__iastDoNotReplace = true
evalExpressionNode.consequent.callee.__iastDoNotReplace = true
evalExpressionNode.alternate.__iastDoNotReplace = true
evalExpressionNode.alternate.callee.__iastDoNotReplace = true
node.arguments = node.arguments.concat(evalExpressionNode)
}
else if (node.type === 'SequenceExpression' && isMemberExpressionWithGlobalObject(node.expressions[node.expressions.length - 1])) {
const evalNode = node.expressions[node.expressions.length - 1]
// e.g. (1, global.eval)
if (hasEvalIdProperty(evalNode)) {
evalNode.property.name = '__iastIndirectEval'
node.__iastDoNotReplace = true
}
// e.g. (1, global['eval'])
else if (hasEvalLiteralProperty(evalNode)) {
evalNode.property.value = '__iastIndirectEval'
evalNode.property.raw = "'__iastIndirectEval'"
node.__iastDoNotReplace = true
}
// e.g. (1, eval)
else if (evalNode.name === 'eval') {
evalNode.name = '__iastIndirectEval'
node.__iastDoNotReplace = true
}
}
else if (node.type === 'CallExpression' && node.callee && node.callee.type === 'MemberExpression' &&
['apply', 'call'].origArrayIncludes(node.callee.property.name) && hasGlobalArgAsThis(node) && node.__iastDoNotReplace == null) {
if (isMemberExpressionWithGlobalObject(node.callee.object)) {
// examples:
// 1. let res = global.eval.call(global, 'input')
// 2. let res = global.eval.apply(global, 'input')
if (hasEvalIdProperty(node.callee.object)) {
node.callee.object.property.name = '__iastIndirectEval'
node.__iastDoNotReplace = true
}
// examples:
// 1. let res = eval.call(global, 'input')
// 2. let res = eval.apply(global, 'input')
else if (hasEvalLiteralProperty(node.callee.object)) {
node.callee.object.property.value = '__iastIndirectEval'
node.callee.object.property.raw = "'__iastIndirectEval'"
node.__iastDoNotReplace = true
}
}
}
// else if (isMemberExpressionWithGlobalObject(node) && node.__iastDoNotReplace == null) {
else if (node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.__iastDoNotReplace == null) {
if (node.object.name === 'global') {
// e.g. let f = global.eval
if (hasEvalIdProperty(node)) {
node.property.name = '__iastIndirectEval'
node.__iastDoNotReplace = true
}
// e.g. let f = global['eval']
else if (hasEvalLiteralProperty(node)) {
node.property.value = '__iastIndirectEval'
node.property.raw = "'__iastIndirectEval'"
node.__iastDoNotReplace = true
}
}
else if (hasEvalIdProperty(node) || hasEvalLiteralProperty(node)) {
node.property.__iastDoNotReplace = true
}
}
// e.g. let res = foo().eval()
else if (node.type === 'CallExpression' && node.callee && node.callee.type === 'MemberExpression' &&
node.callee.property.type === 'Identifier' && node.callee.property.name === 'eval' &&
node.callee.object.type === 'CallExpression' && node.__iastDoNotReplace == null){
const callee = node.callee
const functionName = callee.property.name
// store that:
const that = callee.object
// add that as the first argument:
node.arguments.unshift(that)
// change that to 'global':
callee.object = {
type: 'Identifier',
name: 'global'
}
// change function name to our hook name:
callee.property.name = '__iastEvalCheck'
}
// e.g. let f = eval
else if (node.type === 'Identifier' && node.name === 'eval' && node.__iastDoNotReplace == null) {
node.name = '__iastIndirectEval'
node.__iastDoNotReplace = true
}
else {
isEvalCall = false
}
setNode(node)
return isEvalCall
}
}
module.exports = EvalNode