From 327b90df153b06c01b3f3fb68ea1043f51b1836c Mon Sep 17 00:00:00 2001 From: David Roe Date: Wed, 10 May 2023 10:12:51 +0100 Subject: [PATCH] feat: gitlab improvements part 2 (#46) --- javascript/aws_lambda/code_injection.yml | 4 +- .../aws_lambda/os_command_injection.yml | 3 +- .../testdata/os_command_injection.js | 4 +- javascript/aws_lambda/query_injection.yml | 4 +- javascript/aws_lambda/sql_injection.yml | 3 +- .../lang/format_string_using_user_input.yml | 4 +- javascript/lang/hardcoded_secret.yml | 35 ++- .../.snapshots/unsecure_assigment.yml | 35 +++ .../.snapshots/unsecure_object.yml | 12 +- .../lang/hardcoded_secret/testdata/secure.js | 4 + .../testdata/unsecure_assigment.js | 2 + javascript/lang/http_url_using_user_input.yml | 4 +- .../.snapshots/bad.yml | 72 ++++++ .../http_url_using_user_input/testdata/bad.js | 9 + .../http_url_using_user_input/testdata/ok.js | 8 + javascript/lang/raw_html_using_user_input.yml | 71 ++++++ .../.snapshots/bad.yml | 78 +++++++ .../.snapshots/ok.yml | 2 + .../raw_html_using_user_input/testdata/bad.js | 2 + .../raw_html_using_user_input/testdata/ok.js | 1 + javascript/lang/regex_using_user_input.yml | 40 ++++ .../regex_using_user_input/.snapshots/bad.yml | 27 +++ .../regex_using_user_input/.snapshots/ok.yml | 2 + .../regex_using_user_input/testdata/bad.js | 1 + .../regex_using_user_input/testdata/ok.js | 1 + javascript/lang/websocket_insecure.yml | 47 ++++ .../websocket_insecure/.snapshots/bad.yml | 35 +++ .../lang/websocket_insecure/.snapshots/ok.yml | 2 + .../lang/websocket_insecure/testdata/bad.js | 1 + .../lang/websocket_insecure/testdata/ok.js | 3 + ruby/lang/deserialization_of_user_input.yml | 35 +-- ruby/lang/eval_using_user_input.yml | 2 +- ruby/lang/exec_using_user_input.yml | 2 +- ruby/lang/format_string_using_user_input.yml | 2 +- ruby/lang/ftp_using_user_input.yml | 2 +- ruby/lang/hardcoded_secret.yml | 15 ++ ruby/lang/hardcoded_secret/testdata/ok.rb | 3 + ruby/lang/http_url_using_user_input.yml | 23 +- .../.snapshots/unsafe_open.yml | 116 ++++++++++ .../testdata/ok_not_unsafe.rb | 7 + .../testdata/unsafe_open.rb | 7 + ruby/lang/path_using_user_input.yml | 47 +++- .../.snapshots/unsafe_rails.yml | 41 +++- .../testdata/ok_not_unsafe.rb | 4 + .../testdata/unsafe_rails.rb | 1 + ruby/lang/raw_html_using_user_input.yml | 90 ++++++++ .../.snapshots/bad.yml | 212 ++++++++++++++++++ .../.snapshots/ok.yml | 2 + .../raw_html_using_user_input/testdata/bad.rb | 9 + .../raw_html_using_user_input/testdata/ok.rb | 7 + ruby/lang/reflection_using_user_input.yml | 2 +- ruby/lang/regex_using_user_input.yml | 2 +- ruby/lang/weak_encryption.yml | 4 + ruby/lang/websocket_insecure.yml | 79 +++++++ .../websocket_insecure/.snapshots/bad.yml | 200 +++++++++++++++++ .../lang/websocket_insecure/.snapshots/ok.yml | 2 + ruby/lang/websocket_insecure/testdata/bad.rb | 9 + ruby/lang/websocket_insecure/testdata/ok.rb | 12 + ruby/rails/detailed_exceptions.yml | 63 ++++++ .../detailed_exceptions/.snapshots/bad.yml | 73 ++++++ .../.snapshots/development.yml | 2 + .../detailed_exceptions/.snapshots/ok.yml | 2 + .../rails/detailed_exceptions/testdata/bad.rb | 7 + .../testdata/development.rb | 1 + ruby/rails/detailed_exceptions/testdata/ok.rb | 13 ++ ruby/rails/open_redirect.yml | 26 ++- ruby/rails/open_redirect/.snapshots/ok.yml | 2 + .../open_redirect/.snapshots/unsecure.yml | 2 +- ruby/rails/open_redirect/testdata/ok.rb | 5 + ruby/rails/open_redirect/testdata/unsecure.rb | 6 +- ruby/rails/permissive_parameters.yml | 10 +- .../permissive_parameters/.snapshots/bad.yml | 36 ++- .../permissive_parameters/testdata/bad.rb | 1 + .../permissive_parameters/testdata/ok.rb | 1 + ruby/rails/permissive_regex_validation.yml | 4 +- .../testdata/ok.rb | 1 + ruby/rails/render_using_user_input.yml | 2 +- ruby/rails/session_key_using_user_input.yml | 2 +- ruby/rails/sql_injection.yml | 2 +- scripts/rule_schema.json | 8 +- 80 files changed, 1640 insertions(+), 82 deletions(-) create mode 100644 javascript/lang/raw_html_using_user_input.yml create mode 100644 javascript/lang/raw_html_using_user_input/.snapshots/bad.yml create mode 100644 javascript/lang/raw_html_using_user_input/.snapshots/ok.yml create mode 100644 javascript/lang/raw_html_using_user_input/testdata/bad.js create mode 100644 javascript/lang/raw_html_using_user_input/testdata/ok.js create mode 100644 javascript/lang/regex_using_user_input.yml create mode 100644 javascript/lang/regex_using_user_input/.snapshots/bad.yml create mode 100644 javascript/lang/regex_using_user_input/.snapshots/ok.yml create mode 100644 javascript/lang/regex_using_user_input/testdata/bad.js create mode 100644 javascript/lang/regex_using_user_input/testdata/ok.js create mode 100644 javascript/lang/websocket_insecure.yml create mode 100644 javascript/lang/websocket_insecure/.snapshots/bad.yml create mode 100644 javascript/lang/websocket_insecure/.snapshots/ok.yml create mode 100644 javascript/lang/websocket_insecure/testdata/bad.js create mode 100644 javascript/lang/websocket_insecure/testdata/ok.js create mode 100644 ruby/lang/http_url_using_user_input/.snapshots/unsafe_open.yml create mode 100644 ruby/lang/http_url_using_user_input/testdata/unsafe_open.rb create mode 100644 ruby/lang/raw_html_using_user_input.yml create mode 100644 ruby/lang/raw_html_using_user_input/.snapshots/bad.yml create mode 100644 ruby/lang/raw_html_using_user_input/.snapshots/ok.yml create mode 100644 ruby/lang/raw_html_using_user_input/testdata/bad.rb create mode 100644 ruby/lang/raw_html_using_user_input/testdata/ok.rb create mode 100644 ruby/lang/websocket_insecure.yml create mode 100644 ruby/lang/websocket_insecure/.snapshots/bad.yml create mode 100644 ruby/lang/websocket_insecure/.snapshots/ok.yml create mode 100644 ruby/lang/websocket_insecure/testdata/bad.rb create mode 100644 ruby/lang/websocket_insecure/testdata/ok.rb create mode 100644 ruby/rails/detailed_exceptions.yml create mode 100644 ruby/rails/detailed_exceptions/.snapshots/bad.yml create mode 100644 ruby/rails/detailed_exceptions/.snapshots/development.yml create mode 100644 ruby/rails/detailed_exceptions/.snapshots/ok.yml create mode 100644 ruby/rails/detailed_exceptions/testdata/bad.rb create mode 100644 ruby/rails/detailed_exceptions/testdata/development.rb create mode 100644 ruby/rails/detailed_exceptions/testdata/ok.rb create mode 100644 ruby/rails/open_redirect/.snapshots/ok.yml create mode 100644 ruby/rails/open_redirect/testdata/ok.rb diff --git a/javascript/aws_lambda/code_injection.yml b/javascript/aws_lambda/code_injection.yml index 77e5a7a9a..38bf9cc6e 100644 --- a/javascript/aws_lambda/code_injection.yml +++ b/javascript/aws_lambda/code_injection.yml @@ -39,8 +39,8 @@ patterns: auxiliary: - id: javascript_aws_lambda_code_injection_user_input patterns: - - event.$<_> - - event[$<_>] + - async function ($event, $<_>) {} + - async ($event, $<_>) => {} languages: - javascript severity: high diff --git a/javascript/aws_lambda/os_command_injection.yml b/javascript/aws_lambda/os_command_injection.yml index 926986e82..81118ba42 100644 --- a/javascript/aws_lambda/os_command_injection.yml +++ b/javascript/aws_lambda/os_command_injection.yml @@ -13,7 +13,8 @@ patterns: auxiliary: - id: javascript_aws_lambda_cross_site_scripting_event patterns: - - event + - async function ($event, $<_>) {} + - async ($event, $<_>) => {} languages: - javascript severity: high diff --git a/javascript/aws_lambda/os_command_injection/testdata/os_command_injection.js b/javascript/aws_lambda/os_command_injection/testdata/os_command_injection.js index 8787f0847..478e53802 100644 --- a/javascript/aws_lambda/os_command_injection/testdata/os_command_injection.js +++ b/javascript/aws_lambda/os_command_injection/testdata/os_command_injection.js @@ -1,6 +1,6 @@ const { exec, execSync, spawn, spawnSync } = require('node:child_process'); -exports.handler = async (event) => { +exports.handler = async (event, _context) => { exec("ls "+event["user_dir"]+"| wc -l", (err, stdout, stderr) => { // do something }); @@ -12,4 +12,4 @@ exports.handler = async (event) => { spawn(event["query"]); spawnSync("grep " + event["tmp"]) -}; \ No newline at end of file +}; diff --git a/javascript/aws_lambda/query_injection.yml b/javascript/aws_lambda/query_injection.yml index f505e03d7..4c1dfca78 100644 --- a/javascript/aws_lambda/query_injection.yml +++ b/javascript/aws_lambda/query_injection.yml @@ -14,8 +14,8 @@ patterns: auxiliary: - id: javascript_aws_lambda_query_injection_user_input patterns: - - event.$<_> - - event[$<_>] + - async function ($event, $<_>) {} + - async ($event, $<_>) => {} - id: javascript_aws_lambda_query_injection_hash patterns: - | diff --git a/javascript/aws_lambda/sql_injection.yml b/javascript/aws_lambda/sql_injection.yml index 22c7e3e65..e8b9c3b78 100644 --- a/javascript/aws_lambda/sql_injection.yml +++ b/javascript/aws_lambda/sql_injection.yml @@ -50,7 +50,8 @@ patterns: auxiliary: - id: javascript_aws_lambda_sql_injection_event patterns: - - event.$<_> + - async function ($event, $<_>) {} + - async ($event, $<_>) => {} - id: javascript_aws_lambda_sql_injection_pg_client patterns: - new Client() diff --git a/javascript/lang/format_string_using_user_input.yml b/javascript/lang/format_string_using_user_input.yml index 18b6bd806..469013318 100644 --- a/javascript/lang/format_string_using_user_input.yml +++ b/javascript/lang/format_string_using_user_input.yml @@ -30,8 +30,8 @@ auxiliary: - req.cookies - req.headers # AWS - - event.$<_> - - event[$<_>] + - async function ($event, $<_>) {} + - async ($event, $<_>) => {} severity: low metadata: description: "User input in format string detected." diff --git a/javascript/lang/hardcoded_secret.yml b/javascript/lang/hardcoded_secret.yml index 248a8d12a..47371d18b 100644 --- a/javascript/lang/hardcoded_secret.yml +++ b/javascript/lang/hardcoded_secret.yml @@ -1,26 +1,37 @@ patterns: - pattern: | - { $: $ } + const $ = $ filters: - - variable: KEY - values: - - clientSecret - - secretOrKey - - consumerSecret + - variable: NAME + regex: (?i)(password|api_?key|secret)\b - variable: STRING_LITERAL detection: string_literal contains: false + - not: + variable: STRING_LITERAL + string_regex: \A[*•]+\z - pattern: | - $<_>.$ = $ + $<_>.$ = $ filters: - - variable: KEY - values: - - clientSecret - - secretOrKey - - consumerSecret + - variable: NAME + regex: (?i)(password|api_?key|secret)\b + - variable: STRING_LITERAL + detection: string_literal + contains: false + - not: + variable: STRING_LITERAL + string_regex: \A[*•]+\z + - pattern: | + { $$: $ } + filters: + - variable: NAME + regex: (?i)(password|api_?key|secret)\b - variable: STRING_LITERAL detection: string_literal contains: false + - not: + variable: STRING_LITERAL + string_regex: \A[*•]+\z - pattern: crypto.$($<_>, $$<...>) filters: - variable: METHOD diff --git a/javascript/lang/hardcoded_secret/.snapshots/unsecure_assigment.yml b/javascript/lang/hardcoded_secret/.snapshots/unsecure_assigment.yml index d8289b42d..b6f67c614 100644 --- a/javascript/lang/hardcoded_secret/.snapshots/unsecure_assigment.yml +++ b/javascript/lang/hardcoded_secret/.snapshots/unsecure_assigment.yml @@ -34,4 +34,39 @@ high: parent_line_number: 2 snippet: config.clientSecret = "secretHardcodedString" fingerprint: 33f4ae54bd29e69c4afb8971fe2b4c1c_0 + - rule: + cwe_ids: + - "798" + id: javascript_lang_hardcoded_secret + title: Hardcoded secret detected + description: | + ## Description + + Code is not a safe place to store secrets, use environment variables instead. + + ## Remediations + ```javascript + passport.use(new OAuth2Strategy({ + authorizationURL: 'https://www.example.com/oauth2/authorize', + tokenURL: 'https://www.example.com/oauth2/token', + clientID: process.env.CLIENT_ID, + clientSecret: process.env.CLIENT_SECRET, + callbackURL: "http://localhost:3000/auth/example/callback" + }, + function(accessToken, refreshToken, profile, cb) { + User.findOrCreate({ exampleId: profile.id }, function (err, user) { + return cb(err, user); + }); + } + )); + ``` + + ## Resources + - [OWASP hardcoded passwords](https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_password) + documentation_url: https://docs.bearer.com/reference/rules/javascript_lang_hardcoded_secret + line_number: 4 + filename: /tmp/scan/unsecure_assigment.js + parent_line_number: 4 + snippet: const apiKey = "oops" + fingerprint: 33f4ae54bd29e69c4afb8971fe2b4c1c_1 diff --git a/javascript/lang/hardcoded_secret/.snapshots/unsecure_object.yml b/javascript/lang/hardcoded_secret/.snapshots/unsecure_object.yml index 97fed81a5..812eebbe1 100644 --- a/javascript/lang/hardcoded_secret/.snapshots/unsecure_object.yml +++ b/javascript/lang/hardcoded_secret/.snapshots/unsecure_object.yml @@ -29,18 +29,12 @@ high: ## Resources - [OWASP hardcoded passwords](https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_password) documentation_url: https://docs.bearer.com/reference/rules/javascript_lang_hardcoded_secret - line_number: 1 + line_number: 3 filename: /tmp/scan/unsecure_object.js category_groups: - PII - Personal Data - parent_line_number: 1 - snippet: |- - { - clientID: process.env["GOOGLE_CLIENT_ID"], - clientSecret: "secretHardcodedString", - callbackURL: "/oauth2/redirect/google", - scope: ["profile"], - } + parent_line_number: 3 + snippet: 'clientSecret: "secretHardcodedString"' fingerprint: 08b891215f23b5f7082f674966366927_0 diff --git a/javascript/lang/hardcoded_secret/testdata/secure.js b/javascript/lang/hardcoded_secret/testdata/secure.js index b26f37746..a7873fe66 100644 --- a/javascript/lang/hardcoded_secret/testdata/secure.js +++ b/javascript/lang/hardcoded_secret/testdata/secure.js @@ -13,3 +13,7 @@ crypto.privateEncrypt({ passphrase: process.env["SECRET"] }, buffer) const s = crypto.createSign('RSA-SHA256') s.sign(process.env["SECRET"], "utf-8") + +const apiKey = "**" +o.password = "***" +const foo = { password: "••" } diff --git a/javascript/lang/hardcoded_secret/testdata/unsecure_assigment.js b/javascript/lang/hardcoded_secret/testdata/unsecure_assigment.js index 89d1146f0..e4721cedb 100644 --- a/javascript/lang/hardcoded_secret/testdata/unsecure_assigment.js +++ b/javascript/lang/hardcoded_secret/testdata/unsecure_assigment.js @@ -1,2 +1,4 @@ const config = {}; config.clientSecret = "secretHardcodedString"; + +const apiKey = "oops" diff --git a/javascript/lang/http_url_using_user_input.yml b/javascript/lang/http_url_using_user_input.yml index abf5b345c..96006c454 100644 --- a/javascript/lang/http_url_using_user_input.yml +++ b/javascript/lang/http_url_using_user_input.yml @@ -72,8 +72,8 @@ auxiliary: - req.cookies - req.headers # AWS - - event.$<_> - - event[$<_>] + - async function ($event, $<_>) {} + - async ($event, $<_>) => {} severity: high metadata: description: "HTTP communication with user-controlled destination detected." diff --git a/javascript/lang/http_url_using_user_input/.snapshots/bad.yml b/javascript/lang/http_url_using_user_input/.snapshots/bad.yml index 92fb41616..63c087803 100644 --- a/javascript/lang/http_url_using_user_input/.snapshots/bad.yml +++ b/javascript/lang/http_url_using_user_input/.snapshots/bad.yml @@ -215,4 +215,76 @@ high: parent_line_number: 27 snippet: request(url, function () {}) fingerprint: 62309be37a26b5016cdbcb3c143da49f_5 + - rule: + cwe_ids: + - "918" + id: javascript_lang_http_url_using_user_input + title: HTTP communication with user-controlled destination detected. + description: | + ## Description + + Applications should not connect to locations formed from user input. + This rule checks for URLs containing user-supplied data. + + ## Remediations + + ❌ Avoid using user input in HTTP URLs: + + ```javascript + const response = axios.get(`https://${req.params.host}`) + ``` + + ✅ Use user input indirectly to form a URL: + + ```javascript + const hosts = new Map([ + ["option1", "api1.com"], + ["option2", "api2.com"] + ]) + + const host = hosts.get(req.params.host) + const response = axois.get(`https://${host}`) + ``` + documentation_url: https://docs.bearer.com/reference/rules/javascript_lang_http_url_using_user_input + line_number: 31 + filename: /tmp/scan/bad.js + parent_line_number: 31 + snippet: axios.get(event.url) + fingerprint: 62309be37a26b5016cdbcb3c143da49f_6 + - rule: + cwe_ids: + - "918" + id: javascript_lang_http_url_using_user_input + title: HTTP communication with user-controlled destination detected. + description: | + ## Description + + Applications should not connect to locations formed from user input. + This rule checks for URLs containing user-supplied data. + + ## Remediations + + ❌ Avoid using user input in HTTP URLs: + + ```javascript + const response = axios.get(`https://${req.params.host}`) + ``` + + ✅ Use user input indirectly to form a URL: + + ```javascript + const hosts = new Map([ + ["option1", "api1.com"], + ["option2", "api2.com"] + ]) + + const host = hosts.get(req.params.host) + const response = axois.get(`https://${host}`) + ``` + documentation_url: https://docs.bearer.com/reference/rules/javascript_lang_http_url_using_user_input + line_number: 35 + filename: /tmp/scan/bad.js + parent_line_number: 35 + snippet: axios.get(event["url"]) + fingerprint: 62309be37a26b5016cdbcb3c143da49f_7 diff --git a/javascript/lang/http_url_using_user_input/testdata/bad.js b/javascript/lang/http_url_using_user_input/testdata/bad.js index 3c829fd8d..28d549283 100644 --- a/javascript/lang/http_url_using_user_input/testdata/bad.js +++ b/javascript/lang/http_url_using_user_input/testdata/bad.js @@ -25,3 +25,12 @@ const request = require('request') const imageRequest = request.get(url) request(url, function () {}) + + +export const handler = async (event, context) => { + axios.get(event.url) +} + +exports.handler = async function (event, context) { + axios.get(event["url"]) +} diff --git a/javascript/lang/http_url_using_user_input/testdata/ok.js b/javascript/lang/http_url_using_user_input/testdata/ok.js index f47c2ef11..bc2165f35 100644 --- a/javascript/lang/http_url_using_user_input/testdata/ok.js +++ b/javascript/lang/http_url_using_user_input/testdata/ok.js @@ -25,3 +25,11 @@ const request = require('request') const imageRequest = request.get(url) request(url, function () {}) + +export const handler = async (event, context) => { + axios.get(url) +} + +exports.handler = async function (event, context) { + axios.get(url) +} diff --git a/javascript/lang/raw_html_using_user_input.yml b/javascript/lang/raw_html_using_user_input.yml new file mode 100644 index 000000000..ed5d423bb --- /dev/null +++ b/javascript/lang/raw_html_using_user_input.yml @@ -0,0 +1,71 @@ +languages: + - javascript +patterns: + - pattern: $ + filters: + - variable: STRING + string_regex: <\w+(\s[^>]*)?> + - variable: STRING + detection: javascript_lang_raw_html_using_user_input_user_input + - not: + variable: STRING + detection: javascript_lang_raw_html_using_user_input_sanitized +auxiliary: + - id: javascript_lang_raw_html_using_user_input_user_input_source + patterns: + # Express + - req.params + - req.query + - req.body + - req.cookies + - req.headers + # AWS + - async function ($event, $<_>) {} + - async ($event, $<_>) => {} + - id: javascript_lang_raw_html_using_user_input_user_input + patterns: + - pattern: $ + filters: + - variable: USER_INPUT + detection: javascript_lang_raw_html_using_user_input_user_input_source + contains: false + - id: javascript_lang_raw_html_using_user_input_sanitized + patterns: + - pattern: sanitizeHtml($$<...>) + filters: + - variable: SANITIZED_USER_INPUT + detection: javascript_lang_raw_html_using_user_input_user_input +severity: high +metadata: + description: "Unsanitized user input detected in raw HTML string." + remediation_message: | + ## Description + + Applications should not include unsanitized user input in HTML. This + can allow cross-site scripting (XSS) attacks. + + ## Remediations + + ❌ Avoid including user input directly in HTML strings: + + ```javascript + const html = `

${req.params.title}

` + ``` + + ✅ Use a framework or templating language to construct the HTML. + + ✅ When HTML strings must be used, sanitize user input: + + ```javascript + import sanitizeHtml from 'sanitize-html' + + const sanitizedTitle = sanitizeHtml(req.params.title) + const html = `

${sanitizedTitle}

` + ``` + + ## Resources + - [OWASP Cross-Site Scripting (XSS) Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) + cwe_id: + - 79 + id: javascript_lang_raw_html_using_user_input + documentation_url: https://docs.bearer.com/reference/rules/javascript_lang_raw_html_using_user_input diff --git a/javascript/lang/raw_html_using_user_input/.snapshots/bad.yml b/javascript/lang/raw_html_using_user_input/.snapshots/bad.yml new file mode 100644 index 000000000..4d6776dce --- /dev/null +++ b/javascript/lang/raw_html_using_user_input/.snapshots/bad.yml @@ -0,0 +1,78 @@ +high: + - rule: + cwe_ids: + - "79" + id: javascript_lang_raw_html_using_user_input + title: Unsanitized user input detected in raw HTML string. + description: | + ## Description + + Applications should not include unsanitized user input in HTML. This + can allow cross-site scripting (XSS) attacks. + + ## Remediations + + ❌ Avoid including user input directly in HTML strings: + + ```javascript + const html = `

${req.params.title}

` + ``` + + ✅ Use a framework or templating language to construct the HTML. + + ✅ When HTML strings must be used, sanitize user input: + + ```javascript + import sanitizeHtml from 'sanitize-html' + + const sanitizedTitle = sanitizeHtml(req.params.title) + const html = `

${sanitizedTitle}

` + ``` + + ## Resources + - [OWASP Cross-Site Scripting (XSS) Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) + documentation_url: https://docs.bearer.com/reference/rules/javascript_lang_raw_html_using_user_input + line_number: 1 + filename: /tmp/scan/bad.js + parent_line_number: 1 + snippet: '`

${req.params.oops}

`' + fingerprint: a0a2333cacbf9549cd4f61e405954319_0 + - rule: + cwe_ids: + - "79" + id: javascript_lang_raw_html_using_user_input + title: Unsanitized user input detected in raw HTML string. + description: | + ## Description + + Applications should not include unsanitized user input in HTML. This + can allow cross-site scripting (XSS) attacks. + + ## Remediations + + ❌ Avoid including user input directly in HTML strings: + + ```javascript + const html = `

${req.params.title}

` + ``` + + ✅ Use a framework or templating language to construct the HTML. + + ✅ When HTML strings must be used, sanitize user input: + + ```javascript + import sanitizeHtml from 'sanitize-html' + + const sanitizedTitle = sanitizeHtml(req.params.title) + const html = `

${sanitizedTitle}

` + ``` + + ## Resources + - [OWASP Cross-Site Scripting (XSS) Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) + documentation_url: https://docs.bearer.com/reference/rules/javascript_lang_raw_html_using_user_input + line_number: 2 + filename: /tmp/scan/bad.js + parent_line_number: 2 + snippet: '`

${req.params.oops}

`' + fingerprint: a0a2333cacbf9549cd4f61e405954319_1 + diff --git a/javascript/lang/raw_html_using_user_input/.snapshots/ok.yml b/javascript/lang/raw_html_using_user_input/.snapshots/ok.yml new file mode 100644 index 000000000..311847daa --- /dev/null +++ b/javascript/lang/raw_html_using_user_input/.snapshots/ok.yml @@ -0,0 +1,2 @@ +{} + diff --git a/javascript/lang/raw_html_using_user_input/testdata/bad.js b/javascript/lang/raw_html_using_user_input/testdata/bad.js new file mode 100644 index 000000000..3bca22679 --- /dev/null +++ b/javascript/lang/raw_html_using_user_input/testdata/bad.js @@ -0,0 +1,2 @@ +`

${req.params.oops}

` +`

${req.params.oops}

` diff --git a/javascript/lang/raw_html_using_user_input/testdata/ok.js b/javascript/lang/raw_html_using_user_input/testdata/ok.js new file mode 100644 index 000000000..affad1898 --- /dev/null +++ b/javascript/lang/raw_html_using_user_input/testdata/ok.js @@ -0,0 +1 @@ +`

#{sanitizeHtml(req.params.ok)}

` diff --git a/javascript/lang/regex_using_user_input.yml b/javascript/lang/regex_using_user_input.yml new file mode 100644 index 000000000..93437f33d --- /dev/null +++ b/javascript/lang/regex_using_user_input.yml @@ -0,0 +1,40 @@ +patterns: + - pattern: | + new RegExp($$<...>) + filters: + - variable: USER_INPUT + detection: javascript_lang_regex_using_user_input_user_input +auxiliary: + - id: javascript_lang_regex_using_user_input_user_input + patterns: + # Express + - req.params + - req.query + - req.body + - req.cookies + - req.headers + # AWS + - async function ($event, $<_>) {} + - async ($event, $<_>) => {} +languages: + - javascript +metadata: + description: "Regular expression built from user input detected." + remediation_message: | + ## Description + + Applications should avoid constructing regular expressions from user input. + Regular expressions can have exponential worst-case computational + complexity, allowing users to trigger this behaviour can result in + excessive CPU consumption causing a regular expression denial of service (ReDoS). + + ## Remediations + + ❌ Avoid using untrusted or user data when building regular expressions + + ## Resources + - [OWASP ReDoS attacks explained](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) + cwe_id: + - 1333 + id: javascript_lang_regex_using_user_input + documentation_url: https://docs.bearer.com/reference/rules/javascript_lang_regex_using_user_input diff --git a/javascript/lang/regex_using_user_input/.snapshots/bad.yml b/javascript/lang/regex_using_user_input/.snapshots/bad.yml new file mode 100644 index 000000000..e6f89e61a --- /dev/null +++ b/javascript/lang/regex_using_user_input/.snapshots/bad.yml @@ -0,0 +1,27 @@ +low: + - rule: + cwe_ids: + - "1333" + id: javascript_lang_regex_using_user_input + title: Regular expression built from user input detected. + description: | + ## Description + + Applications should avoid constructing regular expressions from user input. + Regular expressions can have exponential worst-case computational + complexity, allowing users to trigger this behaviour can result in + excessive CPU consumption causing a regular expression denial of service (ReDoS). + + ## Remediations + + ❌ Avoid using untrusted or user data when building regular expressions + + ## Resources + - [OWASP ReDoS attacks explained](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) + documentation_url: https://docs.bearer.com/reference/rules/javascript_lang_regex_using_user_input + line_number: 1 + filename: /tmp/scan/bad.js + parent_line_number: 1 + snippet: new RegExp(`abc${req.params.oops}`, 'i') + fingerprint: b1bcb8fc8ddea5d10e1ed23067f8008b_0 + diff --git a/javascript/lang/regex_using_user_input/.snapshots/ok.yml b/javascript/lang/regex_using_user_input/.snapshots/ok.yml new file mode 100644 index 000000000..311847daa --- /dev/null +++ b/javascript/lang/regex_using_user_input/.snapshots/ok.yml @@ -0,0 +1,2 @@ +{} + diff --git a/javascript/lang/regex_using_user_input/testdata/bad.js b/javascript/lang/regex_using_user_input/testdata/bad.js new file mode 100644 index 000000000..14af8e4a2 --- /dev/null +++ b/javascript/lang/regex_using_user_input/testdata/bad.js @@ -0,0 +1 @@ +new RegExp(`abc${req.params.oops}`, 'i') diff --git a/javascript/lang/regex_using_user_input/testdata/ok.js b/javascript/lang/regex_using_user_input/testdata/ok.js new file mode 100644 index 000000000..019746d2d --- /dev/null +++ b/javascript/lang/regex_using_user_input/testdata/ok.js @@ -0,0 +1 @@ +new RegExp(`abc${ok}`, 'i') diff --git a/javascript/lang/websocket_insecure.yml b/javascript/lang/websocket_insecure.yml new file mode 100644 index 000000000..5b024d85b --- /dev/null +++ b/javascript/lang/websocket_insecure.yml @@ -0,0 +1,47 @@ +languages: + - javascript +patterns: + - pattern: new WebSocket($$<...>) + filters: + - variable: INSECURE_URL + detection: javascript_lang_websocket_insecure_url + contains: false +auxiliary: + - id: javascript_lang_websocket_insecure_url + patterns: + - pattern: $ + filters: + - variable: URL + string_regex: '\A(ws|http):' + - not: + variable: URL + string_regex: '\A(ws|http)://(localhost|127\.0\.0\.1)' +severity: low +metadata: + description: "Insecure websocket communication detected." + remediation_message: | + ## Description + + Applications should only connect to APIs using SSL connections. This rule + checks that all websocket connections use SSL. + + ## Remediations + + ❌ Avoid using unsecured outgoing websocket communication: + + ```javascript + const client = new WebSocket('ws://insecure-api.com') + ``` + + ✅ Always connect using SSL: + + ```javascript + const client = new WebSocket('wss://secure-api.com') + ``` + + ## Resources + - [OWASP insecure transport](https://owasp.org/www-community/vulnerabilities/Insecure_Transport) + cwe_id: + - 319 + id: javascript_lang_websocket_insecure + documentation_url: https://docs.bearer.com/reference/rules/javascript_lang_websocket_insecure diff --git a/javascript/lang/websocket_insecure/.snapshots/bad.yml b/javascript/lang/websocket_insecure/.snapshots/bad.yml new file mode 100644 index 000000000..b49bdc4eb --- /dev/null +++ b/javascript/lang/websocket_insecure/.snapshots/bad.yml @@ -0,0 +1,35 @@ +low: + - rule: + cwe_ids: + - "319" + id: javascript_lang_websocket_insecure + title: Insecure websocket communication detected. + description: | + ## Description + + Applications should only connect to APIs using SSL connections. This rule + checks that all websocket connections use SSL. + + ## Remediations + + ❌ Avoid using unsecured outgoing websocket communication: + + ```javascript + const client = new WebSocket('ws://insecure-api.com') + ``` + + ✅ Always connect using SSL: + + ```javascript + const client = new WebSocket('wss://secure-api.com') + ``` + + ## Resources + - [OWASP insecure transport](https://owasp.org/www-community/vulnerabilities/Insecure_Transport) + documentation_url: https://docs.bearer.com/reference/rules/javascript_lang_websocket_insecure + line_number: 1 + filename: /tmp/scan/bad.js + parent_line_number: 1 + snippet: new WebSocket("ws://insecure.com", {}) + fingerprint: 5030e8af750b035df07204ef3ae95efe_0 + diff --git a/javascript/lang/websocket_insecure/.snapshots/ok.yml b/javascript/lang/websocket_insecure/.snapshots/ok.yml new file mode 100644 index 000000000..311847daa --- /dev/null +++ b/javascript/lang/websocket_insecure/.snapshots/ok.yml @@ -0,0 +1,2 @@ +{} + diff --git a/javascript/lang/websocket_insecure/testdata/bad.js b/javascript/lang/websocket_insecure/testdata/bad.js new file mode 100644 index 000000000..92fffc9ba --- /dev/null +++ b/javascript/lang/websocket_insecure/testdata/bad.js @@ -0,0 +1 @@ +new WebSocket("ws://insecure.com", {}) diff --git a/javascript/lang/websocket_insecure/testdata/ok.js b/javascript/lang/websocket_insecure/testdata/ok.js new file mode 100644 index 000000000..ab97b7b18 --- /dev/null +++ b/javascript/lang/websocket_insecure/testdata/ok.js @@ -0,0 +1,3 @@ +new WebSocket("wss://insecure.com", {}) +new WebSocket("ws://localhost", {}) +new WebSocket("ws://127.0.0.1", {}) diff --git a/ruby/lang/deserialization_of_user_input.yml b/ruby/lang/deserialization_of_user_input.yml index 115f7cea5..d56128d66 100644 --- a/ruby/lang/deserialization_of_user_input.yml +++ b/ruby/lang/deserialization_of_user_input.yml @@ -14,7 +14,7 @@ patterns: detection: ruby_lang_deserialization_of_user_input_user_input - not: variable: USER_INPUT - detection: ruby_lang_deserialization_of_user_input_sanitized_user_input + detection: ruby_lang_deserialization_of_user_input_sanitized - pattern: | Marshal.restore($$<...>) filters: @@ -22,7 +22,7 @@ patterns: detection: ruby_lang_deserialization_of_user_input_user_input - not: variable: USER_INPUT - detection: ruby_lang_deserialization_of_user_input_sanitized_user_input + detection: ruby_lang_deserialization_of_user_input_sanitized - pattern: | Oj.object_load($$<...>)$<...> filters: @@ -30,24 +30,33 @@ patterns: detection: ruby_lang_deserialization_of_user_input_user_input - not: variable: USER_INPUT - detection: ruby_lang_deserialization_of_user_input_sanitized_user_input + detection: ruby_lang_deserialization_of_user_input_sanitized auxiliary: - - id: ruby_lang_deserialization_of_user_input_sanitized_user_input + - id: ruby_lang_deserialization_of_user_input_sanitized patterns: - - JSON.parse() - - Nokogiri::XML() - - id: ruby_lang_deserialization_of_user_input_user_input + - pattern: JSON.parse($) + filters: + - variable: SANITIZED_USER_INPUT + detection: ruby_lang_deserialization_of_user_input_user_input + - pattern: Nokogiri::XML($) + filters: + - variable: SANITIZED_USER_INPUT + detection: ruby_lang_deserialization_of_user_input_user_input + - id: ruby_lang_deserialization_of_user_input_user_input_source patterns: - | # AWS lambda def $<_>($event:, context:) end - - pattern: $ + - params + - request.$<_> + - cookies + - id: ruby_lang_deserialization_of_user_input_user_input + patterns: + - pattern: $ filters: - - variable: INNER_USER_INPUT - values: - - params - - request - - cookies + - variable: MATCHED_USER_INPUT + detection: ruby_lang_deserialization_of_user_input_user_input_source + contains: false languages: - ruby severity: high diff --git a/ruby/lang/eval_using_user_input.yml b/ruby/lang/eval_using_user_input.yml index 4edd76460..2e07ea627 100644 --- a/ruby/lang/eval_using_user_input.yml +++ b/ruby/lang/eval_using_user_input.yml @@ -30,7 +30,7 @@ auxiliary: - id: ruby_lang_eval_using_user_input_user_input patterns: - params - - request + - request.$<_> - cookies - | # AWS lambda def $<_>($event:, context:) diff --git a/ruby/lang/exec_using_user_input.yml b/ruby/lang/exec_using_user_input.yml index 8bb08e9d4..d543a5093 100644 --- a/ruby/lang/exec_using_user_input.yml +++ b/ruby/lang/exec_using_user_input.yml @@ -93,7 +93,7 @@ auxiliary: - id: ruby_lang_exec_using_user_input_user_input patterns: - params - - request + - request.$<_> - cookies - | # AWS lambda def $<_>($event:, context:) diff --git a/ruby/lang/format_string_using_user_input.yml b/ruby/lang/format_string_using_user_input.yml index e53cc0f33..c5b324951 100644 --- a/ruby/lang/format_string_using_user_input.yml +++ b/ruby/lang/format_string_using_user_input.yml @@ -30,7 +30,7 @@ auxiliary: - id: ruby_lang_format_string_using_user_input_user_input patterns: - params - - request + - request.$<_> - cookies - | # AWS lambda def $<_>($event:, context:) diff --git a/ruby/lang/ftp_using_user_input.yml b/ruby/lang/ftp_using_user_input.yml index dbea7c0fd..302d7c597 100644 --- a/ruby/lang/ftp_using_user_input.yml +++ b/ruby/lang/ftp_using_user_input.yml @@ -20,7 +20,7 @@ auxiliary: - id: ruby_lang_ftp_using_user_input_user_input patterns: - params - - request + - request.$<_> - cookies - | # AWS lambda def $<_>($event:, context:) diff --git a/ruby/lang/hardcoded_secret.yml b/ruby/lang/hardcoded_secret.yml index 995eb112d..a5e062b5e 100644 --- a/ruby/lang/hardcoded_secret.yml +++ b/ruby/lang/hardcoded_secret.yml @@ -7,6 +7,9 @@ patterns: - variable: STRING_LITERAL detection: string_literal contains: false + - not: + variable: STRING_LITERAL + string_regex: \A[*•]+\z - pattern: | $<_>($$: $) filters: @@ -15,6 +18,9 @@ patterns: - variable: STRING_LITERAL detection: string_literal contains: false + - not: + variable: STRING_LITERAL + string_regex: \A[*•]+\z - pattern: | $<_>($$ => $) filters: @@ -23,6 +29,9 @@ patterns: - variable: STRING_LITERAL detection: string_literal contains: false + - not: + variable: STRING_LITERAL + string_regex: \A[*•]+\z - pattern: | { $$: $ } filters: @@ -31,6 +40,9 @@ patterns: - variable: STRING_LITERAL detection: string_literal contains: false + - not: + variable: STRING_LITERAL + string_regex: \A[*•]+\z - pattern: | { $$ => $ } filters: @@ -39,6 +51,9 @@ patterns: - variable: STRING_LITERAL detection: string_literal contains: false + - not: + variable: STRING_LITERAL + string_regex: \A[*•]+\z languages: - ruby severity: high diff --git a/ruby/lang/hardcoded_secret/testdata/ok.rb b/ruby/lang/hardcoded_secret/testdata/ok.rb index b3018c6f3..24db41e4f 100644 --- a/ruby/lang/hardcoded_secret/testdata/ok.rb +++ b/ruby/lang/hardcoded_secret/testdata/ok.rb @@ -10,3 +10,6 @@ { "secret" => x } Passwordless.default_from_address = "some_address@bear.com" + +call(api_key: "**") +call(api_key: "•••") diff --git a/ruby/lang/http_url_using_user_input.yml b/ruby/lang/http_url_using_user_input.yml index 262e35cd1..90d2ae91a 100644 --- a/ruby/lang/http_url_using_user_input.yml +++ b/ruby/lang/http_url_using_user_input.yml @@ -88,11 +88,18 @@ patterns: detection: ruby_lang_http_url_using_user_input_net_http - variable: USER_INPUT detection: ruby_lang_http_url_using_user_input_user_input - - pattern: | - URI($) + - pattern: $.open$<...> filters: - - variable: USER_INPUT - detection: ruby_lang_http_url_using_user_input_user_input + - variable: URI + detection: ruby_lang_http_url_using_user_input_uri + - pattern: open($$<...>)$<...> + filters: + - variable: URI + detection: ruby_lang_http_url_using_user_input_uri + - pattern: Kernel.open($$<...>)$<...> + filters: + - variable: URI + detection: ruby_lang_http_url_using_user_input_uri - pattern: | Curl.http($<_>, $$<...>)$<...> filters: @@ -257,6 +264,12 @@ auxiliary: - new - newobj - start + - id: ruby_lang_http_url_using_user_input_uri + patterns: + - pattern: URI($) + filters: + - variable: USER_INPUT + detection: ruby_lang_http_url_using_user_input_user_input - id: ruby_lang_http_url_using_user_input_net_http patterns: - pattern: | @@ -301,7 +314,7 @@ auxiliary: - id: ruby_lang_http_url_using_user_input_user_input patterns: - params - - request + - request.$<_> - cookies - | # AWS lambda def $<_>($event:, context:) diff --git a/ruby/lang/http_url_using_user_input/.snapshots/unsafe_open.yml b/ruby/lang/http_url_using_user_input/.snapshots/unsafe_open.yml new file mode 100644 index 000000000..fc780dedf --- /dev/null +++ b/ruby/lang/http_url_using_user_input/.snapshots/unsafe_open.yml @@ -0,0 +1,116 @@ +high: + - rule: + cwe_ids: + - "918" + id: ruby_lang_http_url_using_user_input + title: HTTP communication with user-controlled destination detected. + description: | + ## Description + + Applications should not connect to locations formed from user input. + This rule checks for URLs containing user-supplied data. + + ## Remediations + + ❌ Avoid using user input in HTTP URLs: + + ```ruby + Faraday.get("https://#{params[:host]}') + ``` + + ✅ Use user input indirectly to form a URL: + + ```ruby + host = + case params[:host] + when "option1" + "api1.com" + when "option2" + "api2.com" + end + + Faraday.get("https://#{host}') + ``` + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_http_url_using_user_input + line_number: 3 + filename: /tmp/scan/unsafe_open.rb + parent_line_number: 3 + snippet: open(uri, "r") + fingerprint: ba5be050d651983409e1dbac19278ee1_0 + - rule: + cwe_ids: + - "918" + id: ruby_lang_http_url_using_user_input + title: HTTP communication with user-controlled destination detected. + description: | + ## Description + + Applications should not connect to locations formed from user input. + This rule checks for URLs containing user-supplied data. + + ## Remediations + + ❌ Avoid using user input in HTTP URLs: + + ```ruby + Faraday.get("https://#{params[:host]}') + ``` + + ✅ Use user input indirectly to form a URL: + + ```ruby + host = + case params[:host] + when "option1" + "api1.com" + when "option2" + "api2.com" + end + + Faraday.get("https://#{host}') + ``` + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_http_url_using_user_input + line_number: 5 + filename: /tmp/scan/unsafe_open.rb + parent_line_number: 5 + snippet: Kernel.open(uri) {} + fingerprint: ba5be050d651983409e1dbac19278ee1_1 + - rule: + cwe_ids: + - "918" + id: ruby_lang_http_url_using_user_input + title: HTTP communication with user-controlled destination detected. + description: | + ## Description + + Applications should not connect to locations formed from user input. + This rule checks for URLs containing user-supplied data. + + ## Remediations + + ❌ Avoid using user input in HTTP URLs: + + ```ruby + Faraday.get("https://#{params[:host]}') + ``` + + ✅ Use user input indirectly to form a URL: + + ```ruby + host = + case params[:host] + when "option1" + "api1.com" + when "option2" + "api2.com" + end + + Faraday.get("https://#{host}') + ``` + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_http_url_using_user_input + line_number: 7 + filename: /tmp/scan/unsafe_open.rb + parent_line_number: 7 + snippet: uri.open + fingerprint: ba5be050d651983409e1dbac19278ee1_2 + diff --git a/ruby/lang/http_url_using_user_input/testdata/ok_not_unsafe.rb b/ruby/lang/http_url_using_user_input/testdata/ok_not_unsafe.rb index 9eca88b24..77dee6645 100644 --- a/ruby/lang/http_url_using_user_input/testdata/ok_not_unsafe.rb +++ b/ruby/lang/http_url_using_user_input/testdata/ok_not_unsafe.rb @@ -57,3 +57,10 @@ Faraday.post(x) + +unused_uri = URI(params[:url]) + +uri = URI(x) +open(uri, "r") +Kernel.open(uri) {} +uri.open diff --git a/ruby/lang/http_url_using_user_input/testdata/unsafe_open.rb b/ruby/lang/http_url_using_user_input/testdata/unsafe_open.rb new file mode 100644 index 000000000..0cb8354c7 --- /dev/null +++ b/ruby/lang/http_url_using_user_input/testdata/unsafe_open.rb @@ -0,0 +1,7 @@ +uri = URI(params[:oops]) + +open(uri, "r") + +Kernel.open(uri) {} + +uri.open diff --git a/ruby/lang/path_using_user_input.yml b/ruby/lang/path_using_user_input.yml index 71221470e..be26bbfa4 100644 --- a/ruby/lang/path_using_user_input.yml +++ b/ruby/lang/path_using_user_input.yml @@ -10,10 +10,20 @@ patterns: - variable: MODULE values: - Dir - - File - Pathname - variable: USER_INPUT detection: ruby_lang_path_using_user_input_user_input + - pattern: File.$($<...>$$<...>)$<...> + filters: + - not: + variable: METHOD + values: + - basename + - extname + - fnmatch + - fnmatch? + - variable: USER_INPUT + detection: ruby_lang_path_using_user_input_user_input - pattern: | IO.$($<...>$$<...>) filters: @@ -33,11 +43,17 @@ patterns: filters: - variable: USER_INPUT detection: ruby_lang_path_using_user_input_user_input + - not: + variable: USER_INPUT + detection: ruby_lang_path_using_user_input_open_sanitized - pattern: | open($$<...>)$<...> filters: - variable: USER_INPUT detection: ruby_lang_path_using_user_input_user_input + - not: + variable: USER_INPUT + detection: ruby_lang_path_using_user_input_open_sanitized - pattern: | PStore.new($$<...>) filters: @@ -152,6 +168,14 @@ patterns: - template - variable: USER_INPUT detection: ruby_lang_path_using_user_input_user_input + - pattern: render($$<...>) + filters: + - variable: USER_INPUT + detection: ruby_lang_path_using_user_input_user_input + - not: + variable: USER_INPUT + detection: ruby_lang_path_using_user_input_render_first_arg_pair + contains: false - pattern: | $({ $: $ }) filters: @@ -173,14 +197,27 @@ patterns: - variable: USER_INPUT detection: ruby_lang_path_using_user_input_user_input auxiliary: - - id: ruby_lang_path_using_user_input_user_input + - id: ruby_lang_path_using_user_input_user_input_source patterns: - params - - request + - request.$<_> - cookies - | # AWS lambda def $<_>($event:, context:) end + - id: ruby_lang_path_using_user_input_user_input + patterns: + - pattern: $ + filters: + - variable: MATCHED_USER_INPUT + detection: ruby_lang_path_using_user_input_user_input_source + contains: false + - id: ruby_lang_path_using_user_input_open_sanitized + patterns: + - pattern: URI($) + filters: + - variable: SANITIZED_USER_INPUT + detection: ruby_lang_path_using_user_input_user_input - id: ruby_lang_path_using_user_input_pathname patterns: - Rails.root @@ -216,6 +253,10 @@ auxiliary: - unlink - variable: USER_INPUT detection: ruby_lang_path_using_user_input_user_input + - id: ruby_lang_path_using_user_input_render_first_arg_pair + patterns: + - pattern: | + render($$<_>: $<_>) languages: - ruby severity: high diff --git a/ruby/lang/path_using_user_input/.snapshots/unsafe_rails.yml b/ruby/lang/path_using_user_input/.snapshots/unsafe_rails.yml index 1b93a9ebe..c2aa87451 100644 --- a/ruby/lang/path_using_user_input/.snapshots/unsafe_rails.yml +++ b/ruby/lang/path_using_user_input/.snapshots/unsafe_rails.yml @@ -96,7 +96,7 @@ high: line_number: 4 filename: /tmp/scan/unsafe_rails.rb parent_line_number: 4 - snippet: 'render_to_string({ file: "/templates/#{params[:oops]}" })' + snippet: render("something/#{params[:oops]}") fingerprint: 99da8d49acffa4d91e80acc91cd1a5c3_2 - rule: cwe_ids: @@ -126,9 +126,42 @@ high: ## Resources - [OWASP path traversal attack](https://owasp.org/www-community/attacks/Path_Traversal) documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_path_using_user_input - line_number: 6 + line_number: 5 filename: /tmp/scan/unsafe_rails.rb - parent_line_number: 6 - snippet: 'send_file params[:oops], type: "text/html"' + parent_line_number: 5 + snippet: 'render_to_string({ file: "/templates/#{params[:oops]}" })' fingerprint: 99da8d49acffa4d91e80acc91cd1a5c3_3 + - rule: + cwe_ids: + - "22" + - "73" + id: ruby_lang_path_using_user_input + title: Do not use user input to form file paths. + description: | + ## Description + Using raw unsanitized input when forming filenames or file paths is bad practice. + It can lead to path manipulation, by which attackers can gain access to resources outside of the intended scope. + + ## Remediations + ❌ Avoid wherever possible + + ✅ Validate expected file paths using `File` methods + + ```ruby + path = File.expand("/home/" + params[:resource_name]) + if path.starts_with?("/home/") + Dir.chdir(path) + else + # path is unexpected + end + ``` + + ## Resources + - [OWASP path traversal attack](https://owasp.org/www-community/attacks/Path_Traversal) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_path_using_user_input + line_number: 7 + filename: /tmp/scan/unsafe_rails.rb + parent_line_number: 7 + snippet: 'send_file params[:oops], type: "text/html"' + fingerprint: 99da8d49acffa4d91e80acc91cd1a5c3_4 diff --git a/ruby/lang/path_using_user_input/testdata/ok_not_unsafe.rb b/ruby/lang/path_using_user_input/testdata/ok_not_unsafe.rb index db8bede85..2b396364e 100644 --- a/ruby/lang/path_using_user_input/testdata/ok_not_unsafe.rb +++ b/ruby/lang/path_using_user_input/testdata/ok_not_unsafe.rb @@ -4,6 +4,7 @@ event = not_from_handler File.exist?(event["oops"]) +File.basename(params[:filename]) IO.readlines("/home/#{x}") @@ -45,6 +46,9 @@ render(partial: x, locals: { z: params[:ok] }) +render(foo: "something/#{params[:oops]}") render_to_string({ file: "/templates/#{x}", locals: { z: params[:ok] } }) send_file x, type: "text/html" + +open(URI(params[:oops])) diff --git a/ruby/lang/path_using_user_input/testdata/unsafe_rails.rb b/ruby/lang/path_using_user_input/testdata/unsafe_rails.rb index cf0256aad..a9523cf5c 100644 --- a/ruby/lang/path_using_user_input/testdata/unsafe_rails.rb +++ b/ruby/lang/path_using_user_input/testdata/unsafe_rails.rb @@ -1,6 +1,7 @@ Rails.root.join(params[:oops]) render(partial: params[:oops]) +render("something/#{params[:oops]}") render_to_string({ file: "/templates/#{params[:oops]}" }) send_file params[:oops], type: "text/html" diff --git a/ruby/lang/raw_html_using_user_input.yml b/ruby/lang/raw_html_using_user_input.yml new file mode 100644 index 000000000..233e6c73b --- /dev/null +++ b/ruby/lang/raw_html_using_user_input.yml @@ -0,0 +1,90 @@ +languages: + - ruby +patterns: + - pattern: $ + filters: + - variable: STRING + string_regex: <\w+(\s[^>]*)?> + - variable: STRING + detection: ruby_lang_raw_html_using_user_input_user_input + - not: + variable: STRING + detection: ruby_lang_raw_html_using_user_input_sanitized + - pattern: $.html_safe + filters: + - variable: STRING + detection: ruby_lang_raw_html_using_user_input_user_input + - not: + variable: STRING + detection: ruby_lang_raw_html_using_user_input_sanitized + - pattern: raw($) + filters: + - variable: STRING + detection: ruby_lang_raw_html_using_user_input_user_input + - not: + variable: STRING + detection: ruby_lang_raw_html_using_user_input_sanitized + - pattern: ERB.new($$<...>) + filters: + - variable: STRING + detection: ruby_lang_raw_html_using_user_input_user_input + - not: + variable: STRING + detection: ruby_lang_raw_html_using_user_input_sanitized +auxiliary: + - id: ruby_lang_raw_html_using_user_input_user_input_source + patterns: + - params + - request.$<_> + - cookies + - | # AWS lambda + def $<_>($event:, context:) + end + - id: ruby_lang_raw_html_using_user_input_user_input + patterns: + - pattern: $ + filters: + - variable: USER_INPUT + detection: ruby_lang_raw_html_using_user_input_user_input_source + contains: false + - id: ruby_lang_raw_html_using_user_input_sanitized + patterns: + - pattern: $($) + filters: + - variable: METHOD + values: + - sanitize + - strip_tags + - variable: SANITIZED_USER_INPUT + detection: ruby_lang_raw_html_using_user_input_user_input +severity: high +metadata: + description: "Unsanitized user input detected in raw HTML string." + remediation_message: | + ## Description + + Applications should not include unsanitized user input in HTML. This + can allow cross-site scripting (XSS) attacks. + + ## Remediations + + ❌ Avoid including user input directly in HTML strings: + + ```ruby + html = "

#{params[:title]}

" + ``` + + ✅ Use a templating language such as ERB, and place the template in a separate file. + + ✅ When HTML strings must be used, sanitize user input: + + ```ruby + html = "

#{strip_tags(params[:title])}

" + ``` + + ## Resources + - [OWASP Cross-Site Scripting (XSS) Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) + cwe_id: + - 79 + id: ruby_lang_raw_html_using_user_input + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_raw_html_using_user_input diff --git a/ruby/lang/raw_html_using_user_input/.snapshots/bad.yml b/ruby/lang/raw_html_using_user_input/.snapshots/bad.yml new file mode 100644 index 000000000..c44c495f4 --- /dev/null +++ b/ruby/lang/raw_html_using_user_input/.snapshots/bad.yml @@ -0,0 +1,212 @@ +high: + - rule: + cwe_ids: + - "79" + id: ruby_lang_raw_html_using_user_input + title: Unsanitized user input detected in raw HTML string. + description: | + ## Description + + Applications should not include unsanitized user input in HTML. This + can allow cross-site scripting (XSS) attacks. + + ## Remediations + + ❌ Avoid including user input directly in HTML strings: + + ```ruby + html = "

#{params[:title]}

" + ``` + + ✅ Use a templating language such as ERB, and place the template in a separate file. + + ✅ When HTML strings must be used, sanitize user input: + + ```ruby + html = "

#{strip_tags(params[:title])}

" + ``` + + ## Resources + - [OWASP Cross-Site Scripting (XSS) Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_raw_html_using_user_input + line_number: 1 + filename: /tmp/scan/bad.rb + parent_line_number: 1 + snippet: '"

#{params[:oops]}

"' + fingerprint: e656ad1fa2ca8c10a0a51f19d4d6a852_0 + - rule: + cwe_ids: + - "79" + id: ruby_lang_raw_html_using_user_input + title: Unsanitized user input detected in raw HTML string. + description: | + ## Description + + Applications should not include unsanitized user input in HTML. This + can allow cross-site scripting (XSS) attacks. + + ## Remediations + + ❌ Avoid including user input directly in HTML strings: + + ```ruby + html = "

#{params[:title]}

" + ``` + + ✅ Use a templating language such as ERB, and place the template in a separate file. + + ✅ When HTML strings must be used, sanitize user input: + + ```ruby + html = "

#{strip_tags(params[:title])}

" + ``` + + ## Resources + - [OWASP Cross-Site Scripting (XSS) Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_raw_html_using_user_input + line_number: 2 + filename: /tmp/scan/bad.rb + parent_line_number: 2 + snippet: '"

#{params[:oops]}

"' + fingerprint: e656ad1fa2ca8c10a0a51f19d4d6a852_1 + - rule: + cwe_ids: + - "79" + id: ruby_lang_raw_html_using_user_input + title: Unsanitized user input detected in raw HTML string. + description: | + ## Description + + Applications should not include unsanitized user input in HTML. This + can allow cross-site scripting (XSS) attacks. + + ## Remediations + + ❌ Avoid including user input directly in HTML strings: + + ```ruby + html = "

#{params[:title]}

" + ``` + + ✅ Use a templating language such as ERB, and place the template in a separate file. + + ✅ When HTML strings must be used, sanitize user input: + + ```ruby + html = "

#{strip_tags(params[:title])}

" + ``` + + ## Resources + - [OWASP Cross-Site Scripting (XSS) Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_raw_html_using_user_input + line_number: 4 + filename: /tmp/scan/bad.rb + parent_line_number: 4 + snippet: 'raw("Hello: #{params[:oops]}")' + fingerprint: e656ad1fa2ca8c10a0a51f19d4d6a852_2 + - rule: + cwe_ids: + - "79" + id: ruby_lang_raw_html_using_user_input + title: Unsanitized user input detected in raw HTML string. + description: | + ## Description + + Applications should not include unsanitized user input in HTML. This + can allow cross-site scripting (XSS) attacks. + + ## Remediations + + ❌ Avoid including user input directly in HTML strings: + + ```ruby + html = "

#{params[:title]}

" + ``` + + ✅ Use a templating language such as ERB, and place the template in a separate file. + + ✅ When HTML strings must be used, sanitize user input: + + ```ruby + html = "

#{strip_tags(params[:title])}

" + ``` + + ## Resources + - [OWASP Cross-Site Scripting (XSS) Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_raw_html_using_user_input + line_number: 5 + filename: /tmp/scan/bad.rb + parent_line_number: 5 + snippet: '"

#{params[:oops]}

"' + fingerprint: e656ad1fa2ca8c10a0a51f19d4d6a852_3 + - rule: + cwe_ids: + - "79" + id: ruby_lang_raw_html_using_user_input + title: Unsanitized user input detected in raw HTML string. + description: | + ## Description + + Applications should not include unsanitized user input in HTML. This + can allow cross-site scripting (XSS) attacks. + + ## Remediations + + ❌ Avoid including user input directly in HTML strings: + + ```ruby + html = "

#{params[:title]}

" + ``` + + ✅ Use a templating language such as ERB, and place the template in a separate file. + + ✅ When HTML strings must be used, sanitize user input: + + ```ruby + html = "

#{strip_tags(params[:title])}

" + ``` + + ## Resources + - [OWASP Cross-Site Scripting (XSS) Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_raw_html_using_user_input + line_number: 7 + filename: /tmp/scan/bad.rb + parent_line_number: 7 + snippet: '"Hello: #{params[:oops]}".html_safe' + fingerprint: e656ad1fa2ca8c10a0a51f19d4d6a852_5 + - rule: + cwe_ids: + - "79" + id: ruby_lang_raw_html_using_user_input + title: Unsanitized user input detected in raw HTML string. + description: | + ## Description + + Applications should not include unsanitized user input in HTML. This + can allow cross-site scripting (XSS) attacks. + + ## Remediations + + ❌ Avoid including user input directly in HTML strings: + + ```ruby + html = "

#{params[:title]}

" + ``` + + ✅ Use a templating language such as ERB, and place the template in a separate file. + + ✅ When HTML strings must be used, sanitize user input: + + ```ruby + html = "

#{strip_tags(params[:title])}

" + ``` + + ## Resources + - [OWASP Cross-Site Scripting (XSS) Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_raw_html_using_user_input + line_number: 9 + filename: /tmp/scan/bad.rb + parent_line_number: 9 + snippet: 'ERB.new("Test: " + params[:oops])' + fingerprint: e656ad1fa2ca8c10a0a51f19d4d6a852_6 + diff --git a/ruby/lang/raw_html_using_user_input/.snapshots/ok.yml b/ruby/lang/raw_html_using_user_input/.snapshots/ok.yml new file mode 100644 index 000000000..311847daa --- /dev/null +++ b/ruby/lang/raw_html_using_user_input/.snapshots/ok.yml @@ -0,0 +1,2 @@ +{} + diff --git a/ruby/lang/raw_html_using_user_input/testdata/bad.rb b/ruby/lang/raw_html_using_user_input/testdata/bad.rb new file mode 100644 index 000000000..4e9aaba50 --- /dev/null +++ b/ruby/lang/raw_html_using_user_input/testdata/bad.rb @@ -0,0 +1,9 @@ +"

#{params[:oops]}

" +"

#{params[:oops]}

" + +raw("Hello: #{params[:oops]}") +raw("

#{params[:oops]}

") + +"Hello: #{params[:oops]}".html_safe + +ERB.new("Test: " + params[:oops]) diff --git a/ruby/lang/raw_html_using_user_input/testdata/ok.rb b/ruby/lang/raw_html_using_user_input/testdata/ok.rb new file mode 100644 index 000000000..0a69d31a9 --- /dev/null +++ b/ruby/lang/raw_html_using_user_input/testdata/ok.rb @@ -0,0 +1,7 @@ +"

#{strip_tags(params[:ok])}

" + +raw("Hello: #{sanitize(params[:ok])}") + +"Hello: #{sanitize(params[:ok])}".html_safe + +ERB.new("Test: " + strip_tags(params[:ok])) diff --git a/ruby/lang/reflection_using_user_input.yml b/ruby/lang/reflection_using_user_input.yml index 32031318f..e3c756c34 100644 --- a/ruby/lang/reflection_using_user_input.yml +++ b/ruby/lang/reflection_using_user_input.yml @@ -82,7 +82,7 @@ auxiliary: - id: ruby_lang_reflection_using_user_input_user_input patterns: - params - - request + - request.$<_> - cookies - | # AWS lambda def $<_>($event:, context:) diff --git a/ruby/lang/regex_using_user_input.yml b/ruby/lang/regex_using_user_input.yml index 6a0404dfb..245711dde 100644 --- a/ruby/lang/regex_using_user_input.yml +++ b/ruby/lang/regex_using_user_input.yml @@ -22,7 +22,7 @@ auxiliary: - id: ruby_lang_regex_using_user_input_user_input patterns: - params - - request + - request.$<_> - cookies - | # AWS lambda def $<_>($event:, context:) diff --git a/ruby/lang/weak_encryption.yml b/ruby/lang/weak_encryption.yml index 569ed5c14..c3b9b9320 100644 --- a/ruby/lang/weak_encryption.yml +++ b/ruby/lang/weak_encryption.yml @@ -69,6 +69,8 @@ patterns: detection: datatype - pattern: $.$($<_>) filters: + - variable: DIGEST + detection: ruby_lang_weak_encryption_digest - variable: METHOD values: - digest @@ -76,6 +78,8 @@ patterns: - base64digest - pattern: $.$($) filters: + - variable: DIGEST + detection: ruby_lang_weak_encryption_digest - variable: METHOD values: - digest diff --git a/ruby/lang/websocket_insecure.yml b/ruby/lang/websocket_insecure.yml new file mode 100644 index 000000000..5a2408ebc --- /dev/null +++ b/ruby/lang/websocket_insecure.yml @@ -0,0 +1,79 @@ +languages: + - ruby +patterns: + - pattern: Kontena::Websocket::Client.$($$<...>)$<...> + filters: + - variable: METHOD + values: + - new + - connect + - variable: INSECURE_URL + detection: ruby_lang_websocket_insecure_url + contains: false + - pattern: WebSocket::Client::Simple.connect($$<...>)$<...> + filters: + - variable: INSECURE_URL + detection: ruby_lang_websocket_insecure_url + contains: false + - pattern: $.connect($$<...>) + filters: + - variable: SIMPLE_CLIENT + detection: ruby_lang_websocket_insecure_simple_client + - variable: INSECURE_URL + detection: ruby_lang_websocket_insecure_url + contains: false + - pattern: Faye::WebSocket::Client.new($$<...>) + filters: + - variable: INSECURE_URL + detection: ruby_lang_websocket_insecure_url + contains: false + - pattern: | + WebSocket::Handshake::Client.new(url: $) + filters: + - variable: INSECURE_URL + detection: ruby_lang_websocket_insecure_url + contains: false + - pattern: | + WebSocket::Handshake::Client.new(secure: false) +auxiliary: + - id: ruby_lang_websocket_insecure_url + patterns: + - pattern: $ + filters: + - variable: URL + string_regex: '\A(ws|http):' + - not: + variable: URL + string_regex: '\A(ws|http)://(localhost|127\.0\.0\.1)' + - id: ruby_lang_websocket_insecure_simple_client + patterns: + - WebSocket::Client::Simple::Client.new +severity: low +metadata: + description: "Insecure websocket communication detected." + remediation_message: | + ## Description + + Applications should only connect to APIs using SSL connections. This rule + checks that all websocket connections use SSL. + + ## Remediations + + ❌ Avoid using unsecured outgoing websocket communication: + + ```ruby + client = Faye::WebSocket::Client.new('ws://insecure-api.com') + ``` + + ✅ Always connect using SSL: + + ```ruby + client = Faye::WebSocket::Client.new('wss://secure-api.com') + ``` + + ## Resources + - [OWASP insecure transport](https://owasp.org/www-community/vulnerabilities/Insecure_Transport) + cwe_id: + - 319 + id: ruby_lang_websocket_insecure + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_websocket_insecure diff --git a/ruby/lang/websocket_insecure/.snapshots/bad.yml b/ruby/lang/websocket_insecure/.snapshots/bad.yml new file mode 100644 index 000000000..dd4c1baef --- /dev/null +++ b/ruby/lang/websocket_insecure/.snapshots/bad.yml @@ -0,0 +1,200 @@ +low: + - rule: + cwe_ids: + - "319" + id: ruby_lang_websocket_insecure + title: Insecure websocket communication detected. + description: | + ## Description + + Applications should only connect to APIs using SSL connections. This rule + checks that all websocket connections use SSL. + + ## Remediations + + ❌ Avoid using unsecured outgoing websocket communication: + + ```ruby + client = Faye::WebSocket::Client.new('ws://insecure-api.com') + ``` + + ✅ Always connect using SSL: + + ```ruby + client = Faye::WebSocket::Client.new('wss://secure-api.com') + ``` + + ## Resources + - [OWASP insecure transport](https://owasp.org/www-community/vulnerabilities/Insecure_Transport) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_websocket_insecure + line_number: 1 + filename: /tmp/scan/bad.rb + parent_line_number: 1 + snippet: 'Kontena::Websocket::Client.connect("ws://insecure.com", open_timeout: 10) {}' + fingerprint: e565fd801fa463b5da999403fd9c7f4c_0 + - rule: + cwe_ids: + - "319" + id: ruby_lang_websocket_insecure + title: Insecure websocket communication detected. + description: | + ## Description + + Applications should only connect to APIs using SSL connections. This rule + checks that all websocket connections use SSL. + + ## Remediations + + ❌ Avoid using unsecured outgoing websocket communication: + + ```ruby + client = Faye::WebSocket::Client.new('ws://insecure-api.com') + ``` + + ✅ Always connect using SSL: + + ```ruby + client = Faye::WebSocket::Client.new('wss://secure-api.com') + ``` + + ## Resources + - [OWASP insecure transport](https://owasp.org/www-community/vulnerabilities/Insecure_Transport) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_websocket_insecure + line_number: 3 + filename: /tmp/scan/bad.rb + parent_line_number: 3 + snippet: WebSocket::Client::Simple.connect("ws://insecure.com", {}) {} + fingerprint: e565fd801fa463b5da999403fd9c7f4c_1 + - rule: + cwe_ids: + - "319" + id: ruby_lang_websocket_insecure + title: Insecure websocket communication detected. + description: | + ## Description + + Applications should only connect to APIs using SSL connections. This rule + checks that all websocket connections use SSL. + + ## Remediations + + ❌ Avoid using unsecured outgoing websocket communication: + + ```ruby + client = Faye::WebSocket::Client.new('ws://insecure-api.com') + ``` + + ✅ Always connect using SSL: + + ```ruby + client = Faye::WebSocket::Client.new('wss://secure-api.com') + ``` + + ## Resources + - [OWASP insecure transport](https://owasp.org/www-community/vulnerabilities/Insecure_Transport) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_websocket_insecure + line_number: 4 + filename: /tmp/scan/bad.rb + parent_line_number: 4 + snippet: WebSocket::Client::Simple::Client.new.connect("ws://insecure.com", {}) + fingerprint: e565fd801fa463b5da999403fd9c7f4c_2 + - rule: + cwe_ids: + - "319" + id: ruby_lang_websocket_insecure + title: Insecure websocket communication detected. + description: | + ## Description + + Applications should only connect to APIs using SSL connections. This rule + checks that all websocket connections use SSL. + + ## Remediations + + ❌ Avoid using unsecured outgoing websocket communication: + + ```ruby + client = Faye::WebSocket::Client.new('ws://insecure-api.com') + ``` + + ✅ Always connect using SSL: + + ```ruby + client = Faye::WebSocket::Client.new('wss://secure-api.com') + ``` + + ## Resources + - [OWASP insecure transport](https://owasp.org/www-community/vulnerabilities/Insecure_Transport) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_websocket_insecure + line_number: 6 + filename: /tmp/scan/bad.rb + parent_line_number: 6 + snippet: Faye::WebSocket::Client.new("ws://insecure.com", nil) + fingerprint: e565fd801fa463b5da999403fd9c7f4c_3 + - rule: + cwe_ids: + - "319" + id: ruby_lang_websocket_insecure + title: Insecure websocket communication detected. + description: | + ## Description + + Applications should only connect to APIs using SSL connections. This rule + checks that all websocket connections use SSL. + + ## Remediations + + ❌ Avoid using unsecured outgoing websocket communication: + + ```ruby + client = Faye::WebSocket::Client.new('ws://insecure-api.com') + ``` + + ✅ Always connect using SSL: + + ```ruby + client = Faye::WebSocket::Client.new('wss://secure-api.com') + ``` + + ## Resources + - [OWASP insecure transport](https://owasp.org/www-community/vulnerabilities/Insecure_Transport) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_websocket_insecure + line_number: 8 + filename: /tmp/scan/bad.rb + parent_line_number: 8 + snippet: 'WebSocket::Handshake::Client.new(url: "ws://insecure.com")' + fingerprint: e565fd801fa463b5da999403fd9c7f4c_4 + - rule: + cwe_ids: + - "319" + id: ruby_lang_websocket_insecure + title: Insecure websocket communication detected. + description: | + ## Description + + Applications should only connect to APIs using SSL connections. This rule + checks that all websocket connections use SSL. + + ## Remediations + + ❌ Avoid using unsecured outgoing websocket communication: + + ```ruby + client = Faye::WebSocket::Client.new('ws://insecure-api.com') + ``` + + ✅ Always connect using SSL: + + ```ruby + client = Faye::WebSocket::Client.new('wss://secure-api.com') + ``` + + ## Resources + - [OWASP insecure transport](https://owasp.org/www-community/vulnerabilities/Insecure_Transport) + documentation_url: https://docs.bearer.com/reference/rules/ruby_lang_websocket_insecure + line_number: 9 + filename: /tmp/scan/bad.rb + parent_line_number: 9 + snippet: 'WebSocket::Handshake::Client.new(secure: false, host: "insecure.com")' + fingerprint: e565fd801fa463b5da999403fd9c7f4c_5 + diff --git a/ruby/lang/websocket_insecure/.snapshots/ok.yml b/ruby/lang/websocket_insecure/.snapshots/ok.yml new file mode 100644 index 000000000..311847daa --- /dev/null +++ b/ruby/lang/websocket_insecure/.snapshots/ok.yml @@ -0,0 +1,2 @@ +{} + diff --git a/ruby/lang/websocket_insecure/testdata/bad.rb b/ruby/lang/websocket_insecure/testdata/bad.rb new file mode 100644 index 000000000..35f9f86f7 --- /dev/null +++ b/ruby/lang/websocket_insecure/testdata/bad.rb @@ -0,0 +1,9 @@ +Kontena::Websocket::Client.connect("ws://insecure.com", open_timeout: 10) {} + +WebSocket::Client::Simple.connect("ws://insecure.com", {}) {} +WebSocket::Client::Simple::Client.new.connect("ws://insecure.com", {}) + +Faye::WebSocket::Client.new("ws://insecure.com", nil) + +WebSocket::Handshake::Client.new(url: "ws://insecure.com") +WebSocket::Handshake::Client.new(secure: false, host: "insecure.com") diff --git a/ruby/lang/websocket_insecure/testdata/ok.rb b/ruby/lang/websocket_insecure/testdata/ok.rb new file mode 100644 index 000000000..aded2a8a2 --- /dev/null +++ b/ruby/lang/websocket_insecure/testdata/ok.rb @@ -0,0 +1,12 @@ +Kontena::Websocket::Client.connect("wss://secure.com", open_timeout: 10) {} + +WebSocket::Client::Simple.connect("wss://secure.com", {}) {} +WebSocket::Client::Simple.new.connect("wss://secure.com", {}) + +Faye::WebSocket::Client.new("wss://secure.com", nil) + +WebSocket::Handshake::Client.new(url: "wss://secure.com") +WebSocket::Handshake::Client.new(secure: true, host: "secure.com") + +Faye::WebSocket::Client.new("ws://localhost", nil) +Faye::WebSocket::Client.new("ws://127.0.0.1", nil) diff --git a/ruby/rails/detailed_exceptions.yml b/ruby/rails/detailed_exceptions.yml new file mode 100644 index 000000000..2bbe3c11f --- /dev/null +++ b/ruby/rails/detailed_exceptions.yml @@ -0,0 +1,63 @@ +patterns: + - pattern: config.consider_all_requests_local = true + filters: + - not: + filename_regex: (\A|/)(development|test)\.rb\z + - pattern: | + class $ < $<_> + $$ + end + filters: + - variable: NAME + regex: Controller\z + - variable: METHOD + detection: show_detailed_exceptions_method + contains: false + - not: + variable: METHOD + detection: show_detailed_exceptions_method_false + contains: false +auxiliary: + - id: show_detailed_exceptions_method + patterns: + - | + def show_detailed_exceptions? + end + - id: show_detailed_exceptions_method_false + patterns: + - | + def show_detailed_exceptions? + false + end +languages: + - ruby +severity: low +metadata: + description: "Detailed error reporting detected." + remediation_message: | + ## Description + + Returning detailed error messages to users could reveal sensitive + information. This could lead to + + ## Remediations + + ❌ Don't configure your application to return details for every error: + + ```ruby + config.consider_all_requests_local = false + ``` + + ❌ Don't use `show_detailed_exceptions?` in controllers: + + ```ruby + class MyController < ApplicationController + def show_detailed_exceptions? + ... + end + end + ``` + cwe_id: + - 209 + id: ruby_rails_detailed_exceptions + documentation_url: https://docs.bearer.com/reference/rules/ruby_rails_detailed_exceptions diff --git a/ruby/rails/detailed_exceptions/.snapshots/bad.yml b/ruby/rails/detailed_exceptions/.snapshots/bad.yml new file mode 100644 index 000000000..f7e1e8352 --- /dev/null +++ b/ruby/rails/detailed_exceptions/.snapshots/bad.yml @@ -0,0 +1,73 @@ +low: + - rule: + cwe_ids: + - "209" + id: ruby_rails_detailed_exceptions + title: Detailed error reporting detected. + description: | + ## Description + + Returning detailed error messages to users could reveal sensitive + information. This could lead to + + ## Remediations + + ❌ Don't configure your application to return details for every error: + + ```ruby + config.consider_all_requests_local = false + ``` + + ❌ Don't use `show_detailed_exceptions?` in controllers: + + ```ruby + class MyController < ApplicationController + def show_detailed_exceptions? + ... + end + end + ``` + documentation_url: https://docs.bearer.com/reference/rules/ruby_rails_detailed_exceptions + line_number: 2 + filename: /tmp/scan/bad.rb + parent_line_number: 2 + snippet: |- + def show_detailed_exceptions? + foo + end + fingerprint: b45c0dfd18b39fa46005c703eef96ee5_0 + - rule: + cwe_ids: + - "209" + id: ruby_rails_detailed_exceptions + title: Detailed error reporting detected. + description: | + ## Description + + Returning detailed error messages to users could reveal sensitive + information. This could lead to + + ## Remediations + + ❌ Don't configure your application to return details for every error: + + ```ruby + config.consider_all_requests_local = false + ``` + + ❌ Don't use `show_detailed_exceptions?` in controllers: + + ```ruby + class MyController < ApplicationController + def show_detailed_exceptions? + ... + end + end + ``` + documentation_url: https://docs.bearer.com/reference/rules/ruby_rails_detailed_exceptions + line_number: 7 + filename: /tmp/scan/bad.rb + parent_line_number: 7 + snippet: config.consider_all_requests_local = true + fingerprint: b45c0dfd18b39fa46005c703eef96ee5_1 + diff --git a/ruby/rails/detailed_exceptions/.snapshots/development.yml b/ruby/rails/detailed_exceptions/.snapshots/development.yml new file mode 100644 index 000000000..311847daa --- /dev/null +++ b/ruby/rails/detailed_exceptions/.snapshots/development.yml @@ -0,0 +1,2 @@ +{} + diff --git a/ruby/rails/detailed_exceptions/.snapshots/ok.yml b/ruby/rails/detailed_exceptions/.snapshots/ok.yml new file mode 100644 index 000000000..311847daa --- /dev/null +++ b/ruby/rails/detailed_exceptions/.snapshots/ok.yml @@ -0,0 +1,2 @@ +{} + diff --git a/ruby/rails/detailed_exceptions/testdata/bad.rb b/ruby/rails/detailed_exceptions/testdata/bad.rb new file mode 100644 index 000000000..7bcb80398 --- /dev/null +++ b/ruby/rails/detailed_exceptions/testdata/bad.rb @@ -0,0 +1,7 @@ +class MyController < ApplicationController + def show_detailed_exceptions? + foo + end +end + +config.consider_all_requests_local = true diff --git a/ruby/rails/detailed_exceptions/testdata/development.rb b/ruby/rails/detailed_exceptions/testdata/development.rb new file mode 100644 index 000000000..ddc1ab7f3 --- /dev/null +++ b/ruby/rails/detailed_exceptions/testdata/development.rb @@ -0,0 +1 @@ +config.consider_all_requests_local = true diff --git a/ruby/rails/detailed_exceptions/testdata/ok.rb b/ruby/rails/detailed_exceptions/testdata/ok.rb new file mode 100644 index 000000000..77d30ea4e --- /dev/null +++ b/ruby/rails/detailed_exceptions/testdata/ok.rb @@ -0,0 +1,13 @@ +class MyController < ApplicationController + def show_detailed_exceptions? + false + end +end + +class OtherClass < Base + def show_detailed_exceptions? + foo + end +end + +config.consider_all_requests_local = false diff --git a/ruby/rails/open_redirect.yml b/ruby/rails/open_redirect.yml index 8fe9b07bb..81f47c74c 100644 --- a/ruby/rails/open_redirect.yml +++ b/ruby/rails/open_redirect.yml @@ -1,16 +1,34 @@ patterns: - pattern: | - redirect_to($) + redirect_to($$<...>) filters: - variable: USER_INPUT - detection: ruby_rails_redirect_to_user_input + detection: ruby_rails_open_redirect_user_input + - not: + variable: USER_INPUT + detection: ruby_rails_open_redirect_sanitized languages: - ruby auxiliary: - - id: ruby_rails_redirect_to_user_input + - id: ruby_rails_open_redirect_sanitized + patterns: + - pattern: $($<...>$$<...>) + filters: + - variable: METHOD + regex: _(path|url)\z + - variable: SANITIZED_USER_INPUT + detection: ruby_rails_open_redirect_user_input + - id: ruby_rails_open_redirect_user_input + patterns: + - pattern: $ + filters: + - variable: MATCHED_USER_INPUT + detection: ruby_rails_open_redirect_user_input_source + contains: false + - id: ruby_rails_open_redirect_user_input_source patterns: - params - - request + - request.$<_> - cookies severity: medium metadata: diff --git a/ruby/rails/open_redirect/.snapshots/ok.yml b/ruby/rails/open_redirect/.snapshots/ok.yml new file mode 100644 index 000000000..311847daa --- /dev/null +++ b/ruby/rails/open_redirect/.snapshots/ok.yml @@ -0,0 +1,2 @@ +{} + diff --git a/ruby/rails/open_redirect/.snapshots/unsecure.yml b/ruby/rails/open_redirect/.snapshots/unsecure.yml index 8232da424..6e74e309d 100644 --- a/ruby/rails/open_redirect/.snapshots/unsecure.yml +++ b/ruby/rails/open_redirect/.snapshots/unsecure.yml @@ -11,6 +11,6 @@ medium: line_number: 3 filename: /tmp/scan/unsecure.rb parent_line_number: 3 - snippet: redirect_to(params[:id]) + snippet: 'redirect_to params[:id], status: :found' fingerprint: fdabf776dc4a54cdf78559feba91e138_0 diff --git a/ruby/rails/open_redirect/testdata/ok.rb b/ruby/rails/open_redirect/testdata/ok.rb new file mode 100644 index 000000000..24931e13c --- /dev/null +++ b/ruby/rails/open_redirect/testdata/ok.rb @@ -0,0 +1,5 @@ +class OrdersController < ApplicationController + def notify + redirect_to some_route_url(params[:id]), status: :found + end +end diff --git a/ruby/rails/open_redirect/testdata/unsecure.rb b/ruby/rails/open_redirect/testdata/unsecure.rb index aa052f12d..c2d153e3d 100644 --- a/ruby/rails/open_redirect/testdata/unsecure.rb +++ b/ruby/rails/open_redirect/testdata/unsecure.rb @@ -1,5 +1,5 @@ class OrdersController < ApplicationController - def notify - redirect_to(params[:id]) - end + def notify + redirect_to params[:id], status: :found end +end diff --git a/ruby/rails/permissive_parameters.yml b/ruby/rails/permissive_parameters.yml index 8861617fc..4cc577e1a 100644 --- a/ruby/rails/permissive_parameters.yml +++ b/ruby/rails/permissive_parameters.yml @@ -1,8 +1,16 @@ languages: - ruby patterns: - - pattern: $<_>.permit! + - pattern: $.permit! + filters: + - not: + variable: PARAMS + detection: ruby_rails_permissive_parameters_sliced - pattern: ActionController::Parameters.permit_all_parameters = true +auxiliary: + - id: ruby_rails_permissive_parameters_sliced + patterns: + - $<_>.slice() severity: medium metadata: description: "Overly permissive request parameters detected." diff --git a/ruby/rails/permissive_parameters/.snapshots/bad.yml b/ruby/rails/permissive_parameters/.snapshots/bad.yml index b0fb53858..f21a827e2 100644 --- a/ruby/rails/permissive_parameters/.snapshots/bad.yml +++ b/ruby/rails/permissive_parameters/.snapshots/bad.yml @@ -54,9 +54,39 @@ medium: params.permit(:name, :email) ``` documentation_url: https://docs.bearer.com/reference/rules/ruby_rails_permissive_parameters - line_number: 3 + line_number: 2 filename: /tmp/scan/bad.rb - parent_line_number: 3 - snippet: ActionController::Parameters.permit_all_parameters = true + parent_line_number: 2 + snippet: params.merge(other).permit! fingerprint: 4ada1717774c2aa01998e9b04b82f795_1 + - rule: + cwe_ids: + - "915" + id: ruby_rails_permissive_parameters + title: Overly permissive request parameters detected. + description: | + ## Description + + Being overly permissive with request parameters can allow an attacker to + update arbitrary model attributes. + + ## Remediations + + ❌ Avoid blanket permitting of parameters: + + ```ruby + params.permit! + ``` + + ✅ Only permit parameters the user should be able to update: + + ```ruby + params.permit(:name, :email) + ``` + documentation_url: https://docs.bearer.com/reference/rules/ruby_rails_permissive_parameters + line_number: 4 + filename: /tmp/scan/bad.rb + parent_line_number: 4 + snippet: ActionController::Parameters.permit_all_parameters = true + fingerprint: 4ada1717774c2aa01998e9b04b82f795_2 diff --git a/ruby/rails/permissive_parameters/testdata/bad.rb b/ruby/rails/permissive_parameters/testdata/bad.rb index 5da01f033..b2e155e6a 100644 --- a/ruby/rails/permissive_parameters/testdata/bad.rb +++ b/ruby/rails/permissive_parameters/testdata/bad.rb @@ -1,3 +1,4 @@ params.permit! +params.merge(other).permit! ActionController::Parameters.permit_all_parameters = true diff --git a/ruby/rails/permissive_parameters/testdata/ok.rb b/ruby/rails/permissive_parameters/testdata/ok.rb index b21318853..bfc10d1df 100644 --- a/ruby/rails/permissive_parameters/testdata/ok.rb +++ b/ruby/rails/permissive_parameters/testdata/ok.rb @@ -1,3 +1,4 @@ params.permit(:name) +params.slice(:name).permit! ActionController::Parameters.permit_all_parameters = false diff --git a/ruby/rails/permissive_regex_validation.yml b/ruby/rails/permissive_regex_validation.yml index b903922d3..688e1c75b 100644 --- a/ruby/rails/permissive_regex_validation.yml +++ b/ruby/rails/permissive_regex_validation.yml @@ -8,7 +8,7 @@ patterns: regex: \A([/'"]|%r.).*.\z - not: variable: REGEX - regex: \A([/'"]|%r.)\\A.*\\[zZ].\z + regex: \A([/'"]|%r.)\\A.*\\[zZ](.|/[a-z]*)\z - pattern: | validates_format_of with: $ filters: @@ -16,7 +16,7 @@ patterns: regex: \A([/'"]|%r.).*.\z - not: variable: REGEX - regex: \A([/'"]|%r.)\\A.*\\[zZ].\z + regex: \A([/'"]|%r.)\\A.*\\[zZ](.|/[a-z]*)\z metadata: description: "Validation using permissive regular expression detected." remediation_message: | diff --git a/ruby/rails/permissive_regex_validation/testdata/ok.rb b/ruby/rails/permissive_regex_validation/testdata/ok.rb index 011694e7c..2e7f0ea94 100644 --- a/ruby/rails/permissive_regex_validation/testdata/ok.rb +++ b/ruby/rails/permissive_regex_validation/testdata/ok.rb @@ -1,4 +1,5 @@ validates :attr, format: { with: /\Aoops\z/ } +validates :attr, format: { with: /\Aoops\z/i } validates :attr, format: { with: %r[\Aoops\Z] } validates :attr, format: { with: "\Aoops\z" } validates :attr, format: { with: x } diff --git a/ruby/rails/render_using_user_input.yml b/ruby/rails/render_using_user_input.yml index cc384695c..d7abfac16 100644 --- a/ruby/rails/render_using_user_input.yml +++ b/ruby/rails/render_using_user_input.yml @@ -21,7 +21,7 @@ auxiliary: - id: ruby_rails_render_using_user_input_user_input patterns: - params - - request + - request.$<_> - cookies - id: ruby_rails_render_using_user_input_sanitized patterns: diff --git a/ruby/rails/session_key_using_user_input.yml b/ruby/rails/session_key_using_user_input.yml index 0c2e3780a..51a4e86f0 100644 --- a/ruby/rails/session_key_using_user_input.yml +++ b/ruby/rails/session_key_using_user_input.yml @@ -8,7 +8,7 @@ auxiliary: - id: ruby_rails_session_key_using_user_input_user_input patterns: - params - - request + - request.$<_> - cookies languages: - ruby diff --git a/ruby/rails/sql_injection.yml b/ruby/rails/sql_injection.yml index 759b5dfb1..634b8dc97 100644 --- a/ruby/rails/sql_injection.yml +++ b/ruby/rails/sql_injection.yml @@ -69,7 +69,7 @@ auxiliary: - id: ruby_rails_sql_injection_user_input_source patterns: - params - - request + - request.$<_> - cookies - | # AWS lambda def $<_>($event:, context:) diff --git a/scripts/rule_schema.json b/scripts/rule_schema.json index f1d4cfb13..91cef6d96 100644 --- a/scripts/rule_schema.json +++ b/scripts/rule_schema.json @@ -157,6 +157,12 @@ "regex": { "type": "string" }, + "filename_regex": { + "type": "string" + }, + "string_regex": { + "type": "string" + }, "detection": { "type": "string" }, @@ -216,4 +222,4 @@ "title": "Filter" } } -} \ No newline at end of file +}