From e12cb06b4ef0393b2f63633258975c4b7de5c7b6 Mon Sep 17 00:00:00 2001 From: elsapet Date: Fri, 23 Feb 2024 18:49:24 +0200 Subject: [PATCH] feat(js): add handlebars XSS rule (#317) --- .../javascript/lang/handlebars_no_escape.yml | 37 +++++++++++++++++++ .../lang/handlebars_no_escape/test.js | 20 ++++++++++ .../lang/handlebars_no_escape/testdata/app.js | 23 ++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 rules/javascript/lang/handlebars_no_escape.yml create mode 100644 tests/javascript/lang/handlebars_no_escape/test.js create mode 100644 tests/javascript/lang/handlebars_no_escape/testdata/app.js diff --git a/rules/javascript/lang/handlebars_no_escape.yml b/rules/javascript/lang/handlebars_no_escape.yml new file mode 100644 index 000000000..8c0a8ddb8 --- /dev/null +++ b/rules/javascript/lang/handlebars_no_escape.yml @@ -0,0 +1,37 @@ +patterns: + - pattern: handlebars.compile($<...>$) + filters: + - variable: NO_ESCAPE + detection: javascript_lang_handlebars_no_escape_true + scope: result +auxiliary: + - id: javascript_lang_handlebars_no_escape_true + patterns: + - | + { noEscape: true } +languages: + - javascript +severity: warning +metadata: + description: "Missing escape of HTML entities in Handlebars template compilation" + remediation_message: | + ## Description + + As a templating engine, Handlebars generates HTML markup dynamically. + Setting `noEscape` to true disables escaping HTML entities within the template output itself. + + This is a security risk as it could lead to Cross-Site Scripting (XSS) vulnerabilities if + the template is from an untrusted source. + + ## Remediations + + ❌ Do not set `noEscape` to true when compiling Handlebars templates + + ## References + + - [Handlebars compile docs](https://handlebarsjs.com/api-reference/compilation.html#handlebars-compile-template-options) + - [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) + cwe_id: + - 80 + id: javascript_lang_handlebars_no_escape + documentation_url: https://docs.bearer.com/reference/rules/javascript_lang_handlebars_no_escape diff --git a/tests/javascript/lang/handlebars_no_escape/test.js b/tests/javascript/lang/handlebars_no_escape/test.js new file mode 100644 index 000000000..76e2e2a08 --- /dev/null +++ b/tests/javascript/lang/handlebars_no_escape/test.js @@ -0,0 +1,20 @@ +const { + createNewInvoker, + getEnvironment, +} = require("../../../helper.js") +const { ruleId, ruleFile, testBase } = getEnvironment(__dirname) + +describe(ruleId, () => { + const invoke = createNewInvoker(ruleId, ruleFile, testBase) + + test("handlebars_no_escape", () => { + const testCase = "app.js" + + const results = invoke(testCase) + + expect(results).toEqual({ + Missing: [], + Extra: [] + }) + }) +}) \ No newline at end of file diff --git a/tests/javascript/lang/handlebars_no_escape/testdata/app.js b/tests/javascript/lang/handlebars_no_escape/testdata/app.js new file mode 100644 index 000000000..06902176b --- /dev/null +++ b/tests/javascript/lang/handlebars_no_escape/testdata/app.js @@ -0,0 +1,23 @@ +import Handlebars from 'handlebars'; + +const handlebars = Handlebars.create(); + +export function bad(templateStr) { + try { + // bearer:expected javascript_lang_handlebars_no_escape + const template = handlebars.compile(templateStr, { noEscape: true }); + + compiledTemplate = template(vars); + } catch (err) { + // ... + } +} + +export function ok(templateStr) { + try { + const template = handlebars.compile(templateStr, { noEscape: false }); + compiledTemplate = template(vars); + } catch (err) { + // ... + } +} \ No newline at end of file