@hclsoftware/secagent
Version:
IAST agent
252 lines (212 loc) • 8.46 kB
JavaScript
//IASTIGNORE
/*
* ****************************************************
* Licensed Materials - Property of HCL.
* (c) Copyright HCL Technologies Ltd. 2017, 2025.
* Note to U.S. Government Users *Restricted Rights.
* ****************************************************
*/
const HookParser = require('../Hooks/HookParser')
const HookValidator = require('./ReadOnlyHookValidator')
const TaintTracker = require('../TaintTracker')
const {property} = require('./IastProperties')
// replace for operators '+' and '+='
console.__iastPlus = (a, b) => {
/* for array argument, the '+' operator calls internally to join in order to convert it to string. Join is propagator,
* so in case this array contains tainted elements, the result will be tainted as well (of type String object). As a
* result, the '+' operator 'think' that this array cannot be converted into primitive value and therefore throw an
* error. To prevent it, we first call join in case of array argument:
*/
a = Array.isArray(a) ? a.join() : a
b = Array.isArray(b) ? b.join() : b
const c = a + b
return registerTaintAfterConcat(a, b, c)
}
// replace for operator '=='
console.__iastEqualsEquals = (a, b) => {
let c = a == b
if (a != null && b != null) {
const isParameterTainted = a[property.TAINTED_DATA] != null
const isOtherTainted = b[property.TAINTED_DATA] != null
if (isParameterTainted || isOtherTainted || a[property.SANITIZED] != null || b[property.SANITIZED] != null) {
c = a.valueOf() == b.valueOf()
if (c) {
doEqualsSanitation(a, b, isParameterTainted, isOtherTainted)
}
}
}
return c
}
// replace for operator '!='
console.__iastNotEquals = (a, b) => {
let c = a != b
if (a != null && b != null) {
const isParameterTainted = a[property.TAINTED_DATA] != null
const isOtherTainted = b[property.TAINTED_DATA] != null
if (isParameterTainted || isOtherTainted || a[property.SANITIZED] != null || b[property.SANITIZED] != null) {
c = a.valueOf() != b.valueOf()
if (!c) {
doEqualsSanitation(a, b, isParameterTainted, isOtherTainted)
}
}
}
return c
}
// replace for operator '==='
console.__iastEqualsEqualsEquals = (a, b) => {
let c = a === b
if (a != null && b != null) {
const isParameterTainted = a[property.TAINTED_DATA] != null
const isOtherTainted = b[property.TAINTED_DATA] != null
if (isParameterTainted || isOtherTainted || a[property.SANITIZED] != null || b[property.SANITIZED] != null)
{
c = Object.getPrototypeOf(a) == Object.getPrototypeOf(b) && a.valueOf() == b.valueOf()
if (c) {
doEqualsSanitation(a, b, isParameterTainted, isOtherTainted)
}
}
}
return c
}
// replace for operator '!=='
console.__iastNotEqualsEquals = (a, b) => {
let c = a !== b
if (a != null && b != null) {
const isParameterTainted = a[property.TAINTED_DATA] != null
const isOtherTainted = b[property.TAINTED_DATA] != null
if (isParameterTainted || isOtherTainted || a[property.SANITIZED] != null || b[property.SANITIZED] != null)
{
c = (Object.getPrototypeOf(a) != Object.getPrototypeOf(b) || a.valueOf() != b.valueOf())
if (!c) {
doEqualsSanitation(a, b, isParameterTainted, isOtherTainted)
}
}
}
return c
}
// TBD: here we're still assuming that if an object is tainted, it must be a string primitive
console.__iastIsObject = (a) => {
let c = a === Object(a)
if (a != null && (a[property.TAINTED_DATA] != null || a[property.SANITIZED] != null)) {
return false
}
return c
}
// TBD: here we're still assuming that if an object is tainted, it must be a string primitive
console.__iastIsNotObject = (a) => {
let c = a !== Object(a)
if (a != null && (a[property.TAINTED_DATA] != null || a[property.SANITIZED] != null)) {
return true
}
return c
}
console.__iastTypeof = (a) => {
let c = typeof a
if ((a != null && a instanceof String && (a[property.TAINTED_DATA] != null || a[property.SANITIZED] != null))) {
c = 'string'
}
else if (a != null && a[property.FUNCTION_PROXY] != null) {
c = 'function'
}
return c
}
console.__iastNot = (a) => {
let c = !a
if ((a != null && a instanceof String && (a[property.TAINTED_DATA] != null || a[property.SANITIZED] != null))) {
c = !(a.toString())
}
return c
}
console.__iastToString = (a) => {
return a instanceof String && (a[property.TAINTED_DATA] != null || a[property.SANITIZED] != null) ? a.toString() : a
}
console.__iastCryptoCreateCipher = (that, ...args) => {
const validator = HookValidator.validators.CRYPTO_CREATE_CIPHER
return doReadOnlyHook('crypto.createCipher', validator, that, args)
}
console.__iastSqlite3DatabaseExec = (that, ...args) => {
const validator = HookValidator.validators.SQLITE3_DATABASE_EXEC
return doReadOnlyHook('sqlite3.Database.prototype.exec', validator, that, args)
}
console.__iastConcat = (a, b) => {
/* for array argument, the '+' operator calls internally to join in order to convert it to string. Join is propagator,
* so in case this array contains tainted elements, the result will be tainted as well (of type String object). As a
* result, the '+' operator 'think' that this array cannot be converted into primitive value and therefore throw an
* error. To prevent it, we first call join in case of array argument:
*/
a = Array.isArray(a) ? a.join() : a
b = Array.isArray(b) ? b.join() : b
const c = `${a}${b}`
return registerTaintAfterConcat(a, b, c)
}
global.__iastEvalCheck = (that, origArg) => {
if (that != null && !Array.isArray(that) && that.toString() === '[object global]'){
return global.__iastIndirectEval(origArg)
}
return that['eval'].apply(that, origArg)
}
global.__iastEval = (origArg, ret) => {
if (origArg[property.TAINTED_DATA]) {
const hookFunc = HookParser.getHookFunctionFor('global.eval')
return hookFunc(global, [origArg, ret], global.eval)
}
return ret
}
global.__iastIndirectEval = (origArg) => {
let errorThrown
let origRet
try {
origRet = eval.call(global, origArg[property.TAINTED_DATA] != null ? origArg.toString() : origArg)
}
catch (err) {
errorThrown = err
}
if (origArg[property.TAINTED_DATA]) {
const hookFunc = HookParser.getHookFunctionFor('global.eval')
return hookFunc(global, [origArg, origRet], global.eval)
}
if (errorThrown) {
throw errorThrown
}
return origRet
}
function doEqualsSanitation(parameter, other, isParameterTainted, isOtherTainted) {
if (isParameterTainted && !isOtherTainted) {
TaintTracker.sanitizeAll(parameter)
}
else if (isOtherTainted && !isParameterTainted) {
TaintTracker.sanitizeAll(other)
}
}
function doReadOnlyHook (fullName, thatValidator, that, args) {
const methodName = fullName.substring(fullName.lastIndexOf('.') + 1)
const origMethod = that[methodName]
// we need to make sure this object is the correct one. otherwise just run the original method:
if (HookParser.hooksActive && thatValidator(that)) {
const hookFunc = HookParser.getHookFunctionFor(fullName)
return hookFunc(that, args, origMethod)
} else {
return origMethod.apply(that, args)
}
}
function registerTaintAfterConcat(op1, op2, res)
{
if ((op1 != null && op1[property.TAINTED_DATA] != null) || (op2 != null && op2[property.TAINTED_DATA] != null)) {
const cString = new String(res)
if (op1 != null && op1[property.TAINTED_DATA] != null) {
TaintTracker.registerTaint(cString, op1[property.TAINTED_DATA].getCopy())
// cString[property.TAINTED_DATA] = a[property.TAINTED_DATA].getCopy()
if (op2 != null && op2[property.TAINTED_DATA] != null) { cString[property.TAINTED_DATA].merge(op2[property.TAINTED_DATA], null) }
} else
// cString[property.TAINTED_DATA] = b[property.TAINTED_DATA].getCopy()
{
TaintTracker.registerTaint(cString, op2[property.TAINTED_DATA].getCopy())
}
const stackObj = new global.origError()
global.origError.captureStackTrace(stackObj)
cString[property.TAINTED_DATA].addDataToStackList('propagator', null, '+', [op1, op2], cString, stackObj)
return cString
}
return res
}