Skip to content

Commit

Permalink
chore: add new evaluator (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
cfabianski authored Nov 22, 2023
1 parent 22b1ceb commit a344660
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 32 deletions.
5 changes: 3 additions & 2 deletions scripts/invoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 \
Expand All @@ -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 \
Expand Down
36 changes: 35 additions & 1 deletion tests/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)),
}
}
}
26 changes: 13 additions & 13 deletions tests/javascript/lang/eval_user_input/__snapshots__/test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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)"
}
]
}"
Expand Down
32 changes: 19 additions & 13 deletions tests/javascript/lang/eval_user_input/test.js
Original file line number Diff line number Diff line change
@@ -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()
})

})
})
8 changes: 5 additions & 3 deletions tests/javascript/lang/eval_user_input/testdata/eval.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit a344660

Please sign in to comment.