Skip to content

Commit

Permalink
Template injection vulnerability detection in handlebars
Browse files Browse the repository at this point in the history
  • Loading branch information
IlyasShabi committed Oct 25, 2024
1 parent c007354 commit a5f156b
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 0 deletions.
22 changes: 22 additions & 0 deletions packages/datadog-instrumentations/src/handlebars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict'

const shimmer = require('../../datadog-shimmer')
const { channel, addHook } = require('./helpers/instrument')

const handlebarsReadCh = channel('datadog:handlebars:compile:start')

function wrapCompile (compile) {
return function () {
if (handlebarsReadCh.hasSubscribers) {
const source = arguments[0]
handlebarsReadCh.publish({ source })
}
return compile.apply(this, arguments)
}
}

addHook({ name: 'handlebars', file: 'dist/cjs/handlebars/compiler/compiler.js', versions: ['>=4.0.0'] }, compiler => {
shimmer.wrap(compiler, 'compile', wrapCompile)
shimmer.wrap(compiler, 'precompile', wrapCompile)
return compiler
})
1 change: 1 addition & 0 deletions packages/datadog-instrumentations/src/helpers/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ module.exports = {
'generic-pool': () => require('../generic-pool'),
graphql: () => require('../graphql'),
grpc: () => require('../grpc'),
handlebars: () => require('../handlebars'),
hapi: () => require('../hapi'),
http: () => require('../http'),
http2: () => require('../http2'),
Expand Down
1 change: 1 addition & 0 deletions packages/dd-trace/src/appsec/iast/analyzers/analyzers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
PATH_TRAVERSAL_ANALYZER: require('./path-traversal-analyzer'),
SQL_INJECTION_ANALYZER: require('./sql-injection-analyzer'),
SSRF: require('./ssrf-analyzer'),
TEMPLATE_INJECTION_ANALYZER: require('./template-injection-analyzer'),
UNVALIDATED_REDIRECT_ANALYZER: require('./unvalidated-redirect-analyzer'),
WEAK_CIPHER_ANALYZER: require('./weak-cipher-analyzer'),
WEAK_HASH_ANALYZER: require('./weak-hash-analyzer'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict'

const InjectionAnalyzer = require('./injection-analyzer')
const { TEMPLATE_INJECTION } = require('../vulnerabilities')

class TemplateInjectionAnalyzer extends InjectionAnalyzer {
constructor () {
super(TEMPLATE_INJECTION)
}

onConfigure () {
this.addSub('datadog:handlebars:compile:start', ({ source }) => this.analyze(source))
}
}

module.exports = new TemplateInjectionAnalyzer()
1 change: 1 addition & 0 deletions packages/dd-trace/src/appsec/iast/vulnerabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
PATH_TRAVERSAL: 'PATH_TRAVERSAL',
SQL_INJECTION: 'SQL_INJECTION',
SSRF: 'SSRF',
TEMPLATE_INJECTION: 'TEMPLATE_INJECTION',
UNVALIDATED_REDIRECT: 'UNVALIDATED_REDIRECT',
WEAK_CIPHER: 'WEAK_CIPHER',
WEAK_HASH: 'WEAK_HASH',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
'use strict'

const { prepareTestServerForIast } = require('../utils')
const { storage } = require('../../../../../datadog-core')
const iastContextFunctions = require('../../../../src/appsec/iast/iast-context')
const { newTaintedString } = require('../../../../src/appsec/iast/taint-tracking/operations')

describe.only('template-injection-analyzer with handlebars', () => {

Check warning on line 8 in packages/dd-trace/test/appsec/iast/analyzers/template-injection-analyzer.handlebars.spec.js

View workflow job for this annotation

GitHub Actions / lint

Unexpected exclusive mocha test
withVersions('handlebars', 'handlebars', version => {
let lib
before(() => {
lib = require(`../../../../../../versions/handlebars@${version}`).get()
})

describe('compile', () => {
prepareTestServerForIast('template injection analyzer',
(testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => {
testThatRequestHasVulnerability(() => {
const store = storage.getStore()
const iastContext = iastContextFunctions.getIastContext(store)
const source = `
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return JSON.stringify(process.env);"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
`
const command = newTaintedString(iastContext, source, 'param', 'Request')
const template = lib.compile(command)
template()
}, 'TEMPLATE_INJECTION')

testThatRequestHasNoVulnerability(() => {
const source = '<p>{{name}}</p>'
const template = lib.compile(source)
template()
}, 'TEMPLATE_INJECTION')
})
})

describe('precompile', () => {
prepareTestServerForIast('template injection analyzer',
(testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => {
testThatRequestHasVulnerability(() => {
const store = storage.getStore()
const iastContext = iastContextFunctions.getIastContext(store)
const source = `
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return JSON.stringify(process.env);"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
`
const command = newTaintedString(iastContext, source, 'param', 'Request')
lib.precompile(command)
}, 'TEMPLATE_INJECTION')

testThatRequestHasNoVulnerability(() => {
const source = '<p>{{name}}</p>'
lib.precompile(source)
}, 'TEMPLATE_INJECTION')
})
})
})
})

0 comments on commit a5f156b

Please sign in to comment.