UNPKG

@hclsoftware/secagent

Version:

IAST agent

165 lines (143 loc) 7.13 kB
//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