Skip to content

Commit

Permalink
feat: add ruby eval rule (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
elsapet authored Nov 14, 2023
1 parent 4d73399 commit 447febc
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 31 deletions.
54 changes: 54 additions & 0 deletions rules/ruby/lang/eval_linter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
imports:
- ruby_shared_common_user_input
patterns:
- pattern: |
eval($<INPUT>$<...>)
filters:
- not:
variable: INPUT
detection: ruby_shared_common_user_input
scope: result
- pattern: |
binding.eval($<INPUT>$<...>)
filters:
- not:
variable: INPUT
detection: ruby_shared_common_user_input
scope: result
languages:
- ruby
severity: warning
metadata:
description: "Use of eval detected."
remediation_message: |
## Description
The use of the `eval` function, which dynamically executes code represented as strings, poses a significant security risk in any programming environment. This is primarily because it can be exploited to run arbitrary and potentially harmful code, making the application vulnerable to code injection attacks.
## Remediations
To maintain the security integrity of your application:
❌ Refrain from using `eval`
Avoid using the `eval` function as it executes code that can be manipulated by an attacker. Code execution through `eval` can lead to various injection vulnerabilities.
✅ Explore safer alternatives to `eval`. Depending on the context, these might include:
- Parsing and handling data formats (like JSON) using safe libraries.
- Using functions or libraries specifically designed for the task you're trying to accomplish with `eval`.
- Implementing functionality directly in the language itself, rather than executing dynamically generated code.
✅ Validate and Sanitize Inputs
If there's an absolute necessity to use a form of dynamic code execution, rigorously validate and sanitize all inputs to reduce the risk of malicious code execution.
✅ Use Restricted Execution Environments
In scenarios where dynamic execution is unavoidable, consider running the code in a sandboxed or restricted environment where the potential impact of malicious actions is minimized.
## Resources
- [OWASP: Eval Injection](https://owasp.org/www-community/attacks/Direct_Dynamic_Code_Evaluation_Eval%20Injection)
- [MDN Web Docs: Never use eval!](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval!)
cwe_id:
- 94
id: ruby_lang_eval_linter
documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_eval_linter
8 changes: 4 additions & 4 deletions rules/ruby/lang/eval_using_user_input.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ metadata:
description: "Potential command injection with user input detected."
remediation_message: |
## Description
It is dangerous to use eval with user input, or to compile code with user-supplied data. Such practices can lead to command injection.
It is dangerous to use eval with user input, or to compile code with user-supplied data. Such practices can lead to code injection.
## Remediations
❌ Avoid using code execution methods with unsanitized user input.
Expand All @@ -47,12 +47,12 @@ metadata:
```ruby
get_total_str = if params["include_vat"]
"def dynamic(a,b,c); a + b + c; end"
"def get_total(a,b,c); a + b + c; end"
else
"def dynamic(a,b); a + b; end"
"def get_total(a,b); a + b; end"
end
get_total = eval(get_total_str)
cart.instance_eval(get_total_str)
```
## Resources
Expand Down
80 changes: 80 additions & 0 deletions tests/ruby/lang/eval_linter/__snapshots__/test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ruby_lang_eval_linter ok 1`] = `"{}"`;

exports[`ruby_lang_eval_linter unsafe 1`] = `
"{
"warning": [
{
"cwe_ids": [
"94"
],
"id": "ruby_lang_eval_linter",
"title": "Use of eval detected.",
"description": "## Description\\n\\nThe use of the \`eval\` function, which dynamically executes code represented as strings, poses a significant security risk in any programming environment. This is primarily because it can be exploited to run arbitrary and potentially harmful code, making the application vulnerable to code injection attacks.\\n\\n## Remediations\\n\\nTo maintain the security integrity of your application:\\n\\n❌ Refrain from using \`eval\`\\nAvoid using the \`eval\` function as it executes code that can be manipulated by an attacker. Code execution through \`eval\` can lead to various injection vulnerabilities.\\n\\n✅ Explore safer alternatives to \`eval\`. Depending on the context, these might include:\\n\\n- Parsing and handling data formats (like JSON) using safe libraries.\\n- Using functions or libraries specifically designed for the task you're trying to accomplish with \`eval\`.\\n- Implementing functionality directly in the language itself, rather than executing dynamically generated code.\\n\\n✅ Validate and Sanitize Inputs\\nIf there's an absolute necessity to use a form of dynamic code execution, rigorously validate and sanitize all inputs to reduce the risk of malicious code execution.\\n\\n✅ Use Restricted Execution Environments\\nIn scenarios where dynamic execution is unavoidable, consider running the code in a sandboxed or restricted environment where the potential impact of malicious actions is minimized.\\n\\n## Resources\\n\\n- [OWASP: Eval Injection](https://owasp.org/www-community/attacks/Direct_Dynamic_Code_Evaluation_Eval%20Injection)\\n- [MDN Web Docs: Never use eval!](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval!)\\n",
"documentation_url": "https://docs.bearer.com/reference/rules/ruby_lang_eval_linter",
"line_number": 2,
"full_filename": "/tmp/bearer-scan/unsafe.rb",
"filename": ".",
"source": {
"start": 2,
"end": 2,
"column": {
"start": 3,
"end": 52
}
},
"sink": {
"start": 2,
"end": 2,
"column": {
"start": 3,
"end": 52
},
"content": "eval(\\"def hello_world; puts 'Hello world!'; end\\")"
},
"parent_line_number": 2,
"snippet": "eval(\\"def hello_world; puts 'Hello world!'; end\\")",
"fingerprint": "0c859d45d36946482f464bb83d4fc747_0",
"old_fingerprint": "51dcde79985d5c0794fc5649a5ee1c93_0",
"code_extract": " eval(\\"def hello_world; puts 'Hello world!'; end\\")"
},
{
"cwe_ids": [
"94"
],
"id": "ruby_lang_eval_linter",
"title": "Use of eval detected.",
"description": "## Description\\n\\nThe use of the \`eval\` function, which dynamically executes code represented as strings, poses a significant security risk in any programming environment. This is primarily because it can be exploited to run arbitrary and potentially harmful code, making the application vulnerable to code injection attacks.\\n\\n## Remediations\\n\\nTo maintain the security integrity of your application:\\n\\n❌ Refrain from using \`eval\`\\nAvoid using the \`eval\` function as it executes code that can be manipulated by an attacker. Code execution through \`eval\` can lead to various injection vulnerabilities.\\n\\n✅ Explore safer alternatives to \`eval\`. Depending on the context, these might include:\\n\\n- Parsing and handling data formats (like JSON) using safe libraries.\\n- Using functions or libraries specifically designed for the task you're trying to accomplish with \`eval\`.\\n- Implementing functionality directly in the language itself, rather than executing dynamically generated code.\\n\\n✅ Validate and Sanitize Inputs\\nIf there's an absolute necessity to use a form of dynamic code execution, rigorously validate and sanitize all inputs to reduce the risk of malicious code execution.\\n\\n✅ Use Restricted Execution Environments\\nIn scenarios where dynamic execution is unavoidable, consider running the code in a sandboxed or restricted environment where the potential impact of malicious actions is minimized.\\n\\n## Resources\\n\\n- [OWASP: Eval Injection](https://owasp.org/www-community/attacks/Direct_Dynamic_Code_Evaluation_Eval%20Injection)\\n- [MDN Web Docs: Never use eval!](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval!)\\n",
"documentation_url": "https://docs.bearer.com/reference/rules/ruby_lang_eval_linter",
"line_number": 5,
"full_filename": "/tmp/bearer-scan/unsafe.rb",
"filename": ".",
"source": {
"start": 5,
"end": 5,
"column": {
"start": 3,
"end": 25
}
},
"sink": {
"start": 5,
"end": 5,
"column": {
"start": 3,
"end": 25
},
"content": "binding.eval(some_arg)"
},
"parent_line_number": 5,
"snippet": "binding.eval(some_arg)",
"fingerprint": "0c859d45d36946482f464bb83d4fc747_1",
"old_fingerprint": "51dcde79985d5c0794fc5649a5ee1c93_1",
"code_extract": " binding.eval(some_arg)"
}
]
}"
`;

exports[`ruby_lang_eval_linter with_user_input 1`] = `"{}"`;
21 changes: 21 additions & 0 deletions tests/ruby/lang/eval_linter/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const { createInvoker, getEnvironment } = require("../../../helper.js")
const { ruleId, ruleFile, testBase } = getEnvironment(__dirname)

describe(ruleId, () => {
const invoke = createInvoker(ruleId, ruleFile, testBase)

test("ok", () => {
const testCase = "ok.rb"
expect(invoke(testCase)).toMatchSnapshot();
})

test("unsafe", () => {
const testCase = "unsafe.rb"
expect(invoke(testCase)).toMatchSnapshot();
})

test("with_user_input", () => {
const testCase = "with_user_input.rb"
expect(invoke(testCase)).toMatchSnapshot();
})
})
6 changes: 6 additions & 0 deletions tests/ruby/lang/eval_linter/testdata/ok.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# these still post security risk but they should not be caught by our eval linter
def index
Foo.class_eval(params["my_code"])

foo.instance_eval(params["my_code"])
end
6 changes: 6 additions & 0 deletions tests/ruby/lang/eval_linter/testdata/unsafe.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def index
eval("def hello_world; puts 'Hello world!'; end")

some_arg = "def test; end"
binding.eval(some_arg)
end
6 changes: 6 additions & 0 deletions tests/ruby/lang/eval_linter/testdata/with_user_input.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# user input is present - linter rule should not pick this up
def index
eval(params["my_code"])

binding.eval(params["my_code"])
end
Loading

0 comments on commit 447febc

Please sign in to comment.