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 28, 2024
1 parent c007354 commit 7370611
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/appsec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ jobs:
express:
runs-on: ubuntu-latest
env:
PLUGINS: express|body-parser|cookie-parser
PLUGINS: express|body-parser|cookie-parser|handlebars
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/node/setup
Expand Down
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,71 @@
'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('template-injection-analyzer with handlebars', () => {
withVersions('handlebars', 'handlebars', version => {
let lib, badSource, goodSource
before(() => {
lib = require(`../../../../../../versions/handlebars@${version}`).get()
badSource = `
{{#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}}
`
goodSource = '<p>{{name}}</p>'
})

describe('compile', () => {
prepareTestServerForIast('template injection analyzer',
(testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => {
testThatRequestHasVulnerability(() => {
const store = storage.getStore()
const iastContext = iastContextFunctions.getIastContext(store)
const command = newTaintedString(iastContext, badSource, 'param', 'Request')
const template = lib.compile(command)
template()
}, 'TEMPLATE_INJECTION')

testThatRequestHasNoVulnerability(() => {
const template = lib.compile(goodSource)
template()
}, 'TEMPLATE_INJECTION')
})
})

describe('precompile', () => {
prepareTestServerForIast('template injection analyzer',
(testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => {
testThatRequestHasVulnerability(() => {
const store = storage.getStore()
const iastContext = iastContextFunctions.getIastContext(store)
const command = newTaintedString(iastContext, badSource, 'param', 'Request')
lib.precompile(command)
}, 'TEMPLATE_INJECTION')

testThatRequestHasNoVulnerability(() => {
lib.precompile(goodSource)
}, 'TEMPLATE_INJECTION')
})
})
})
})

0 comments on commit 7370611

Please sign in to comment.