From a3446604942f339519b84e2c78fa21ccfa6f2c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Fabianski?= Date: Wed, 22 Nov 2023 14:41:34 +0100 Subject: [PATCH] chore: add new evaluator (#180) --- scripts/invoke.sh | 5 +-- tests/helper.js | 36 ++++++++++++++++++- .../__snapshots__/test.js.snap | 26 +++++++------- tests/javascript/lang/eval_user_input/test.js | 32 ++++++++++------- .../lang/eval_user_input/testdata/eval.js | 8 +++-- 5 files changed, 75 insertions(+), 32 deletions(-) diff --git a/scripts/invoke.sh b/scripts/invoke.sh index 15d2983e4..0e690bec4 100755 --- a/scripts/invoke.sh +++ b/scripts/invoke.sh @@ -8,6 +8,7 @@ test_location=$2 rule_id=$3 rule_loc=$PWD/rules BEARER_VERSION=${BEARER_VERSION=latest} +FORMAT=${FORMAT=json} filename=$(basename $test_location) tmp_location=/tmp/bearer-scan/$filename @@ -21,7 +22,7 @@ if [ -n "$BEARER_WORKSPACE" ]; then --quiet \ --disable-default-rules=true \ --external-rule-dir=$rule_loc \ - --format=json \ + --format=${FORMAT} \ --disable-version-check \ --force \ --exit-code=0 \ @@ -37,7 +38,7 @@ else --only-rule=$rule_id \ --disable-default-rules=true \ --external-rule-dir=/tmp/rules \ - --format=json \ + --format=${FORMAT} \ --quiet \ --disable-version-check \ --exit-code=0 \ diff --git a/tests/helper.js b/tests/helper.js index 3cac58dce..c9d2f24bc 100644 --- a/tests/helper.js +++ b/tests/helper.js @@ -29,7 +29,7 @@ exports.createInvoker = (ruleId, ruleFile, testBase) => { results = JSON.parse(out) let findings = [] - for (const [key, values] of Object.entries(results)) { + for (const [_severity, values] of Object.entries(results)) { for (const [value] in values) { findings.push({ // severity: key, @@ -59,3 +59,37 @@ bearer scan ${testBase} --only-rule ${ruleId} --log-level trace` return JSON.stringify(results, null, 2) } } + +function difference(setA, setB) { + const diff = new Set(setA) + + for (const elem of setB) { + diff.delete(elem) + } + + return Array.from(diff) +} + +exports.createNewInvoker = (ruleId, ruleFile, testBase) => { + return (testCase) => { + const out = execSync( + `FORMAT=jsonv2 ./scripts/invoke.sh ${ruleFile} ${testBase}${testCase} ${ruleId}` + ).toString() + + results = JSON.parse(out) + let findings = [] + for (const result of results.findings) { + findings.push(`${result.id}:${result.source.start}`) + } + + let expectedFindings = [] + for (const result of results.expected_findings) { + expectedFindings.push(`${result.rule_id}:${result.location.start}`) + } + + return { + Extra: difference(new Set(findings), new Set(expectedFindings)), + Missing: difference(new Set(expectedFindings), new Set(findings)), + } + } +} diff --git a/tests/javascript/lang/eval_user_input/__snapshots__/test.js.snap b/tests/javascript/lang/eval_user_input/__snapshots__/test.js.snap index ecf6f4892..9b609bdbf 100644 --- a/tests/javascript/lang/eval_user_input/__snapshots__/test.js.snap +++ b/tests/javascript/lang/eval_user_input/__snapshots__/test.js.snap @@ -12,27 +12,27 @@ exports[`javascript_lang_eval_user_input eval 1`] = ` "title": "Dangerous use of eval with user input detected", "description": "## Description\\nUsing \`eval\` (and similar code execution methods such as \`setTimeout\`) with user input is dangerous and can lead to remote code execution.\\n\\n## Remediation\\n❌ As a general rule, avoid using \`eval\`.\\n\\n❌ Avoid using code execution methods with unsanitized user input.\\n\\nInstead, it might be possible to use dynamic hardcoded values:\\n\`\`\`javascript\\n app.post(\\"/:id\\", (req, res) => {\\n let myFunc = \\"(a, b) => a + b\\"\\n if req.params[\\"single_item\\"] {\\n myFunc = \\"(a) => a\\"\\n }\\n\\n setTimeout(myFunc);\\n };\\n\`\`\`\\nor pass user input to a compiled function, instead of compiling it with user input.\\n\`\`\`javascript\\n app.post(\\"/:id\\", (req, res) => {\\n let myFunc = \\"(a, b) => a + b\\"\\n let compiledFunction = vm.compileFunction(myFunc);\\n compiledFunction(req.params[\\"pageCount\\"], req.params[\\"appendixPageCount\\"])\\n };\\n\`\`\`\\n\\n✅ Use JavaScript's strict mode as best practice and to minimize the reach of code execution methods\\n\\n\`\`\`javascript\\n \\"use strict\\"\\n\\n app.post(\\"/:id\\", (req, res) => {\\n ...\\n })\\n\`\`\`\\n\\n## Resources\\n- [MDN JavaScript strict mode reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode)\\n", "documentation_url": "https://docs.bearer.com/reference/rules/javascript_lang_eval_user_input", - "line_number": 11, + "line_number": 12, "full_filename": "/tmp/bearer-scan/eval.js", "filename": ".", "source": { - "start": 11, - "end": 11, + "start": 12, + "end": 12, "column": { "start": 10, "end": 23 } }, "sink": { - "start": 11, - "end": 11, + "start": 12, + "end": 12, "column": { "start": 10, "end": 23 }, "content": "eval(command)" }, - "parent_line_number": 11, + "parent_line_number": 12, "snippet": "eval(command)", "fingerprint": "845c467daab5771a9b5844e411f5576c_0", "old_fingerprint": "047e7e7cf3819e02752e3eb5c918098c_0", @@ -47,31 +47,31 @@ exports[`javascript_lang_eval_user_input eval 1`] = ` "title": "Dangerous use of eval with user input detected", "description": "## Description\\nUsing \`eval\` (and similar code execution methods such as \`setTimeout\`) with user input is dangerous and can lead to remote code execution.\\n\\n## Remediation\\n❌ As a general rule, avoid using \`eval\`.\\n\\n❌ Avoid using code execution methods with unsanitized user input.\\n\\nInstead, it might be possible to use dynamic hardcoded values:\\n\`\`\`javascript\\n app.post(\\"/:id\\", (req, res) => {\\n let myFunc = \\"(a, b) => a + b\\"\\n if req.params[\\"single_item\\"] {\\n myFunc = \\"(a) => a\\"\\n }\\n\\n setTimeout(myFunc);\\n };\\n\`\`\`\\nor pass user input to a compiled function, instead of compiling it with user input.\\n\`\`\`javascript\\n app.post(\\"/:id\\", (req, res) => {\\n let myFunc = \\"(a, b) => a + b\\"\\n let compiledFunction = vm.compileFunction(myFunc);\\n compiledFunction(req.params[\\"pageCount\\"], req.params[\\"appendixPageCount\\"])\\n };\\n\`\`\`\\n\\n✅ Use JavaScript's strict mode as best practice and to minimize the reach of code execution methods\\n\\n\`\`\`javascript\\n \\"use strict\\"\\n\\n app.post(\\"/:id\\", (req, res) => {\\n ...\\n })\\n\`\`\`\\n\\n## Resources\\n- [MDN JavaScript strict mode reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode)\\n", "documentation_url": "https://docs.bearer.com/reference/rules/javascript_lang_eval_user_input", - "line_number": 18, + "line_number": 20, "full_filename": "/tmp/bearer-scan/eval.js", "filename": ".", "source": { - "start": 18, - "end": 18, + "start": 20, + "end": 20, "column": { "start": 3, "end": 16 } }, "sink": { - "start": 18, - "end": 18, + "start": 20, + "end": 20, "column": { "start": 3, "end": 16 }, "content": "eval(context)" }, - "parent_line_number": 18, + "parent_line_number": 20, "snippet": "eval(context)", "fingerprint": "845c467daab5771a9b5844e411f5576c_1", "old_fingerprint": "047e7e7cf3819e02752e3eb5c918098c_1", - "code_extract": " eval(context);" + "code_extract": " eval(context)" } ] }" diff --git a/tests/javascript/lang/eval_user_input/test.js b/tests/javascript/lang/eval_user_input/test.js index 03f127f78..c48cee7ba 100644 --- a/tests/javascript/lang/eval_user_input/test.js +++ b/tests/javascript/lang/eval_user_input/test.js @@ -1,37 +1,43 @@ -const { createInvoker, getEnvironment } = require("../../../helper.js") +const { + createInvoker, + createNewInvoker, + getEnvironment, +} = require("../../../helper.js") const { ruleId, ruleFile, testBase } = getEnvironment(__dirname) describe(ruleId, () => { const invoke = createInvoker(ruleId, ruleFile, testBase) - + const newInvoke = createNewInvoker(ruleId, ruleFile, testBase) test("eval", () => { const testCase = "eval.js" - expect(invoke(testCase)).toMatchSnapshot(); + expect(invoke(testCase)).toMatchSnapshot() + }) + + test("new-eval", () => { + const testCase = "eval.js" + const results = newInvoke(testCase) + expect(results.Missing).toEqual([]) + expect(results.Extra).toEqual([]) }) - test("new_function", () => { const testCase = "new_function.js" - expect(invoke(testCase)).toMatchSnapshot(); + expect(invoke(testCase)).toMatchSnapshot() }) - test("secure", () => { const testCase = "secure.js" - expect(invoke(testCase)).toMatchSnapshot(); + expect(invoke(testCase)).toMatchSnapshot() }) - test("set_interval", () => { const testCase = "set_interval.js" - expect(invoke(testCase)).toMatchSnapshot(); + expect(invoke(testCase)).toMatchSnapshot() }) - test("set_timeout", () => { const testCase = "set_timeout.js" - expect(invoke(testCase)).toMatchSnapshot(); + expect(invoke(testCase)).toMatchSnapshot() }) - -}) \ No newline at end of file +}) diff --git a/tests/javascript/lang/eval_user_input/testdata/eval.js b/tests/javascript/lang/eval_user_input/testdata/eval.js index 9a5888c35..4c6d6ed96 100644 --- a/tests/javascript/lang/eval_user_input/testdata/eval.js +++ b/tests/javascript/lang/eval_user_input/testdata/eval.js @@ -8,12 +8,14 @@ app.use(helmet.hidePoweredBy()) app.post("/:id", (req, res) => { userInput = req.params.id var command = "new Function('" + userInput + "')" + // bearer:expected javascript_lang_eval_user_input return eval(command) }) -const vm = require('node:vm'); +const vm = require("node:vm") exports.handler = async function (event, _context) { - const context = event["params"]["context"]; - eval(context); + const context = event["params"]["context"] + // bearer:expected javascript_lang_eval_user_input + eval(context) }