UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

259 lines (223 loc) 7.46 kB
'use strict' const dc = require('dc-polyfill') const TaintedUtils = require('@datadog/native-iast-taint-tracking') const { storage } = require('../../../../../datadog-core') const iastContextFunctions = require('../iast-context') const { EXECUTED_PROPAGATION } = require('../telemetry/iast-metric') const { isDebugAllowed } = require('../telemetry/verbosity') const log = require('../../../log') const { taintObject } = require('./operations-taint-object') const mathRandomCallCh = dc.channel('datadog:random:call') const evalCallCh = dc.channel('datadog:eval:call') const JSON_VALUE = 'json.value' function noop (res) { return res } // NOTE: methods of this object must be synchronized with csi-methods.js file definitions! // Otherwise you may end up rewriting a method and not providing its rewritten implementation const TaintTrackingNoop = { concat: noop, eval: noop, join: noop, parse: noop, plusOperator: noop, random: noop, replace: noop, slice: noop, substr: noop, substring: noop, stringCase: noop, tplOperator: noop, trim: noop, trimEnd: noop, } function getTransactionId (iastContext) { return iastContext?.[iastContextFunctions.IAST_TRANSACTION_ID] } function getContextDefault () { const store = storage('legacy').getStore() return iastContextFunctions.getIastContext(store) } function getContextDebug () { const iastContext = getContextDefault() EXECUTED_PROPAGATION.inc(iastContext) return iastContext } function getFilteredCsiFn (cb, filter, getContext) { return function csiCall (res, fn, target, ...rest) { try { if (filter(res, fn, target)) { return res } const context = getContext() const transactionId = getTransactionId(context) if (transactionId) { return cb(transactionId, res, target, ...rest) } } catch (e) { log.error('[ASM] Error invoking CSI %s', target, e) } return res } } function notString () { return Array.prototype.some.call(arguments, (p) => typeof p !== 'string') } function isValidCsiMethod (fn, protos) { return protos.includes(fn) } function getCsiFn (cb, getContext, ...protos) { let filter if (!protos || protos.length === 0) { filter = (res, fn, target) => notString(res, target) } else if (protos.length === 1) { const protoFn = protos[0] filter = (res, fn, target) => notString(res, target) || fn !== protoFn } else { filter = (res, fn, target) => notString(res, target) || !isValidCsiMethod(fn, protos) } return getFilteredCsiFn(cb, filter, getContext) } function csiMethodsDefaults (names, excluded, getContext) { const impl = {} for (const name of names) { if (excluded.includes(name)) continue impl[name] = getCsiFn( (transactionId, res, target, ...rest) => TaintedUtils[name](transactionId, res, target, ...rest), getContext, String.prototype[name] ) } return impl } function csiMethodsOverrides (getContext) { return { plusOperator: function (res, op1, op2) { try { if (notString(res) || (notString(op1) && notString(op2))) { return res } const iastContext = getContext() const transactionId = getTransactionId(iastContext) if (transactionId) { return TaintedUtils.concat(transactionId, res, op1, op2) } } catch (e) { log.error('[ASM] Error invoking CSI plusOperator', e) } return res }, tplOperator: function (res, ...rest) { try { const iastContext = getContext() const transactionId = getTransactionId(iastContext) if (transactionId) { return TaintedUtils.concat(transactionId, res, ...rest) } } catch (e) { log.error('[ASM] Error invoking CSI tplOperator', e) } return res }, stringCase: getCsiFn( (transactionId, res, target) => TaintedUtils.stringCase(transactionId, res, target), getContext, String.prototype.toLowerCase, String.prototype.toUpperCase ), trim: getCsiFn( (transactionId, res, target) => TaintedUtils.trim(transactionId, res, target), getContext, String.prototype.trim, String.prototype.trimStart ), random: function (res, fn) { if (mathRandomCallCh.hasSubscribers) { mathRandomCallCh.publish({ fn }) } return res }, eval: function (res, fn, target, script) { // eslint-disable-next-line no-eval if (evalCallCh.hasSubscribers && fn === globalThis.eval) { evalCallCh.publish({ script }) } return res }, parse: function (res, fn, target, json) { if (fn === JSON.parse) { try { const iastContext = getContext() const transactionId = getTransactionId(iastContext) if (transactionId) { const ranges = TaintedUtils.getRanges(transactionId, json) // TODO: first version. // here we are losing the original source because taintObject always creates a new tainted if (ranges?.length > 0) { const range = ranges.find(range => range.iinfo?.type) res = taintObject(iastContext, res, range?.iinfo.type || JSON_VALUE) } } } catch (e) { log.error('[ASM] Error invoking CSI JSON.parse', e) } } return res }, join: function (res, fn, target, separator) { if (fn === Array.prototype.join) { try { const iastContext = getContext() const transactionId = getTransactionId(iastContext) if (transactionId) { res = TaintedUtils.arrayJoin(transactionId, res, target, separator) } } catch (e) { log.error('[ASM] Error invoking CSI join', e) } } return res }, } } function createImplWith (getContext) { const methodNames = Object.keys(TaintTrackingNoop) const overrides = csiMethodsOverrides(getContext) // impls could be cached but at the moment there is only one invocation to getTaintTrackingImpl return { ...csiMethodsDefaults(methodNames, Object.keys(overrides), getContext), ...overrides, } } function getTaintTrackingImpl (telemetryVerbosity, dummy = false) { if (dummy) return TaintTrackingNoop // with Verbosity.DEBUG every invocation of a TaintedUtils method increases the EXECUTED_PROPAGATION metric return isDebugAllowed(telemetryVerbosity) ? createImplWith(getContextDebug) : createImplWith(getContextDefault) } function getTaintTrackingNoop () { return getTaintTrackingImpl(null, true) } const lodashFns = { join: TaintedUtils.arrayJoin, toLower: TaintedUtils.stringCase, toUpper: TaintedUtils.stringCase, trim: TaintedUtils.trim, trimEnd: TaintedUtils.trimEnd, trimStart: TaintedUtils.trim, } function getLodashTaintedUtilFn (lodashFn) { return lodashFns[lodashFn] || ((transactionId, result) => result) } function lodashTaintTrackingHandler (message) { try { if (!message.result) return const context = getContextDefault() const transactionId = getTransactionId(context) if (transactionId) { message.result = getLodashTaintedUtilFn(message.operation)(transactionId, message.result, ...message.arguments) } } catch (e) { log.error('[ASM] Error invoking CSI lodash %s', message.operation, e) } } module.exports = { getTaintTrackingImpl, getTaintTrackingNoop, lodashTaintTrackingHandler, }