UNPKG

@hclsoftware/secagent

Version:

IAST agent

164 lines (150 loc) 7.08 kB
//IASTIGNORE /* * **************************************************** * Licensed Materials - Property of HCL. * (c) Copyright HCL Technologies Ltd. 2017, 2025. * Note to U.S. Government Users *Restricted Rights. * **************************************************** */ const TaintTracker = require("../../TaintTracker"); const {Entity, EntityType} = require("../../Entity"); const {keys} = require("../../AdditionalInfo"); const StackInfo = require("../../StackInfo"); const {ConfigInfo} = require("../../ConfigFile/ConfigInfo"); const Utils = require("../../Utils/Utils"); const AdditionalInfo = require("../../AdditionalInfo"); const IastProperties = require("../../Hooks/IastProperties"); const _ = require('deepdash')(require('lodash')); function sinkTrigger(obj, vulnerabilities, parameters, origPropertyName, entityName = "") { const telemetryDataList = createTelemetryDataForObject(origPropertyName, obj, entityName) for (const telemetryData of telemetryDataList) { if (telemetryData.additionalInfo != null) { TaintTracker.getTaintedData(telemetryData.taintedItem).addAdditionalInfoToFlows(telemetryData.additionalInfo) } for (const vulnerability of vulnerabilities) { TaintTracker.sinkTrigger(telemetryData.taintedItem, vulnerability, parameters) } } } function addSinkAdditionalInfo(source, methodName) { let from = StackInfo.asString(source) if (ConfigInfo.ConfigInfo.hidePasswords && StackInfo.hasPasswordFlow(source)) { from = Utils.PASSWORD_TEXT } const sinkObj = {method: methodName, from: from} AdditionalInfo.addAdditionalInfoToFlows(source, {[keys.K8S_SINK]: JSON.origStringify(sinkObj)}) } /* Recursively iterating over the given object properties to create Telemetry data for each tainted item found. The Telemetry data is an entity-like, converted to string. Returns the found tainted items as an array of objects containing the tainted item and the additional info when needed. */ function createTelemetryDataForObject(origPropertyName, obj, entityName="", taintedItems=[]) { if (TaintTracker.hasTaintedData(obj) && (obj instanceof String || Buffer.isBuffer(obj))) { const entityType = createEntityType(obj, entityName, origPropertyName) let objString = obj.origToString(); let parsedJson; // we try to parse the object as JSON, if it succeeds, we can extract the tainted data for each flow and add it as the telemetry data. try { // try to parse the object as JSON parsedJson = JSON.origParse(objString); // only proceed if parsedJson is an object if (typeof parsedJson !== 'object') { throw new Error('Parsed value is not an object'); // throw exception to handle non-object cases } } catch (e) { // if the object is not a valid JSON, we set the telemetry data to be the value itself const additionalInfo = createTelemetryDataEntity(entityName, objString, entityType) taintedItems.push({taintedItem: obj, additionalInfo: additionalInfo}) return taintedItems; } let objectMap = null; // if we reach here, the object is a valid JSON, we can extract the tainted data for each flow for (const flow of obj[IastProperties.property.TAINTED_DATA].flows) { let value, path; if (flow.storedDataForTelemetryIssue !== null) { // if the flow has stored value for telemetry data, we can use it directly and skip the search in the stack info list ({ value, path } = flow.storedDataForTelemetryIssue); } else { if (!objectMap) { objectMap = mapObject(parsedJson); // create a map of the object properties to their paths } ({value, path} = getFlowValueFromJson(flow, objectMap)); if (value === null) { // if no value was found, we use the original json string as the value value = objString; path = ''; } } const additionalInfo = createTelemetryDataEntity(path, value, entityType) flow.addAdditionalInfo(additionalInfo) } taintedItems.push({taintedItem: obj, additionalInfo: null}) } else if (typeof obj === "object") { const isArray = Array.isArray(obj); for (const innerProperty in obj) { const path = isArray ? `${entityName}[${innerProperty}]` : `${entityName}.${innerProperty}` const currentEntityName = entityName === "" ? innerProperty : path createTelemetryDataForObject(origPropertyName, obj[innerProperty], currentEntityName, taintedItems) } } return taintedItems } function createTelemetryDataEntity(entityName, entityValue, entityType) { const entity = new Entity(entityName, entityValue, entityType) return {[keys.TELEMETRY_DATA]: JSON.origStringify(entity)} } function getFlowValueFromJson(flow, objectMap) { // look for the tainted data in the stack info list let targetValue = null; const stackInfoList = flow.stackInfoList; // walk through the stack info list in reverse order to find the first occurrence of the tainted data as it's defined in the json for (const item of [...stackInfoList].reverse()) { switch (item.type) { case TaintTracker.HookRuleType.SOURCE: targetValue = flow.entity.value; break; case TaintTracker.HookRuleType.PROPAGATOR: targetValue = item.propagatorTargetAsString; break; } if (targetValue === null || targetValue === undefined) continue; if (objectMap.has(targetValue)) { const path = objectMap.get(targetValue); return {value: targetValue, path: path}; } } return null; } function mapObject(obj) { const map = new Map(); _.eachDeep(obj, (value, key, parent, context) => { map.set(value, context.path); // context.path is the full path to the value in the object }, { checkCircular: true, leavesOnly: true }); return map; } function createEntityType(obj, entityName, propertyName) { switch (propertyName) { case "data": if (obj instanceof String || Buffer.isBuffer(obj)) { if (entityName === "body"){ return EntityType.BODY; } else { return EntityType.BODY_PARAMETER } } return EntityType.PARAMETER case "headers": return EntityType.HEADER case "cookies": return EntityType.COOKIE case "response": return EntityType.RESPONSE case "message": return EntityType.MESSAGE default: return EntityType.PARAMETER } } module.exports.sinkTrigger = sinkTrigger module.exports.addSinkAdditionalInfo = addSinkAdditionalInfo