diff --git a/rules/java/security/simple-command-injection-direct-input-java.yml b/rules/java/security/simple-command-injection-direct-input-java.yml new file mode 100644 index 00000000..ad7f3e6e --- /dev/null +++ b/rules/java/security/simple-command-injection-direct-input-java.yml @@ -0,0 +1,56 @@ +id: simple-command-injection-direct-input-java +language: java +severity: warning +message: >- + "Untrusted input might be injected into a command executed by the + application, which can lead to a command injection vulnerability. An + attacker can execute arbitrary commands, potentially gaining complete + control of the system. To prevent this vulnerability, avoid executing OS + commands with user input. If this is unavoidable, validate and sanitize + the input, and use safe methods for executing the commands. For more + information, see: [Java command injection + prevention](https://semgrep.dev/docs/cheat-sheets/java-command-injection/\ + )" +note: >- + [CWE-78] Improper Neutralization of Special Elements used in an OS + [REFERENCES] + - https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html + - https://owasp.org/Top10/A03_2021-Injection + +rule: + kind: method_invocation + pattern: Runtime.getRuntime().exec($SOURCE) + inside: + kind: method_declaration + stopBy: end + has: + stopBy: end + kind: formal_parameter + has: + kind: modifiers + any: + - has: + kind: marker_annotation + has: + kind: identifier + pattern: $REQ + - has: + kind: annotation + all: + - has: + kind: identifier + pattern: $REQ + - has: + kind: annotation_argument_list + precedes: + kind: type_identifier + pattern: $TYPE + precedes: + kind: identifier + pattern: $SOURCE + +constraints: + REQ: + regex: ^(RequestBody|PathVariable|RequestParam|RequestHeader|CookieValue|ModelAttribute) + TYPE: + regex: ^[^I].*|^I[^n].*|^In[^t].*|^Int[^e].*|^Inte[^g].*|^Integ[^e].*|^Inge[^r].*|^L[^o].*|^Lo[^n].*|^Lon[^g].*|^F[^l].*|^Fl[^o].*|^Flo[^a].*|^Floa[^t].*|^D[^o].*|^Do[^u].*|^Dou[^b].*|^Doub[^l].*|^Doubl[^e].*|^C[^h].*|^Ch[^a].*|^Cha[^r].*|^B[^o].*|^Bo[^o].*|^Boo[^l].*|^Bool[^e].*|^Boole[^a].*|^Boolea[^n].*|^i[^n].*|^in[^t].*|^l[^o].*|^lo[^n].*|^lon[^g].*|^f[^l].*|^fl[^o].*|^flo[^a].*|^floa[^t].*|^d[^o].*|^do[^u].*|^dou[^b].*|^doub[^l].*|^doubl[^e].*|^c[^h].*|^ch[^a].*|^cha[^r].*|^b[^o].*|^bo[^o].*|^boo[^l].*|^bool[^e].*|^boole[^a].*|^boolea[^n].* diff --git a/rules/javascript/security/detect-angular-sce-disabled-javascript.yml b/rules/javascript/security/detect-angular-sce-disabled-javascript.yml new file mode 100644 index 00000000..6ddd33fc --- /dev/null +++ b/rules/javascript/security/detect-angular-sce-disabled-javascript.yml @@ -0,0 +1,15 @@ +id: detect-angular-sce-disabled-javascript +language: javascript +severity: warning +message: >- + $sceProvider is set to false. Disabling Strict Contextual escaping + (SCE) in an AngularJS application could provide additional attack surface + for XSS vulnerabilities. +note: >- + [CWE-79] Improper Neutralization of Input During Web Page Generation. + [REFERENCES] + - https://docs.angularjs.org/api/ng/service/$sce + - https://owasp.org/www-chapter-london/assets/slides/OWASPLondon20170727_AngularJS.pdf +rule: + pattern: | + $sceProvider.enabled(false); diff --git a/rules/javascript/security/node-sequelize-empty-password-argument-javascript.yml b/rules/javascript/security/node-sequelize-empty-password-argument-javascript.yml new file mode 100644 index 00000000..2525fc53 --- /dev/null +++ b/rules/javascript/security/node-sequelize-empty-password-argument-javascript.yml @@ -0,0 +1,197 @@ +id: node-sequelize-empty-password-argument-javascript +language: javascript +severity: warning +message: >- + The application creates a database connection with an empty password. + This can lead to unauthorized access by either an internal or external + malicious actor. To prevent this vulnerability, enforce authentication + when connecting to a database by using environment variables to securely + provide credentials or retrieving them from a secure vault or HSM + (Hardware Security Module). +note: >- + [CWE-287] Improper Authentication. + [REFERENCES] + - https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html +utils: + MATCH_BLANK_PASSWORD: + kind: string + pattern: $Q + inside: + stopBy: end + kind: lexical_declaration + all: + - has: + stopBy: end + kind: new_expression + all: + - has: + stopBy: end + kind: identifier + pattern: $E + - has: + stopBy: end + kind: arguments + nthChild: 2 + has: + stopBy: end + kind: string + nthChild: 3 + pattern: $Q + not: + has: + stopBy: end + kind: string_fragment + - any: + - follows: + stopBy: end + kind: lexical_declaration + has: + stopBy: end + kind: variable_declarator + all: + - has: + stopBy: neighbor + kind: identifier + - has: + stopBy: neighbor + kind: call_expression + all: + - has: + stopBy: neighbor + kind: identifier + regex: '^require$' + - has: + stopBy: neighbor + kind: arguments + has: + stopBy: neighbor + kind: string + has: + stopBy: neighbor + kind: string_fragment + regex: '^sequelize$' + - follows: + stopBy: end + kind: import_statement + has: + stopBy: end + kind: import_clause + has: + stopBy: end + kind: identifier + pattern: $E + - follows: + stopBy: end + kind: import_statement + has: + stopBy: end + kind: import_clause + has: + stopBy: end + kind: identifier + pattern: $E + + + MATCH_BLANK_PASSWORD_WITH_INSTANCE: + kind: identifier + pattern: $Q + inside: + stopBy: end + kind: lexical_declaration + all: + - has: + stopBy: end + kind: new_expression + all: + - has: + stopBy: end + kind: identifier + pattern: $E + - has: + stopBy: end + kind: arguments + nthChild: 2 + has: + stopBy: end + kind: identifier + nthChild: 3 + pattern: $Q + not: + has: + stopBy: end + kind: string_fragment + - follows: + stopBy: end + kind: lexical_declaration + has: + stopBy: end + kind: variable_declarator + all: + - has: + stopBy: neighbor + kind: identifier + pattern: $Q + - has: + stopBy: neighbor + kind: string + not: + has: + stopBy: neighbor + kind: string_fragment + - any: + - follows: + stopBy: end + kind: lexical_declaration + has: + stopBy: end + kind: variable_declarator + all: + - has: + stopBy: neighbor + kind: identifier + - has: + stopBy: neighbor + kind: call_expression + all: + - has: + stopBy: neighbor + kind: identifier + regex: '^require$' + - has: + stopBy: neighbor + kind: arguments + has: + stopBy: neighbor + kind: string + has: + stopBy: neighbor + kind: string_fragment + regex: '^sequelize$' + - follows: + stopBy: end + kind: import_statement + has: + stopBy: end + kind: import_clause + has: + stopBy: end + kind: identifier + pattern: $E + - follows: + stopBy: end + kind: import_statement + has: + stopBy: end + kind: import_clause + has: + stopBy: end + kind: identifier + pattern: $E +rule: + any: + - kind: string + matches: MATCH_BLANK_PASSWORD + - kind: identifier + matches: MATCH_BLANK_PASSWORD_WITH_INSTANCE + + diff --git a/tests/__snapshots__/detect-angular-sce-disabled-javascript-snapshot.yml b/tests/__snapshots__/detect-angular-sce-disabled-javascript-snapshot.yml new file mode 100644 index 00000000..809d3ff2 --- /dev/null +++ b/tests/__snapshots__/detect-angular-sce-disabled-javascript-snapshot.yml @@ -0,0 +1,9 @@ +id: detect-angular-sce-disabled-javascript +snapshots: + ? | + $sceProvider.enabled(false); + : labels: + - source: $sceProvider.enabled(false); + style: primary + start: 0 + end: 28 diff --git a/tests/__snapshots__/node-sequelize-empty-password-argument-javascript-snapshot.yml b/tests/__snapshots__/node-sequelize-empty-password-argument-javascript-snapshot.yml new file mode 100644 index 00000000..1f2d60ee --- /dev/null +++ b/tests/__snapshots__/node-sequelize-empty-password-argument-javascript-snapshot.yml @@ -0,0 +1,273 @@ +id: node-sequelize-empty-password-argument-javascript +snapshots: + ? | + const Sequelize = require('sequelize'); + const passwordDynamic = ''; + const sequelize2 = new Sequelize('database', 'username', passwordDynamic, { + host: 'localhost', + port: 5432, + dialect: 'postgres' + }); + : labels: + - source: passwordDynamic + style: primary + start: 125 + end: 140 + - source: Sequelize + style: secondary + start: 91 + end: 100 + - source: passwordDynamic + style: secondary + start: 125 + end: 140 + - source: |- + ('database', 'username', passwordDynamic, { + host: 'localhost', + port: 5432, + dialect: 'postgres' + }) + style: secondary + start: 100 + end: 197 + - source: |- + new Sequelize('database', 'username', passwordDynamic, { + host: 'localhost', + port: 5432, + dialect: 'postgres' + }) + style: secondary + start: 87 + end: 197 + - source: passwordDynamic + style: secondary + start: 46 + end: 61 + - source: '''''' + style: secondary + start: 64 + end: 66 + - source: passwordDynamic = '' + style: secondary + start: 46 + end: 66 + - source: const passwordDynamic = ''; + style: secondary + start: 40 + end: 67 + - source: Sequelize + style: secondary + start: 6 + end: 15 + - source: require + style: secondary + start: 18 + end: 25 + - source: sequelize + style: secondary + start: 27 + end: 36 + - source: '''sequelize''' + style: secondary + start: 26 + end: 37 + - source: ('sequelize') + style: secondary + start: 25 + end: 38 + - source: require('sequelize') + style: secondary + start: 18 + end: 38 + - source: Sequelize = require('sequelize') + style: secondary + start: 6 + end: 38 + - source: const Sequelize = require('sequelize'); + style: secondary + start: 0 + end: 39 + - source: |- + const sequelize2 = new Sequelize('database', 'username', passwordDynamic, { + host: 'localhost', + port: 5432, + dialect: 'postgres' + }); + style: secondary + start: 68 + end: 198 + ? | + const Sequelize = require('sequelize'); + const passwordFromEnv = ''; + const sequelize2 = new Sequelize('database', 'username', passwordFromEnv, { + host: 'localhost', + port: 5432, + dialect: 'postgres' + }); + : labels: + - source: passwordFromEnv + style: primary + start: 125 + end: 140 + - source: Sequelize + style: secondary + start: 91 + end: 100 + - source: passwordFromEnv + style: secondary + start: 125 + end: 140 + - source: |- + ('database', 'username', passwordFromEnv, { + host: 'localhost', + port: 5432, + dialect: 'postgres' + }) + style: secondary + start: 100 + end: 197 + - source: |- + new Sequelize('database', 'username', passwordFromEnv, { + host: 'localhost', + port: 5432, + dialect: 'postgres' + }) + style: secondary + start: 87 + end: 197 + - source: passwordFromEnv + style: secondary + start: 46 + end: 61 + - source: '''''' + style: secondary + start: 64 + end: 66 + - source: passwordFromEnv = '' + style: secondary + start: 46 + end: 66 + - source: const passwordFromEnv = ''; + style: secondary + start: 40 + end: 67 + - source: Sequelize + style: secondary + start: 6 + end: 15 + - source: require + style: secondary + start: 18 + end: 25 + - source: sequelize + style: secondary + start: 27 + end: 36 + - source: '''sequelize''' + style: secondary + start: 26 + end: 37 + - source: ('sequelize') + style: secondary + start: 25 + end: 38 + - source: require('sequelize') + style: secondary + start: 18 + end: 38 + - source: Sequelize = require('sequelize') + style: secondary + start: 6 + end: 38 + - source: const Sequelize = require('sequelize'); + style: secondary + start: 0 + end: 39 + - source: |- + const sequelize2 = new Sequelize('database', 'username', passwordFromEnv, { + host: 'localhost', + port: 5432, + dialect: 'postgres' + }); + style: secondary + start: 68 + end: 198 + ? | + const Sequelize = require('sequelize'); + const sequelize1 = new Sequelize('database', 'username', '', { + host: 'localhost', + port: '5433', + dialect: 'postgres' + }) + : labels: + - source: '''''' + style: primary + start: 97 + end: 99 + - source: Sequelize + style: secondary + start: 63 + end: 72 + - source: '''''' + style: secondary + start: 97 + end: 99 + - source: |- + ('database', 'username', '', { + host: 'localhost', + port: '5433', + dialect: 'postgres' + }) + style: secondary + start: 72 + end: 158 + - source: |- + new Sequelize('database', 'username', '', { + host: 'localhost', + port: '5433', + dialect: 'postgres' + }) + style: secondary + start: 59 + end: 158 + - source: Sequelize + style: secondary + start: 6 + end: 15 + - source: require + style: secondary + start: 18 + end: 25 + - source: sequelize + style: secondary + start: 27 + end: 36 + - source: '''sequelize''' + style: secondary + start: 26 + end: 37 + - source: ('sequelize') + style: secondary + start: 25 + end: 38 + - source: require('sequelize') + style: secondary + start: 18 + end: 38 + - source: Sequelize = require('sequelize') + style: secondary + start: 6 + end: 38 + - source: const Sequelize = require('sequelize'); + style: secondary + start: 0 + end: 39 + - source: |- + const sequelize1 = new Sequelize('database', 'username', '', { + host: 'localhost', + port: '5433', + dialect: 'postgres' + }) + style: secondary + start: 40 + end: 158 diff --git a/tests/__snapshots__/simple-command-injection-direct-input-java-snapshot.yml b/tests/__snapshots__/simple-command-injection-direct-input-java-snapshot.yml new file mode 100644 index 00000000..22d0b82e --- /dev/null +++ b/tests/__snapshots__/simple-command-injection-direct-input-java-snapshot.yml @@ -0,0 +1,126 @@ +id: simple-command-injection-direct-input-java +snapshots: + ? | + @GetMapping("/run/{command}") + public ResponseEntity run_direct_from_jumbo( + @PathVariable final String command + ) { + ResponseEntity response = ResponseEntity.noContent().build(); + try { + Runtime.getRuntime().exec(command); + } catch (IOException e) { + response = ResponseEntity.badRequest().build(); + } + + return response; + } + : labels: + - source: Runtime.getRuntime().exec(command) + style: primary + start: 208 + end: 242 + - source: PathVariable + style: secondary + start: 83 + end: 95 + - source: '@PathVariable' + style: secondary + start: 82 + end: 95 + - source: command + style: secondary + start: 109 + end: 116 + - source: String + style: secondary + start: 102 + end: 108 + - source: '@PathVariable final' + style: secondary + start: 82 + end: 101 + - source: '@PathVariable final String command' + style: secondary + start: 82 + end: 116 + - source: |- + @GetMapping("/run/{command}") + public ResponseEntity run_direct_from_jumbo( + @PathVariable final String command + ) { + ResponseEntity response = ResponseEntity.noContent().build(); + try { + Runtime.getRuntime().exec(command); + } catch (IOException e) { + response = ResponseEntity.badRequest().build(); + } + + return response; + } + style: secondary + start: 0 + end: 358 + ? | + @GetMapping("/run/{command}") + public ResponseEntity run_direct_from_jumbo( + @PathVariable() final String command + ) { + ResponseEntity response = ResponseEntity.noContent().build(); + try { + Runtime.getRuntime().exec(command); + } catch (IOException e) { + response = ResponseEntity.badRequest().build(); + } + + return response; + } + : labels: + - source: Runtime.getRuntime().exec(command) + style: primary + start: 210 + end: 244 + - source: PathVariable + style: secondary + start: 83 + end: 95 + - source: () + style: secondary + start: 95 + end: 97 + - source: '@PathVariable()' + style: secondary + start: 82 + end: 97 + - source: command + style: secondary + start: 111 + end: 118 + - source: String + style: secondary + start: 104 + end: 110 + - source: '@PathVariable() final' + style: secondary + start: 82 + end: 103 + - source: '@PathVariable() final String command' + style: secondary + start: 82 + end: 118 + - source: |- + @GetMapping("/run/{command}") + public ResponseEntity run_direct_from_jumbo( + @PathVariable() final String command + ) { + ResponseEntity response = ResponseEntity.noContent().build(); + try { + Runtime.getRuntime().exec(command); + } catch (IOException e) { + response = ResponseEntity.badRequest().build(); + } + + return response; + } + style: secondary + start: 0 + end: 360 diff --git a/tests/java/simple-command-injection-direct-input-java-test.yml b/tests/java/simple-command-injection-direct-input-java-test.yml new file mode 100644 index 00000000..cba713e4 --- /dev/null +++ b/tests/java/simple-command-injection-direct-input-java-test.yml @@ -0,0 +1,59 @@ +id: simple-command-injection-direct-input-java +valid: + - | + @GetMapping("/run/{command}") + public ResponseEntity run1( + @PathVariable final String command + ) { + ResponseEntity response = ResponseEntity.noContent().build(); + try { + String foo = command + "something something..."; + Runtime.getRuntime().exec(foo); + } catch (IOException e) { + response = ResponseEntity.badRequest().build(); + } + return response; + } + - | + @GetMapping("/run/{command}") + public ResponseEntity ok( + @PathVariable final String command + ) { + ResponseEntity response = ResponseEntity.noContent().build(); + try { + Runtime.getRuntime().exec("/bin/ls"); + } catch (IOException e) { + response = ResponseEntity.badRequest().build(); + } + + return response; + } +invalid: + - | + @GetMapping("/run/{command}") + public ResponseEntity run_direct_from_jumbo( + @PathVariable() final String command + ) { + ResponseEntity response = ResponseEntity.noContent().build(); + try { + Runtime.getRuntime().exec(command); + } catch (IOException e) { + response = ResponseEntity.badRequest().build(); + } + + return response; + } + - | + @GetMapping("/run/{command}") + public ResponseEntity run_direct_from_jumbo( + @PathVariable final String command + ) { + ResponseEntity response = ResponseEntity.noContent().build(); + try { + Runtime.getRuntime().exec(command); + } catch (IOException e) { + response = ResponseEntity.badRequest().build(); + } + + return response; + } diff --git a/tests/javascript/detect-angular-sce-disabled-javascript-test.yml b/tests/javascript/detect-angular-sce-disabled-javascript-test.yml new file mode 100644 index 00000000..02b587b4 --- /dev/null +++ b/tests/javascript/detect-angular-sce-disabled-javascript-test.yml @@ -0,0 +1,7 @@ +id: detect-angular-sce-disabled-javascript +valid: + - | + $sceProvider.enabled(true); +invalid: + - | + $sceProvider.enabled(false); diff --git a/tests/javascript/node-sequelize-empty-password-argument-javascript-test.yml b/tests/javascript/node-sequelize-empty-password-argument-javascript-test.yml new file mode 100644 index 00000000..b0a0e79f --- /dev/null +++ b/tests/javascript/node-sequelize-empty-password-argument-javascript-test.yml @@ -0,0 +1,34 @@ +id: node-sequelize-empty-password-argument-javascript +valid: + - | + const Sequelize = require('sequelize'); + const sequelize = new Sequelize({ + database: 'pinche', + username: 'root', + password: '123456789', + dialect: 'mysql' + }); +invalid: + - | + const Sequelize = require('sequelize'); + const sequelize1 = new Sequelize('database', 'username', '', { + host: 'localhost', + port: '5433', + dialect: 'postgres' + }) + - | + const Sequelize = require('sequelize'); + const passwordFromEnv = ''; + const sequelize2 = new Sequelize('database', 'username', passwordFromEnv, { + host: 'localhost', + port: 5432, + dialect: 'postgres' + }); + - | + const Sequelize = require('sequelize'); + const passwordDynamic = ''; + const sequelize2 = new Sequelize('database', 'username', passwordDynamic, { + host: 'localhost', + port: 5432, + dialect: 'postgres' + });