From 53cba93a23fae1a644de6468e0aaba883e3d7435 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Sat, 26 Sep 2020 21:52:37 -0400 Subject: [PATCH 1/3] semgrep tests --- .github/workflows/semgrep-test.yml | 30 ++ Makefile | 2 +- nodejsscan/archive_path_overwrite.js | 82 --- nodejsscan/archive_path_overwrite.yaml | 158 ------ nodejsscan/crypto_node.js | 53 -- nodejsscan/crypto_node.yaml | 72 --- nodejsscan/crypto_timing_attacks.js | 56 -- nodejsscan/error_disclosure.yaml | 51 -- nodejsscan/error_info_disclosure.js | 23 - nodejsscan/eval_deserialize.js | 31 -- nodejsscan/eval_deserialize.yaml | 33 -- nodejsscan/eval_node.js | 19 - nodejsscan/eval_node.yaml | 69 --- nodejsscan/eval_yaml_deserialize.js | 14 - nodejsscan/eval_yaml_deserialize.yaml | 17 - nodejsscan/exec_os_command.js | 118 ----- nodejsscan/exec_os_command.yaml | 91 ---- nodejsscan/express_bodyparser_dos.js | 16 - nodejsscan/express_bodyparser_dos.yaml | 19 - nodejsscan/good_anti_csrf.yaml | 19 - nodejsscan/good_helmet_checks.yaml | 236 --------- nodejsscan/good_ratelimiting.yaml | 14 - nodejsscan/hardcoded_jwt.js | 119 ----- nodejsscan/hardcoded_jwt.yaml | 73 --- nodejsscan/hardcoded_secrets.js | 28 - nodejsscan/hardcoded_secrets.yaml | 185 ------- nodejsscan/header_cors_star.js | 41 -- nodejsscan/header_cors_star.yaml | 71 --- nodejsscan/header_helmet_disabled.js | 10 - nodejsscan/header_helmet_disabled.yaml | 36 -- nodejsscan/header_injection.js | 63 --- nodejsscan/header_injection.yaml | 55 -- nodejsscan/header_xss_protection.js | 54 -- nodejsscan/header_xss_protection.yaml | 68 --- nodejsscan/host_header_injection.js | 54 -- nodejsscan/host_header_injection.yaml | 55 -- nodejsscan/jwt_none_algorithm.js | 14 - nodejsscan/jwt_none_algorithm.yaml | 45 -- nodejsscan/layer7_object_dos.js | 31 -- nodejsscan/layer7_object_dos.yaml | 29 - nodejsscan/logic_bypass.yaml | 55 -- nodejsscan/logic_user_controlled_checks.js | 10 - nodejsscan/missing_good_controls.js | 35 -- nodejsscan/nosql_injection.js | 81 --- nodejsscan/nosql_injection.yaml | 167 ------ nodejsscan/open_redirect.js | 130 ----- nodejsscan/open_redirect.yaml | 83 --- nodejsscan/path_traversal.js | 40 -- nodejsscan/path_traversal.yaml | 128 ----- nodejsscan/regex_dos.js | 56 -- nodejsscan/regex_dos.yaml | 67 --- nodejsscan/regex_injection.yaml | 67 --- nodejsscan/regex_injection_dos.js | 11 - nodejsscan/security_electron.js | 65 --- nodejsscan/security_electronjs.yaml | 99 ---- nodejsscan/server_side_template_injection.js | 45 -- .../server_side_template_injection.yaml | 78 --- nodejsscan/sql_injection.yaml | 39 -- nodejsscan/sqli_node.js | 66 --- nodejsscan/ssrf_node.js | 143 ----- nodejsscan/ssrf_node.yaml | 191 ------- nodejsscan/timing_attack_node.yaml | 494 ------------------ nodejsscan/tls_node.js | 56 -- nodejsscan/tls_node.yaml | 28 - nodejsscan/xml_entity_expansion.js | 5 - nodejsscan/xml_entity_expansion_dos.yaml | 31 -- nodejsscan/xpathi_node.js | 19 - nodejsscan/xpathi_node.yaml | 57 -- nodejsscan/xss_node.js | 115 ---- nodejsscan/xss_node.yaml | 137 ----- nodejsscan/xss_templates.js | 25 - nodejsscan/xss_templates.yaml | 44 -- nodejsscan/xxe_node.js | 57 -- nodejsscan/xxe_node.yaml | 110 ---- 74 files changed, 31 insertions(+), 5057 deletions(-) create mode 100644 .github/workflows/semgrep-test.yml delete mode 100644 nodejsscan/archive_path_overwrite.js delete mode 100644 nodejsscan/archive_path_overwrite.yaml delete mode 100644 nodejsscan/crypto_node.js delete mode 100644 nodejsscan/crypto_node.yaml delete mode 100644 nodejsscan/crypto_timing_attacks.js delete mode 100644 nodejsscan/error_disclosure.yaml delete mode 100644 nodejsscan/error_info_disclosure.js delete mode 100644 nodejsscan/eval_deserialize.js delete mode 100644 nodejsscan/eval_deserialize.yaml delete mode 100644 nodejsscan/eval_node.js delete mode 100644 nodejsscan/eval_node.yaml delete mode 100644 nodejsscan/eval_yaml_deserialize.js delete mode 100644 nodejsscan/eval_yaml_deserialize.yaml delete mode 100644 nodejsscan/exec_os_command.js delete mode 100644 nodejsscan/exec_os_command.yaml delete mode 100644 nodejsscan/express_bodyparser_dos.js delete mode 100644 nodejsscan/express_bodyparser_dos.yaml delete mode 100644 nodejsscan/good_anti_csrf.yaml delete mode 100644 nodejsscan/good_helmet_checks.yaml delete mode 100644 nodejsscan/good_ratelimiting.yaml delete mode 100644 nodejsscan/hardcoded_jwt.js delete mode 100644 nodejsscan/hardcoded_jwt.yaml delete mode 100644 nodejsscan/hardcoded_secrets.js delete mode 100644 nodejsscan/hardcoded_secrets.yaml delete mode 100644 nodejsscan/header_cors_star.js delete mode 100644 nodejsscan/header_cors_star.yaml delete mode 100644 nodejsscan/header_helmet_disabled.js delete mode 100644 nodejsscan/header_helmet_disabled.yaml delete mode 100644 nodejsscan/header_injection.js delete mode 100644 nodejsscan/header_injection.yaml delete mode 100644 nodejsscan/header_xss_protection.js delete mode 100644 nodejsscan/header_xss_protection.yaml delete mode 100644 nodejsscan/host_header_injection.js delete mode 100644 nodejsscan/host_header_injection.yaml delete mode 100644 nodejsscan/jwt_none_algorithm.js delete mode 100644 nodejsscan/jwt_none_algorithm.yaml delete mode 100644 nodejsscan/layer7_object_dos.js delete mode 100644 nodejsscan/layer7_object_dos.yaml delete mode 100644 nodejsscan/logic_bypass.yaml delete mode 100644 nodejsscan/logic_user_controlled_checks.js delete mode 100644 nodejsscan/missing_good_controls.js delete mode 100644 nodejsscan/nosql_injection.js delete mode 100644 nodejsscan/nosql_injection.yaml delete mode 100644 nodejsscan/open_redirect.js delete mode 100644 nodejsscan/open_redirect.yaml delete mode 100644 nodejsscan/path_traversal.js delete mode 100644 nodejsscan/path_traversal.yaml delete mode 100644 nodejsscan/regex_dos.js delete mode 100644 nodejsscan/regex_dos.yaml delete mode 100644 nodejsscan/regex_injection.yaml delete mode 100644 nodejsscan/regex_injection_dos.js delete mode 100644 nodejsscan/security_electron.js delete mode 100644 nodejsscan/security_electronjs.yaml delete mode 100644 nodejsscan/server_side_template_injection.js delete mode 100644 nodejsscan/server_side_template_injection.yaml delete mode 100644 nodejsscan/sql_injection.yaml delete mode 100644 nodejsscan/sqli_node.js delete mode 100644 nodejsscan/ssrf_node.js delete mode 100644 nodejsscan/ssrf_node.yaml delete mode 100644 nodejsscan/timing_attack_node.yaml delete mode 100644 nodejsscan/tls_node.js delete mode 100644 nodejsscan/tls_node.yaml delete mode 100644 nodejsscan/xml_entity_expansion.js delete mode 100644 nodejsscan/xml_entity_expansion_dos.yaml delete mode 100644 nodejsscan/xpathi_node.js delete mode 100644 nodejsscan/xpathi_node.yaml delete mode 100644 nodejsscan/xss_node.js delete mode 100644 nodejsscan/xss_node.yaml delete mode 100644 nodejsscan/xss_templates.js delete mode 100644 nodejsscan/xss_templates.yaml delete mode 100644 nodejsscan/xxe_node.js delete mode 100644 nodejsscan/xxe_node.yaml diff --git a/.github/workflows/semgrep-test.yml b/.github/workflows/semgrep-test.yml new file mode 100644 index 0000000..bed5b1d --- /dev/null +++ b/.github/workflows/semgrep-test.yml @@ -0,0 +1,30 @@ +name: semgrep rules test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest] + python-version: [3.6] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install semgrep + - name: Run tests + run: | + python -m semgrep --quiet --json --test rules \ No newline at end of file diff --git a/Makefile b/Makefile index 6279e16..aa921fd 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,3 @@ test: - semgrep --validate --config=$$PWD/nodejsscan $$PWD + semgrep --validate --config=$$PWD/rules $$PWD semgrep --test --strict --test-ignore-todo $$PWD diff --git a/nodejsscan/archive_path_overwrite.js b/nodejsscan/archive_path_overwrite.js deleted file mode 100644 index 75d413f..0000000 --- a/nodejsscan/archive_path_overwrite.js +++ /dev/null @@ -1,82 +0,0 @@ -//Ref: https://snyk.io/research/zip-slip-vulnerability -const fs = require('fs'); -const unzip = require('unzip'); - -fs.createReadStream('archive.zip') - .pipe(unzip.Parse()) - .on('entry', entry => { - const fileName = entry.path; - // Arbitrary file overwrite - // ruleid:zip_path_overwrite - entry.pipe(fs.createWriteStream(fileName)); - }); - -fs.createReadStream('archive.zip') - .pipe(unzip.Parse()) - .on('entry', entry => { - const fileName = entry.path; - // Arbitrary file overwrite - // ruleid:zip_path_overwrite - entry.pipe(fs.writeFileSync(fileName)); - }); - -fs.readFile('path/to/archive.zip', function (err, zipContents) { - unzip.Parse(zipContents).on('entry', function (entry) { - var fileName = 'output/path/' + entry.path; - // Arbitrary file overwrite - // ruleid:zip_path_overwrite2 - fs.writeFileSync(fileName, entry.contents); - }); -}); - -//admzip -const fs = require('fs'); -var AdmZip = require('adm-zip'); -var zip = new AdmZip("archive.zip"); -var zipEntries = zip.getEntries(); -// ruleid:admzip_path_overwrite -zipEntries.forEach(function (zipEntry) { - fs.createWriteStream(zipEntry.entryName); -}); - -// ruleid:admzip_path_overwrite -zip.getEntries().forEach(function (zipEntry) { - fs.writeFileSync(zipEntry.entryName); -}); - -// tar-stream overwrite -const tar = require('tar-stream'); -const extract = tar.extract(); - -extract.on('entry', (header, stream, next) => { - // ruleid:tar_path_overwrite - const out = fs.createWriteStream(header.name); - stream.pipe(out); - stream.on('end', () => { - next(); - }) - stream.resume(); -}) - -tar.extract().on('entry', (header, stream, next) => { - // ruleid:tar_path_overwrite - const out = fs.writeFileSync(header.name); - stream.pipe(out); - stream.on('end', () => { - next(); - }) - stream.resume(); -}) - -///unzipper lib -fs.createReadStream('./bad.tar').pipe(extract); -const fs = require('fs'); -const unzipper = require('unzipper'); - -fs.createReadStream('path/to/archive.zip') - .pipe(unzipper.Parse()) - .on('entry', function (entry) { - var fileName = entry.path; - // ruleid:zip_path_overwrite - entry.pipe(fs.createWriteStream(fileName)); - }); diff --git a/nodejsscan/archive_path_overwrite.yaml b/nodejsscan/archive_path_overwrite.yaml deleted file mode 100644 index a8aa978..0000000 --- a/nodejsscan/archive_path_overwrite.yaml +++ /dev/null @@ -1,158 +0,0 @@ -rules: - - id: zip_path_overwrite - patterns: - - pattern-either: - - pattern-inside: | - $X = require('unzip'); - ... - - pattern-inside: | - $X = require('unzipper'); - ... - - pattern-inside: | - $Y.pipe($UNZIP.Parse(...)).on('entry', function $FUNC(...) { - ... - }, ...); - - pattern-not: | - $X = $FILENAME.indexOf(...); - - pattern-not: > - $FUNC.pipe($FS.createWriteStream($PATH.join(..., - $PATH.basename($FILENAME, ...)))); - - pattern-not: > - $FUNC.pipe($FS.writeFile($PATH.join(..., $PATH.basename($FILENAME, - ...)))); - - pattern-not: > - $FUNC.pipe($FS.writeFileSync($PATH.join(..., $PATH.basename($FILENAME, - ...)))); - - pattern-either: - - pattern: | - $FUNC.pipe($FS.createWriteStream($FIL, ...)); - - pattern: | - $FUNC.pipe($FS.writeFile($FIL, ...)); - - pattern: | - $FUNC.pipe($FS.writeFileSync($FIL, ...)); - message: >- - Insecure ZIP archive extraction can result in arbitrary path over write - and can result in code injection. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A5: Broken Access Control' - cwe: >- - CWE-22: Improper Limitation of a Pathname to a Restricted Directory - ('Path Traversal') - - id: zip_path_overwrite2 - patterns: - - pattern-either: - - pattern-inside: | - $X = require('unzip'); - ... - - pattern-inside: | - $X = require('unzipper'); - ... - - pattern-inside: | - $UNZIP.Parse(...).on('entry', function $FUNC($ENTRY) { - ... - }, ...); - - pattern-not: | - if ($FILENAME.indexOf('..')); - - pattern-not: > - $FS.createWriteStream($PATH.join(..., $PATH.basename($FILENAME, - ...))); - - pattern-not: | - $FS.writeFile($PATH.join(..., $PATH.basename($FILENAME, ...))); - - pattern-not: | - $FS.writeFileSync($PATH.join(..., $PATH.basename($FILENAME, ...))); - - pattern-either: - - pattern: | - $FS.createWriteStream($FIL, ...); - - pattern: | - $FS.writeFile($FIL, ...); - - pattern: | - $FS.writeFileSync($FIL, ...); - message: >- - Insecure ZIP archive extraction can result in arbitrary path over write - and can result in code injection. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A5: Broken Access Control' - cwe: >- - CWE-22: Improper Limitation of a Pathname to a Restricted Directory - ('Path Traversal') - - id: admzip_path_overwrite - patterns: - - pattern-inside: | - $X = require('adm-zip'); - ... - - pattern-not: | - if ($FILENAME.indexOf('..')); - - pattern-not: > - $FS.createWriteStream($PATH.join(..., $PATH.basename($FILENAME, - ...))); - - pattern-not: | - $FS.writeFile($PATH.join(..., $PATH.basename($FILENAME, ...))); - - pattern-not: | - $FS.writeFileSync($PATH.join(..., $PATH.basename($FILENAME, ...))); - - pattern-either: - - pattern: >- - $ZIPENTZ.forEach(function $FUNC($ENTRY, ...) { - $FS.createWriteStream(...); }, ...); - - pattern: >- - $ZIPENTZ.forEach(function $FUNC($ENTRY, ...) { $FS.writeFile(...); - }, ...); - - pattern: >- - $ZIPENTZ.forEach(function $FUNC($ENTRY, ...) { - $FS.writeFileSync(...); }, ...); - message: >- - Insecure ZIP archive extraction using adm-zip can result in arbitrary path - over write and can result in code injection. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A5: Broken Access Control' - cwe: >- - CWE-22: Improper Limitation of a Pathname to a Restricted Directory - ('Path Traversal') - - id: tar_path_overwrite - patterns: - - pattern-inside: | - $X = require('tar-stream'); - ... - - pattern-not-inside: | - $Y.pipe($UNZIP.Parse(...)).on('entry', function $FUNC(...) { - ... - }, ...); - - pattern-inside: | - $EXTRACT.on('entry', function $FUNC(...) { - ... - }, ...); - - pattern-not: | - if ($FILENAME.indexOf('..')); - - pattern-not: > - $FS.createWriteStream($PATH.join(..., $PATH.basename($FILENAME, - ...))); - - pattern-not: | - $FS.writeFile($PATH.join(..., $PATH.basename($FILENAME, ...))); - - pattern-not: | - $FS.writeFileSync($PATH.join(..., $PATH.basename($FILENAME, ...))); - - pattern-either: - - pattern: | - $FS.createWriteStream($FIL, ...); - - pattern: | - $FS.writeFile($FIL, ...); - - pattern: | - $FS.writeFileSync($FIL, ...); - message: >- - Insecure TAR archive extraction can result in arbitrary path over write - and can result in code injection. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A5: Broken Access Control' - cwe: >- - CWE-22: Improper Limitation of a Pathname to a Restricted Directory - ('Path Traversal') diff --git a/nodejsscan/crypto_node.js b/nodejsscan/crypto_node.js deleted file mode 100644 index cbce6ce..0000000 --- a/nodejsscan/crypto_node.js +++ /dev/null @@ -1,53 +0,0 @@ -var key = new Buffer('8CBDEC62EB4DCA778F842B02503011B2', 'hex') -var src = new Buffer('0002123401010100000000000000c631', 'hex') -// ruleid:node_aes_ecb -cipher = crypto.createCipheriv("aes-128-ecb", key, '') -cipher.setAutoPadding(false) -result = cipher.update(src).toString('hex'); -result += cipher.final().toString('hex'); -"result : " + result - -// ruleid:node_sha1 -require("crypto") - .createHash("sha1") - .update("Man oh man do I love node!") - .digest("hex"); - -// ruleid:node_md5 -require("crypto") - .createHash("md5") - .update("Man oh man do I love node!") - .digest("hex"); - -function encrypt(text) { - let iv = crypto.randomBytes(IV_LENGTH); - // ruleid:node_aes_ecb - let cipher = crypto.createCipheriv('aes-256-ecb', Buffer.from(ENCRYPTION_KEY), iv); - // ruleid:node_aes_ecb - let cipher = crypto.createCipheriv('aes-192-ecb', Buffer.from(ENCRYPTION_KEY), iv); - // ruleid:node_aes_ecb - let cipher = crypto.createCipheriv('aes-128-ecb', Buffer.from(ENCRYPTION_KEY), iv); - let encrypted = cipher.update(text); - - encrypted = Buffer.concat([encrypted, cipher.final()]); - - return iv.toString('hex') + ':' + encrypted.toString('hex'); -} - -function decrypt(text) { - let textParts = text.split(':'); - let iv = Buffer.from(textParts.shift(), 'hex'); - let encryptedText = Buffer.from(textParts.join(':'), 'hex'); - // ruleid:node_aes_ecb - let decipher = crypto.createDecipheriv('aes-128-ecb', Buffer.from(ENCRYPTION_KEY), iv); - let decrypted = decipher.update(encryptedText); -} - -// ruleid:node_insecure_random_generator -crypto.pseudoRandomBytes(1); // -//Math based random insecure -// ruleid:node_insecure_random_generator -const val = Math.random(); - -// ruleid:node_weak_crypto -var des = crypto.createCipher('des', key); \ No newline at end of file diff --git a/nodejsscan/crypto_node.yaml b/nodejsscan/crypto_node.yaml deleted file mode 100644 index f595b43..0000000 --- a/nodejsscan/crypto_node.yaml +++ /dev/null @@ -1,72 +0,0 @@ -rules: - - id: node_md5 - patterns: - - pattern: | - $X.createHash("md5") - message: >- - MD5 is a a weak hash which is known to have collision. Use a strong - hashing function. - languages: - - javascript - severity: WARNING - metadata: - owasp: 'A9: Using Components with Known Vulnerabilities' - cwe: 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' - - id: node_sha1 - patterns: - - pattern: | - $X.createHash("sha1") - message: >- - SHA1 is a a weak hash which is known to have collision. Use a strong - hashing function. - languages: - - javascript - severity: WARNING - metadata: - owasp: 'A9: Using Components with Known Vulnerabilities' - cwe: 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' - - id: node_aes_ecb - patterns: - - pattern-either: - - pattern: | - $X.createCipheriv("=~/aes-\([0-9]+\)-ecb/", ...) - - pattern: | - $X.createDecipheriv("=~/aes-\([0-9]+\)-ecb/", ...) - message: >- - AES with ECB mode is deterministic in nature and not suitable for - encrypting large amount of repetitive data. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A9: Using Components with Known Vulnerabilities' - cwe: 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' - - id: node_weak_crypto - patterns: - - pattern-either: - - pattern: | - $X.createCipher('des', ...) - message: >- - A weak or broken cryptographic algorithm was identified. Using these - functions will introduce vulnerabilities or downgrade the security of your application. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A9: Using Components with Known Vulnerabilities' - cwe: 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' - - id: node_insecure_random_generator - patterns: - - pattern-either: - - pattern: | - $X.pseudoRandomBytes(...) - - pattern: | - Math.random(...) - message: >- - crypto.pseudoRandomBytes()/Math.random() is a cryptographically weak random number generator. - languages: - - javascript - severity: WARNING - metadata: - owasp: 'A9: Using Components with Known Vulnerabilities' - cwe: 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' diff --git a/nodejsscan/crypto_timing_attacks.js b/nodejsscan/crypto_timing_attacks.js deleted file mode 100644 index b7b8b0a..0000000 --- a/nodejsscan/crypto_timing_attacks.js +++ /dev/null @@ -1,56 +0,0 @@ -if (name == 'test') { - acces = 1; -} - -// ruleid:node_timing_attack -if (password == 'mypass') { - correct = 1; -} - -// ruleid:node_timing_attack -if ('test' == password) { - correct = 2; -} - -// ruleid:node_timing_attack -if ('test' === password) { - correct = 2; -} - -// ruleid:node_timing_attack -if (password == test) - x = 1; - - - -// https://stackoverflow.com/a/47518578/2927282 -import { pbkdf2Sync, randomBytes } from 'crypto'; - -export class Auth { - iters = 1e1; // TODO: increase later - keylen = 64; - digest = 'sha512'; - - create(password) { - const salt = randomBytes(128).toString('base64'); // <- salt - // salt was not base64 before being used by pbkdf2 - - const hash = pbkdf2Sync(password, salt, this.iters, this.keylen, this.digest).toString('base64'); - - return [salt, hash, this.iters].join('::'); - } - - verify(stored, password) { - const [salt, hash, iters] = stored.split('::'); - const verify = pbkdf2Sync(password, salt, parseInt(iters, 10), this.keylen, this.digest); - - // ruleid:node_timing_attack - return hash === verify.toString('base64'); - } -} - -function isAuthenticated(user, token) { - var correctToken = FetchUserTokenFromDB(user); - // ruleid:node_timing_attack - return token === correctToken; -} \ No newline at end of file diff --git a/nodejsscan/error_disclosure.yaml b/nodejsscan/error_disclosure.yaml deleted file mode 100644 index 846f9da..0000000 --- a/nodejsscan/error_disclosure.yaml +++ /dev/null @@ -1,51 +0,0 @@ -rules: - - id: node_error_disclosure - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $ERR = $ERROR.stack; - ... - $RES.end($ERR); - - pattern: | - $ERR = $ERROR.stack; - ... - $RES.send($ERR); - - pattern: | - $RES.end($ERR.stack) - - pattern: | - $RES.send($ERR.stack) - message: >- - Error messages with stack traces can expose sensitive information about - the application. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A3: Sensitive Data Exposure' - cwe: 'CWE-209: Generation of Error Message Containing Sensitive Information' - - id: generic_error_disclosure - patterns: - - pattern-either: - - pattern: | - console.trace(...) - - pattern: | - try { - ... - } catch($ERR){ - console.error(<... $ERR ...>, ...); - } - message: >- - Error messages with stack traces may expose sensitive information about - the application. - languages: - - javascript - severity: WARNING - metadata: - owasp: 'A3: Sensitive Data Exposure' - cwe: 'CWE-209: Generation of Error Message Containing Sensitive Information' diff --git a/nodejsscan/error_info_disclosure.js b/nodejsscan/error_info_disclosure.js deleted file mode 100644 index f02579c..0000000 --- a/nodejsscan/error_info_disclosure.js +++ /dev/null @@ -1,23 +0,0 @@ -app.get('/', function (req, res) { - try { - foo; - } - catch (err) { - res.statusCode = 500; - res.setHeader("Content-Type", "text/plain"); - // ruleid:node_error_disclosure - res.end(err.stack); - return; - } -}); - - -// ruleid:generic_error_disclosure -try { - throw new Error("Something unexpected has occurred."); -} catch (e) { - console.error(e); -} - -// ruleid:generic_error_disclosure -console.trace("baad") \ No newline at end of file diff --git a/nodejsscan/eval_deserialize.js b/nodejsscan/eval_deserialize.js deleted file mode 100644 index cc9c476..0000000 --- a/nodejsscan/eval_deserialize.js +++ /dev/null @@ -1,31 +0,0 @@ -var express = require('express'); -var cookieParser = require('cookie-parser'); -var escape = require('escape-html'); -var serialize = require('node-serialize'); -const serialize2 = require('serialize-to-js') - - -var app = express(); -app.use(cookieParser()) - -app.get('/', function (req, res) { - if (req.cookies.profile) { - var str = new Buffer(req.cookies.profile, 'base64').toString(); - // ruleid:node_deserialize - var obj = serialize.unserialize(str); - // ruleid:serializetojs_deserialize - serialize2.deserialize(str); - if (obj.username) { - res.send("Hello " + escape(obj.username)); - } - } else { - res.cookie('profile', "eyJ1c2VybmFtZSI6ImFqaW4iLCJjb3VudHJ5IjoiaW5kaWEiLCJjaXR5IjoiYmFuZ2Fsb3JlIn0=", { - maxAge: 900000, - httpOnly: true - }); - } - res.send("Hello World"); -}); -app.listen(3000); -// ruleid:serializetojs_deserialize -require('serialize-to-js').deserialize(str); diff --git a/nodejsscan/eval_deserialize.yaml b/nodejsscan/eval_deserialize.yaml deleted file mode 100644 index 3d13a59..0000000 --- a/nodejsscan/eval_deserialize.yaml +++ /dev/null @@ -1,33 +0,0 @@ -rules: - - id: serializetojs_deserialize - patterns: - - pattern-inside: | - require('serialize-to-js'); - ... - - pattern: | - $X.deserialize(...) - message: >- - User controlled data in 'unserialize()' or 'deserialize()' function can - result in Object Injection or Remote Code Injection. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A8: Insecure Deserialization' - cwe: 'CWE-502: Deserialization of Untrusted Data' - - id: node_deserialize - patterns: - - pattern-inside: | - require('node-serialize'); - ... - - pattern: | - $X.unserialize(...) - message: >- - User controlled data in 'unserialize()' or 'deserialize()' function can - result in Object Injection or Remote Code Injection. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A8: Insecure Deserialization' - cwe: 'CWE-502: Deserialization of Untrusted Data' \ No newline at end of file diff --git a/nodejsscan/eval_node.js b/nodejsscan/eval_node.js deleted file mode 100644 index ef055e3..0000000 --- a/nodejsscan/eval_node.js +++ /dev/null @@ -1,19 +0,0 @@ -var express = require('express'); -var app = express(); -app.get('/', function (req, res) { - // ruleid:eval_nodejs - var resp = eval("(" + req.query.name + ")"); - // ruleid:eval_nodejs - var z = new Function('arg1', 'arg2', req.query.name) - z(1, 2); - // ruleid:eval_nodejs - setTimeout('alert(' + req.body.name, 0); - // ruleid:eval_nodejs - setInterval(req.body.name, 0); - res.send('Response
'); -}); -app.listen(8000); -eval("outside_express" + req.foo) -setTimeout('alert(' + req.body.name, 0); -setInterval(req.body.name, 0); -new Function('arg1', 'arg2', req.query.name) \ No newline at end of file diff --git a/nodejsscan/eval_node.yaml b/nodejsscan/eval_node.yaml deleted file mode 100644 index 375450a..0000000 --- a/nodejsscan/eval_node.yaml +++ /dev/null @@ -1,69 +0,0 @@ -rules: - - id: eval_nodejs - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - new Function(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - new Function(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - eval(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - eval(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - setTimeout(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - setTimeout(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - setInterval(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - setInterval(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - new Function(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - new Function(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - eval(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - eval(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - setTimeout(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - setTimeout(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - setInterval(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - setInterval(..., <... $INP ...>, ...); - message: >- - User controlled data in eval() or similar functions may result in Server - Side Injection or Remote Code Injection - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: >- - CWE-95: Improper Neutralization of Directives in Dynamically Evaluated - Code ('Eval Injection') \ No newline at end of file diff --git a/nodejsscan/eval_yaml_deserialize.js b/nodejsscan/eval_yaml_deserialize.js deleted file mode 100644 index b816a69..0000000 --- a/nodejsscan/eval_yaml_deserialize.js +++ /dev/null @@ -1,14 +0,0 @@ -var untrusted_code = '"toString": ! "function (){very_evil_thing();}"'; -var notneeded = 1; -// I'm just converting that string, what could possibly go wrong? -// ruleid:yaml_deserialize -require('js-yaml').load(untrusted_code) + '' - -var yaml = require('js-yaml') - -const yaml2 = require('js-yaml') - -// ruleid:yaml_deserialize -yaml.load(untrusted_code) -// ruleid:yaml_deserialize -yaml2.load(untrusted_code) \ No newline at end of file diff --git a/nodejsscan/eval_yaml_deserialize.yaml b/nodejsscan/eval_yaml_deserialize.yaml deleted file mode 100644 index 5ef6b08..0000000 --- a/nodejsscan/eval_yaml_deserialize.yaml +++ /dev/null @@ -1,17 +0,0 @@ -rules: - - id: yaml_deserialize - patterns: - - pattern-inside: | - require('js-yaml'); - ... - - pattern: | - $X.load(...) - message: >- - User controlled data in 'yaml.load()' function can result in Remote Code - Injection. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A8: Insecure Deserialization' - cwe: 'CWE-502: Deserialization of Untrusted Data' \ No newline at end of file diff --git a/nodejsscan/exec_os_command.js b/nodejsscan/exec_os_command.js deleted file mode 100644 index 5dc7b36..0000000 --- a/nodejsscan/exec_os_command.js +++ /dev/null @@ -1,118 +0,0 @@ - -const { exec, spawn } = require('child_process'); - - -router.post('/ping', (req, res) => { - // ruleid:generic_os_command_exec2 - exec(`${req.body.url}`, (error) => { - if (error) { - return res.send('error'); - } - res.send('pong') - }) - -}) - -router.post('/gzip', (req, res) => { - // ruleid:generic_os_command_exec2 - exec( - 'gzip ' + req.query.file_path, - function (err, data) { - console.log('err: ', err) - console.log('data: ', data); - res.send('done'); - }); -}) - -var child_process = require('child_process'); -var x = 1; -app.get('/', function (req, res) { - // ruleid:generic_os_command_exec - child_process.exec( - req.query.file_path, - function (err, data) { - console.log('err: ', err) - console.log('data: ', data); - }); - - // ruleid:generic_os_command_exec - child_process.exec('gzip' + - req.query.file_path, - function (err, data) { - console.log('err: ', err) - console.log('data: ', data); - }); - - // ruleid:generic_os_command_exec - child_process.exec('foobar' + - req.query.file_path + "asdD", - function (err, data) { - console.log('err: ', err) - console.log('data: ', data); - }); - - // ruleid:generic_os_command_exec - child_process.exec( - req.query.file_path + "asdD", - function (err, data) { - console.log('err: ', err) - console.log('data: ', data); - }); - - //Do not detect this - child_process.exec( - foo + "asdD", - function (err, data) { - console.log('err: ', err) - console.log('data: ', data); - }); - - // ruleid:generic_os_command_exec - child_process.execSync( - req.query.file_path + 'rsync -avAXz --info=progress2 "/src" "/dest"', - { stdio: 'inherit' }); - - res.send('Hello World!') - - - // ruleid:generic_os_command_exec - var foo = req.query.ping; - var x; - child_process.exec('ping -c 2 ' + foo, function (err, data) { - response.end(); - }); -}) - -var foo = '1'; -require('child_process').exec(foo + 'info=progress2 "/src" "/dest"'); - - -const router = require('express').Router(); -const exe = require('child_process'); - -router.post('/', function (req, res) { - // ruleid:generic_os_command_exec - exe.exec('ls ' + req.body.dir, function (err, data) { - if (!err) { - res.json({ message: data }); - } else { - res.status(500).json({ message: err }); - } - }); -}); - -module.exports = router; - - -var http = require("http"); -var url = require("url"); -var exe = require('child_process'); -http.createServer(function (request, response) { - // ruleid:generic_os_command_exec - var parsedUrl = url.parse(request.url, true); - exe.exec('ping -c 2 ' + parsedUrl.query.ping, function (err, data) { - response.end(); - }); - -}).listen(8888); - diff --git a/nodejsscan/exec_os_command.yaml b/nodejsscan/exec_os_command.yaml deleted file mode 100644 index b97b7b8..0000000 --- a/nodejsscan/exec_os_command.yaml +++ /dev/null @@ -1,91 +0,0 @@ -rules: - - id: generic_os_command_exec - patterns: - - pattern-inside: | - $EXEC = require('child_process'); - ... - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $EXEC.exec(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - $EXEC.exec(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - $EXEC.execSync(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - $EXEC.execSync(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - $EXEC.exec(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - $EXEC.exec(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - $EXEC.execSync(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - $EXEC.execSync(..., <... $INP ...>, ...); - message: >- - User controlled data in 'child_process.exec()' can result in Remote OS - Command Execution. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: >- - CWE-78: Improper Neutralization of Special Elements used in an OS - Command ('OS Command Injection') - - id: generic_os_command_exec2 - patterns: - - pattern-inside: | - var {$EXEC} = require('child_process'); - ... - - pattern-inside: | - $APP.$METHOD(..., function $FUNC($REQ, $RES, ...){ ... }); - - pattern-either: - - pattern: | - exec(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - exec(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - execSync(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - execSync(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - exec(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - exec(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - execSync(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - execSync(..., <... $INP ...>, ...); - message: >- - User controlled data in 'child_process.exec()' can result in Remote OS - Command Execution. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: >- - CWE-78: Improper Neutralization of Special Elements used in an OS - Command ('OS Command Injection') diff --git a/nodejsscan/express_bodyparser_dos.js b/nodejsscan/express_bodyparser_dos.js deleted file mode 100644 index 473b997..0000000 --- a/nodejsscan/express_bodyparser_dos.js +++ /dev/null @@ -1,16 +0,0 @@ -const express = require('express') - , cors = require('cors') - , bodyParser = require('body-parser'); - -var app = express(); - -app.configure(function () { - app.set('port', process.env.PORT || 3000); - app.set('views', __dirname + '/views'); - app.set('view engine', 'jade'); - app.use(express.favicon()); - app.use(express.logger('dev')); - // ruleid:express_bodyparser - app.use(express.bodyParser()); - app.use(cors()); -}); \ No newline at end of file diff --git a/nodejsscan/express_bodyparser_dos.yaml b/nodejsscan/express_bodyparser_dos.yaml deleted file mode 100644 index 22c223e..0000000 --- a/nodejsscan/express_bodyparser_dos.yaml +++ /dev/null @@ -1,19 +0,0 @@ -rules: - - id: express_bodyparser - patterns: - - pattern-inside: - $APP = express(); - ... - - pattern-inside: | - $APP.use(...); - - pattern: - $X.bodyParser(...) - message: >- - POST Request to Express Body Parser 'bodyParser()' can create Temporary - files and consume space. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A9: Using Components with Known Vulnerabilities' - cwe: 'CWE-400: Uncontrolled Resource Consumption' diff --git a/nodejsscan/good_anti_csrf.yaml b/nodejsscan/good_anti_csrf.yaml deleted file mode 100644 index 23d57c3..0000000 --- a/nodejsscan/good_anti_csrf.yaml +++ /dev/null @@ -1,19 +0,0 @@ -rules: - - id: anti_csrf_control - patterns: - - pattern-inside: | - $CSRUF = require('csurf'); - ... - - pattern-either: - - pattern: - $X = csrf(...); - - pattern: - $X = csurf(...); - - pattern: - $APP.use(csrf(...)); - - pattern: - $APP.use(csurf(...)); - message: 'This application has anti CSRF protection which prevents cross site request forgery attacks.' - languages: - - javascript - severity: WARNING \ No newline at end of file diff --git a/nodejsscan/good_helmet_checks.yaml b/nodejsscan/good_helmet_checks.yaml deleted file mode 100644 index bbd9a4e..0000000 --- a/nodejsscan/good_helmet_checks.yaml +++ /dev/null @@ -1,236 +0,0 @@ -# Convert this to INFO when semgrep supports that -rules: - - id: helmet_header_check_csp - message: >- - Content Security Policy header is present. More Information: - https://helmetjs.github.io/docs/csp/ - languages: - - javascript - severity: WARNING - patterns: - - pattern-not: | - $HELMET(..., {contentSecurityPolicy: false}, ...) - - pattern-either: - - pattern: | - helmet({contentSecurityPolicy: {directives: ...}}) - - pattern: | - helmet.contentSecurityPolicy({directives: ...}) - - pattern: | - csp({directives: ...}) - - id: helmet_header_check_crossdomain - message: >- - X-Permitted-Cross-Domain-Policies header set to off. More information: - https://helmetjs.github.io/docs/crossdomain/ - languages: - - javascript - severity: WARNING - patterns: - - pattern-not: | - $HELMET(..., {permittedCrossDomainPolicies: false}, ...) - - pattern-either: - - pattern: | - permittedCrossDomainPolicies() - - pattern: | - permittedCrossDomainPolicies({ permittedPolicies: ... }) - - pattern: | - helmet.permittedCrossDomainPolicies({ permittedPolicies: ... }) - - pattern: | - helmet({permittedCrossDomainPolicies: { permittedPolicies: ... }}) - - pattern: | - helmet.permittedCrossDomainPolicies() - - id: helmet_header_check_expect_ct - message: >- - Expect-CT header is present. More information: - https://helmetjs.github.io/docs/expect-ct/ - languages: - - javascript - severity: WARNING - patterns: - - pattern-not: | - $HELMET(..., {expectCt: false}, ...) - - pattern-either: - - pattern: | - expectCt({maxAge: ...,}) - - pattern: | - helmet.expectCt({maxAge: ...,}) - - pattern: | - expectCt({enforce: ...,}) - - pattern: | - hemlet.expectCt({enforce: ...,}) - - pattern: | - helmet({expectCt: { enforce: ... }}) - - id: helmet_header_feature_policy - message: >- - Feature-Policy header is present. More information: - https://helmetjs.github.io/docs/feature-policy/ - languages: - - javascript - severity: WARNING - patterns: - - pattern-not: | - $HELMET(..., {featurePolicy: false}, ...) - - pattern-either: - - pattern: | - featurePolicy(..., {features: ...}, ...) - - pattern: | - helmet.featurePolicy(..., {features: ...}, ...) - - pattern: | - helmet({featurePolicy: {features: ...}}) - - id: helmet_header_frame_guard - message: >- - X-Frame-Options header is present. More information: - https://helmetjs.github.io/docs/frameguard/ - languages: - - javascript - severity: WARNING - patterns: - - pattern-not: | - $HELMET(..., {frameguard: false}, ...) - - pattern-either: - - pattern: | - $APP.use(hemlet()) - - pattern: | - helmet.frameguard(...) - - pattern: | - frameguard(...) - - pattern: | - helmet({frameguard: ...}) - - id: helmet_header_dns_prefetch - message: >- - X-DNS-Prefetch-Control header is present and DNS Prefetch Control is - enabled. More information: - https://helmetjs.github.io/docs/dns-prefetch-control/ - languages: - - javascript - severity: WARNING - patterns: - - pattern-not: | - $HELMET(..., {dnsPrefetchControl: false}, ...) - - pattern-either: - - pattern: | - $APP.use(helmet()) - - pattern: | - helmet.dnsPrefetchControl() - - pattern: | - dnsPrefetchControl() - - pattern: | - helmet.dnsPrefetchControl({ allow: false }) - - pattern: | - helmet({dnsPrefetchControl: {allow: false}}) - - pattern: | - dnsPrefetchControl({ allow: false }) - - id: helmet_header_x_powered_by - message: >- - Default X-Powered-By is removed or modified. More information: - https://helmetjs.github.io/docs/hide-powered-by/ - languages: - - javascript - severity: WARNING - patterns: - - pattern-not: | - $HELMET(..., {hidePoweredBy: false}, ...) - - pattern-either: - - pattern: | - $APP.use(helmet()) - - pattern: | - app.disable('x-powered-by') - - pattern: | - helmet.hidePoweredBy(...) - - pattern: | - hidePoweredBy(...) - - pattern: | - helmet({hidePoweredBy: ...}) - - id: helmet_header_hsts - message: >- - HSTS header is present. More information: - https://helmetjs.github.io/docs/hsts/ - languages: - - javascript - severity: WARNING - patterns: - - pattern-not: | - $HELMET(..., {hsts: false}, ...) - - pattern-either: - - pattern: | - $APP.use(helmet()) - - pattern: | - helmet.hsts(...) - - pattern: | - hsts({ maxAge: ...}) - - pattern: | - helmet({hsts: ...}) - - id: helmet_header_ienoopen - message: >- - X-Download-Options header is present. More information: - https://helmetjs.github.io/docs/ienoopen/ - languages: - - javascript - severity: WARNING - patterns: - - pattern-not: | - $HELMET(..., {ieNoOpen: false}, ...) - - pattern-either: - - pattern: | - $APP.use(helmet()) - - pattern: | - helmet.ieNoOpen() - - pattern: | - ieNoOpen() - - pattern: | - helmet({ieNoOpen: ...}) - - id: helmet_header_nosniff - message: >- - Content-Type-Options header is present. More information: - https://helmetjs.github.io/docs/dont-sniff-mimetype/ - languages: - - javascript - severity: WARNING - patterns: - - pattern-not: | - $HELMET(..., {noSniff: false}, ...) - - pattern-either: - - pattern: | - $APP.use(helmet()) - - pattern: | - helmet.noSniff() - - pattern: | - noSniff() - - pattern: | - helmet({noSniff: ...}) - - id: helmet_header_referrer_policy - message: >- - Referrer-Policy header is present. More information: - https://helmetjs.github.io/docs/referrer-policy/ - languages: - - javascript - severity: WARNING - patterns: - - pattern-not: | - $HELMET(..., {referrerPolicy: false}, ...) - - pattern-either: - - pattern: | - helmet.referrerPolicy(...) - - pattern: | - referrerPolicy(...) - - pattern: | - helmet({referrerPolicy: ...}) - - id: helmet_header_xss_filter - message: >- - X-XSS-Protection header is present. More information: - https://helmetjs.github.io/docs/xss-filter/ - languages: - - javascript - severity: WARNING - patterns: - - pattern-not: | - $HELMET(..., {xssFilter: false}, ...) - - pattern-either: - - pattern: | - $APP.use(helmet()) - - pattern: | - helmet.xssFilter(...) - - pattern: | - xssFilter(...) - - pattern: | - helmet({xssFilter: ...}) - diff --git a/nodejsscan/good_ratelimiting.yaml b/nodejsscan/good_ratelimiting.yaml deleted file mode 100644 index aba6c0f..0000000 --- a/nodejsscan/good_ratelimiting.yaml +++ /dev/null @@ -1,14 +0,0 @@ -rules: - - id: rate_limit_control - patterns: - - pattern-either: - - pattern: - require("express-rate-limit"); - - pattern: - require("express-limiter"); - - pattern: - require("@authentication/rate-limit"); - message: 'This application has API rate limiting controls.' - languages: - - javascript - severity: WARNING diff --git a/nodejsscan/hardcoded_jwt.js b/nodejsscan/hardcoded_jwt.js deleted file mode 100644 index 2a987c9..0000000 --- a/nodejsscan/hardcoded_jwt.js +++ /dev/null @@ -1,119 +0,0 @@ -// ruleid:hardcoded_jwt_secret -const jsonwt = require('jsonwebtoken') -const jose = require('jose') -const { JWK, JWT } = jose -const config = require('./config') - -const payload = { foo: 'bar' } -const secret = 'shhhhh' - -const secret2 = config.secret -const secret3 = process.env.SECRET || 'fallback-secret' - -//jsonwebtoken -//true -const token1 = jsonwt.sign(payload, secret) -//true -const token2 = jsonwt.sign(payload, 'some-secret') - -//?? -const token5 = jsonwt.sign(payload, secret3) - -//jose -//true -const token6 = JWT.sign(payload, JWK.asKey(secret)) -//true -const token7 = JWT.sign(payload, JWK.asKey('raz-dva-tri')) -//true -const token8 = JWT.sign(payload, secret) -//true -const token9 = JWT.sign(payload, 'secret-again') - - - -// ruleid:hardcoded_jwt_secret -const $jwt = require('jsonwebtoken'); - -const cert = 'hardcoded-secret'; - -module.exports = (app) => { - app.post('/api/login', (req, res) => { - app.login(req.body.username, req.body.password).then((out) => { - out.token = $jwt.sign(out, cert, { expiresIn: '1d' }); - res.send(out); - }, (err) => { - console.error(err); - res.status(400).send(err); - }); - }); -}; - -// ruleid:hardcoded_jwt_secret -const jwt = require('jsonwebtoken') - -const jwtSign = (payload = { id: 1 }) => - jwt.sign(payload, 'hardcoded-secret') - -const jwtVerify = req => () => new Promise((resolve, reject) => { - const token = req.headers['x-access-token'] - if (!token) { - resolve(false) - } - jwt.verify(token, 'hardcoded-secret', (err, decoded) => { - if (err) { - resolve(false) - } - resolve(decoded) - }) -}) - -export default { jwtSign, jwtVerify } - (() => { - - 'use strict'; - - // ruleid:hardcoded_jwt_secret - let User = require('./user'), - jwt = require('jsonwebtoken'); - - const express = require('express'); - let router = express.Router(); - - router.post('/signup', (req, res) => { - let user = new User({ - name: req.body.name, - password: req.body.password - }); - var token = jwt.sign(user, "hardcoded-secret", { expiresIn: 60 * 60 * 10 }); - res.send({ success: true, token: token }); - }); - - module.exports = router; - })(); - -'use strict'; -const config = require('./app.config'); -const privateMethods = { - initialize(USER) { - // ruleid:hardcoded_jwt_secret - const router = require('express').Router(), - jwt = require('jsonwebtoken'); - if (config) { - router.route('/register').post((req, res) => { - USER.findOne({}).exec((error, user) => { - if (error) - return res.status(400).send({ error: error }); - user.save((error, user) => { - if (error) { - return res.status(400).send({ error: error }); - } else { - const token = jwt.sign({ id: user._id }, 'hardcoded-secret'); - return res.status(201).json({ token: token }); - } - }); - }); - }); - } - } -}; -module.exports = privateMethods; \ No newline at end of file diff --git a/nodejsscan/hardcoded_jwt.yaml b/nodejsscan/hardcoded_jwt.yaml deleted file mode 100644 index f398378..0000000 --- a/nodejsscan/hardcoded_jwt.yaml +++ /dev/null @@ -1,73 +0,0 @@ -# Rule is from https://github.com/returntocorp/semgrep-rules/blob/develop/javascript/jwt-hardcode/jwt-hardcode.yaml -# module imports used as described in https://github.com/returntocorp/semgrep/issues/285 -rules: - - id: hardcoded_jwt_secret - patterns: - - pattern-either: - - pattern: | - $JWT = require("jsonwebtoken"); - ... - $JWT.sign($P, "...", ...); - - pattern: | - $JWT = require("jsonwebtoken"); - ... - $JWT.verify($P, "...", ...); - - pattern: | - $JWT = require("jsonwebtoken"); - ... - $SECRET = "..."; - ... - $JWT.sign($P, $SECRET, ...); - - pattern: | - $JWT = require("jsonwebtoken"); - ... - $SECRET = "..."; - ... - $JWT.verify($P, $SECRET, ...); - - pattern: | - $JOSE = require("jose"); - ... - $JOSE.JWT.sign($P, "...", ...); - - pattern: | - $JOSE = require("jose"); - ... - $JOSE.JWT.verify($P, "...", ...); - - pattern: | - $JOSE = require("jose"); - ... - $JOSE.JWT.sign($P, $JOSE.JWK.asKey("..."), ...); - - pattern: | - $JOSE = require("jose"); - ... - $JOSE.JWT.verify($P, $JOSE.JWK.asKey("..."), ...); - - pattern: | - $JOSE = require("jose"); - ... - $SECRET = "..."; - ... - $JOSE.JWT.sign($P, $SECRET, ...); - - pattern: | - $JOSE = require("jose"); - ... - $SECRET = "..."; - ... - $JOSE.JWT.verify($P, $SECRET, ...); - - pattern: | - $JOSE = require("jose"); - ... - $SECRET = "..."; - ... - $JOSE.JWT.sign($P, $JOSE.JWK.asKey($SECRET), ...); - - pattern: | - $JOSE = require("jose"); - ... - $SECRET = "..."; - ... - $JOSE.JWT.verify($P, $JOSE.JWK.asKey($SECRET), ...); - message: Hardcoded JWT secret was found - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A3: Sensitive Data Exposure' - cwe: 'CWE-798: Use of Hard-coded Credentials' diff --git a/nodejsscan/hardcoded_secrets.js b/nodejsscan/hardcoded_secrets.js deleted file mode 100644 index 03cf09a..0000000 --- a/nodejsscan/hardcoded_secrets.js +++ /dev/null @@ -1,28 +0,0 @@ -// ruleid:node_password -password = '1212'; -x = 1; -password = x; -pass = 123; -// ruleid:node_password -PASSWORD = '12211'; - -// ruleid:node_password -obj['password'] = '121233'; -// ruleid:node_password -obj2.password = '1234'; -// ruleid:node_password -obj2.pass = '1234'; -// ruleid:node_password -obj2["pass"] = '1234'; - -// ruleid:node_password -const password = '1212'; -// ruleid:node_password -let password = '1212'; -// ruleid:node_password -var password = '1212'; - -// ruleid:node_api_key -angular.module('starter.services', []) - .constant('api_key', '6e906986c3b199c51fff3154cfb76979') -this.apiUrl = api_url; \ No newline at end of file diff --git a/nodejsscan/hardcoded_secrets.yaml b/nodejsscan/hardcoded_secrets.yaml deleted file mode 100644 index 709e90e..0000000 --- a/nodejsscan/hardcoded_secrets.yaml +++ /dev/null @@ -1,185 +0,0 @@ -rules: - - id: node_password - patterns: - - pattern-not: password = '' - - pattern-not: PASSWORD = '' - - pattern-not: PASS = '' - - pattern-not: pass = '' - - pattern-not: $X[...] = '' - - pattern-either: - - pattern: | - password = '...'; - - pattern: | - PASSWORD = '...'; - - pattern: | - PASS = '...'; - - pattern: | - pass = '...'; - - pattern: | - $X['pass'] = '...'; - - pattern: | - $X['password'] = '...'; - - pattern: | - $X['PASS'] = '...'; - - pattern: | - $X['PASSWORD'] = '...'; - - pattern: | - $X.pass = '...'; - - pattern: | - $X.password = '...'; - - pattern: | - $X.PASS = '...'; - - pattern: | - $X.PASSWORD = '...'; - message: >- - A hardcoded password in plain text is identified. Store it properly in an - environment variable. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A3: Sensitive Data Exposure' - cwe: 'CWE-798: Use of Hard-coded Credentials' - - id: node_secret - patterns: - - pattern-not: secret = '' - - pattern-not: SECRET = '' - - pattern-not: api_secret = '' - - pattern-not: API_SECRET = '' - - pattern-not: $X['...'] = '' - - pattern-either: - - pattern: | - secret = '...'; - - pattern: | - SECRET = '...'; - - pattern: | - api_secret = '...'; - - pattern: | - API_SECRET = '...'; - - pattern: | - $X['secret'] = '...'; - - pattern: | - $X['SECRET'] = '...'; - - pattern: | - $X['api_secret'] = '...'; - - pattern: | - $X['apiSecret'] = '...'; - - pattern: | - $X['API_SECRET'] = '...'; - - pattern: | - $X.secret = '...'; - - pattern: | - $X.SECRET = '...'; - - pattern: | - $X.api_secret = '...'; - - pattern: | - $X.apiSecret = '...'; - - pattern: | - $X.API_SECRET = '...'; - - pattern: | - $X('api_secret', '...') - - pattern: | - $X('apiSecret', '...') - - pattern: | - $X('API_SECRET', '...') - - pattern: | - $X('secret', '...') - - pattern: | - $X('SECRET', '...') - message: >- - A hardcoded secret is identified. Store it properly in an - environment variable. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A3: Sensitive Data Exposure' - cwe: 'CWE-798: Use of Hard-coded Credentials' - - id: node_username - patterns: - - pattern-not: username = '' - - pattern-not: userName = '' - - pattern-not: USERNAME = '' - - pattern-not: user = '' - - pattern-not: USER = '' - - pattern-not: $X['...'] = '' - - pattern-either: - - pattern: | - username = '...'; - - pattern: | - userName = '...'; - - pattern: | - USERNAME = '...'; - - pattern: | - user = '...'; - - pattern: | - USER = '...'; - - pattern: | - $X['username'] = '...'; - - pattern: | - $X['userName'] = '...'; - - pattern: | - $X['USERNAME'] = '...'; - - pattern: | - $X['user'] = '...'; - - pattern: | - $X['USER'] = '...'; - - pattern: | - $X.username = '...'; - - pattern: | - $X.userName = '...'; - - pattern: | - $X.USERNAME = '...'; - - pattern: | - $X.user = '...'; - - pattern: | - $X.USER = '...'; - message: >- - A hardcoded username in plain text is identified. Store it properly in an - environment variable. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A3: Sensitive Data Exposure' - cwe: 'CWE-798: Use of Hard-coded Credentials' - - id: node_api_key - patterns: - - pattern-not: api_key = '' - - pattern-not: apiKey = '' - - pattern-not: API_KEY = '' - - pattern-not: $X['...'] = '' - - pattern-either: - - pattern: | - api_key = '...'; - - pattern: | - apiKey = '...'; - - pattern: | - API_KEY = '...'; - - pattern: | - $X['api_key'] = '...'; - - pattern: | - $X['apiKey'] = '...'; - - pattern: | - $X['API_KEY'] = '...'; - - pattern: | - $X.api_key = '...'; - - pattern: | - $X.apiKey = '...'; - - pattern: | - $X.API_KEY = '...'; - - pattern: | - $X('api_key', '...') - - pattern: | - $X('apiKey', '...') - - pattern: | - $X('API_KEY', '...') - message: >- - A hardcoded API Key is identified. Store it properly in an - environment variable. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A3: Sensitive Data Exposure' - cwe: 'CWE-798: Use of Hard-coded Credentials' diff --git a/nodejsscan/header_cors_star.js b/nodejsscan/header_cors_star.js deleted file mode 100644 index b4d81bd..0000000 --- a/nodejsscan/header_cors_star.js +++ /dev/null @@ -1,41 +0,0 @@ -const express = require('express'); - -const app = express(); - -// ruleid:generic_cors -app.options('*', cors()) -app.get('/', function (req, res) { - - res.set(ffff) -}); - -app.get('/', function (req, res) { - var y = 1; - // ruleid:express_cors - var x = '*'; - //sgrep bug - https://github.com/returntocorp/sgrep/issues/512 - // ruleid:express_cors - res.writeHead(200, { 'Access-Control-Allow-Origin': '*' }); - // ruleid:express_cors - res.set('access-control-allow-origin', '*'); - //do not match - sgrep bug -rewrite-rule - res.set('Access-Control-Allow-Origin', 'google.com'); - // ruleid:express_cors - res.set('Access-Control-Allow-Origin', '*'); - // ruleid:express_cors - res.set({ - 'Content-Length': 123, - 'access-control-allow-origin': '*', - 'ETag': '12345' - }) - // ruleid:express_cors - res.writeHead(200, { 'Access-Control-Allow-Origin': '*' }) - - res.set('access-control-allow-origin', x); - - // do not detect - sgrep bug - res.set('access-control-allow-origin', 'xyz.com'); - //do not detect - sgrep bug - res.set('access-control-allow-origin', null); - -}); \ No newline at end of file diff --git a/nodejsscan/header_cors_star.yaml b/nodejsscan/header_cors_star.yaml deleted file mode 100644 index 6fa49b9..0000000 --- a/nodejsscan/header_cors_star.yaml +++ /dev/null @@ -1,71 +0,0 @@ -# Need QA + sgrep bug fix + false positive in generic_2 and {"=~/[Access-Control-Allow-Origin|access-control-allow-origin]/": '*' } not working -rules: - - id: generic_cors - patterns: - - pattern: | - $APP.options('*', cors(...)) - message: >- - Access-Control-Allow-Origin response header is set to "*". This will - disable CORS Same Origin Policy restrictions. - languages: - - javascript - severity: WARNING - metadata: - owasp: 'A6: Security Misconfiguration' - cwe: 'CWE-346: Origin Validation Error' - - id: express_cors - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $APP.options('*', cors(...)) - - pattern: > - $RES.set("=~/[Access-Control-Allow-Origin|access-control-allow-origin]/", - '*', ...) - - pattern: > - $RES.set(..., { - "=~/[Access-Control-Allow-Origin|access-control-allow-origin]/" : - '*' }, ...) - - pattern: > - $RES.header("=~/[Access-Control-Allow-Origin|access-control-allow-origin]/", - '*', ...) - - pattern: > - $RES.writeHead(..., - {"=~/[Access-Control-Allow-Origin|access-control-allow-origin]/": - '*' }, ...); - - pattern: > - $VAL = '*'; - ... - $RES.set("=~/[Access-Control-Allow-Origin|access-control-allow-origin]/", - $VAL, ...); - - pattern: > - $VAL = '*'; - ... - $RES.set(..., { - "=~/[Access-Control-Allow-Origin|access-control-allow-origin]/" : - $VAL }, ...); - - pattern: > - $VAL = '*'; - ... - $RES.header("=~/[Access-Control-Allow-Origin|access-control-allow-origin]/", - $VAL, ...); - - pattern: > - $VAL = '*'; - ... - $RES.writeHead(..., - {"=~/[Access-Control-Allow-Origin|access-control-allow-origin]/": - $VAL }, ...); - message: >- - Access-Control-Allow-Origin response header is set to "*". This will - disable CORS Same Origin Policy restrictions. - languages: - - javascript - severity: WARNING - metadata: - owasp: 'A6: Security Misconfiguration' - cwe: 'CWE-346: Origin Validation Error' diff --git a/nodejsscan/header_helmet_disabled.js b/nodejsscan/header_helmet_disabled.js deleted file mode 100644 index 8ca4c9a..0000000 --- a/nodejsscan/header_helmet_disabled.js +++ /dev/null @@ -1,10 +0,0 @@ -// ruleid:helmet_feature_disabled -app.use(helmet({ - frameguard: false, -})) - - -// ruleid:helmet_feature_disabled -app.use(helmet({ - "xssFilter": false -})) \ No newline at end of file diff --git a/nodejsscan/header_helmet_disabled.yaml b/nodejsscan/header_helmet_disabled.yaml deleted file mode 100644 index 0f2b67a..0000000 --- a/nodejsscan/header_helmet_disabled.yaml +++ /dev/null @@ -1,36 +0,0 @@ -rules: - - id: helmet_feature_disabled - patterns: - - pattern-either: - - pattern: | - $HELMET(..., {frameguard: false}, ...) - - pattern: | - $HELMET(..., {contentSecurityPolicy: false}, ...) - - pattern: | - $HELMET(..., {permittedCrossDomainPolicies: false}, ...) - - pattern: | - $HELMET(..., {dnsPrefetchControl: false}, ...) - - pattern: | - $HELMET(..., {expectCt: false}, ...) - - pattern: | - $HELMET(..., {featurePolicy: false}, ...) - - pattern: | - $HELMET(..., {hsts: false}, ...) - - pattern: | - $HELMET(..., {ieNoOpen: false}, ...) - - pattern: | - $HELMET(..., {noSniff: false}, ...) - - pattern: | - $HELMET(..., {hidePoweredBy: false}, ...) - - pattern: | - $HELMET(..., {referrerPolicy: false}, ...) - - pattern: | - $HELMET(..., {xssFilter: false}, ...) - message: >- - One or more Security Response header is explicitly disabled in Helmet. - languages: - - javascript - severity: WARNING - metadata: - owasp: 'A6: Security Misconfiguration' - cwe: 'CWE-693: Protection Mechanism Failure' \ No newline at end of file diff --git a/nodejsscan/header_injection.js b/nodejsscan/header_injection.js deleted file mode 100644 index 401398e..0000000 --- a/nodejsscan/header_injection.js +++ /dev/null @@ -1,63 +0,0 @@ -var server = http.createServer(function (req, res) { - var bla = 'dsdsd'; - switch (testIndex++) { - case 0: - // ruleid:generic_header_injection - res.writeHead(200, { test: 'foo \r\ninvalid: bar' + req.foo }); - break; - case 1: - // ruleid:generic_header_injection - res.writeHead(200, { test: req.foo + 'foo \ninvalid: bar' }); - break; - case 2: - // ruleid:generic_header_injection - res.writeHead(200, { test: 'foo \rinvalid: bar' + req.foo + 'asdadasd', foo: bar }); - break; - case 3: - // ruleid:generic_header_injection - res.writeHead(200, { test: bla + 'foo \n\n\ninvalid: bar' + req.foo }); - break; - case 5: - // ruleid:generic_header_injection - res.writeHead(200, { test: bla + 'foo \n\n\ninvalid: bar' + req.foo('asd') }); - break; - case 4: - // ruleid:generic_header_injection - res.writeHead(200, { test: req.foo }); - server.close(); - break; - default: - assert(false); - } - res.end('Hi mars!'); -}); -server.listen(common.PORT); - -var express = require('express'); -var app = express(); -app.get('/', function (req, res) { - // ruleid:generic_header_injection - res.writeHead(200, { test: 'foo \r\ninvalid: bar' + req.foo }); - - // ruleid:generic_header_injection - res.set('Content-Type', req.query.foo); - // ruleid:generic_header_injection - res.set('foo', 'asdad' + req.query.foo); - // ruleid:generic_header_injection - res.set(req.query.foo, 'asdadad'); - // ruleid:generic_header_injection - res.set('asda' + req.query.foo, 'asdadad'); - // ruleid:generic_header_injection - res.set('asda' + req.query["foo"], 'asdadad'); - // ruleid:generic_header_injection - res.set('asda' + req.query("foo"), 'asdadad'); - // ruleid:generic_header_injection - res.set({ - 'Content-Type': 'text/plain', - 'Content-Length': req.query.foo, - 'ETag': '12345' - }) - //do not detect - res.writeHead(200, { tast: ddd }) - res.set(ffff) -}); diff --git a/nodejsscan/header_injection.yaml b/nodejsscan/header_injection.yaml deleted file mode 100644 index 0e64f4f..0000000 --- a/nodejsscan/header_injection.yaml +++ /dev/null @@ -1,55 +0,0 @@ -rules: - - id: generic_header_injection - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $INP = $REQ.$QUERY; - ... - $RES.set(..., <... $INP ...>, ...); - - pattern: | - $INP = $REQ.$QUERY.$VAR; - ... - $RES.set(..., <... $INP ...>, ...); - - pattern: | - $INP = $REQ.$VAR; - ... - $RES.set(..., { $X: <... $INP ...>}, ...); - - pattern: | - $INP = $REQ.$QUERY.$FOO; - ... - $RES.set(..., { $X: <... $INP ...>}, ...); - - pattern: | - $INP = $REQ.$VAR; - ... - $RES.writeHead(..., { $X: <... $INP ...> }, ...); - - pattern: | - $INP = $REQ.$QUERY.$FOO; - ... - $RES.writeHead(..., { $X: <... $INP ...> }, ...); - - pattern: | - $RES.set(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - $RES.set(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - $RES.set(..., { $X: <... $REQ.$VAR ...>}, ...) - - pattern: | - $RES.set(..., { $X: <... $REQ.$QUERY.$FOO ...>}, ...); - - pattern: | - $RES.writeHead(..., { $X: <... $REQ.$VAR ...> }, ...); - - pattern: | - $RES.writeHead(..., { $X: <... $REQ.$QUERY.$FOO ...> }, ...); - message: >- - Untrusted user input in response header will result in HTTP Header - Injection or Response Splitting Attacks. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: 'CWE-644: Improper Neutralization of HTTP Headers for Scripting Syntax' diff --git a/nodejsscan/header_xss_protection.js b/nodejsscan/header_xss_protection.js deleted file mode 100644 index 4155fae..0000000 --- a/nodejsscan/header_xss_protection.js +++ /dev/null @@ -1,54 +0,0 @@ -const express = require('express'); -const lusca = require('lusca'); - -const app = express(); - -// ruleid:header_xss_lusca -app.use(lusca({ - csrf: true, - csp: { policy: "referrer no-referrer" }, - xframe: 'SAMEORIGIN', - p3p: 'ABCDEF', - hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }, - xssProtection: false, - nosniff: true, - referrerPolicy: 'same-origin' -})); - -app.use(lusca.csrf()); -app.use(lusca.csp({ policy: [{ "img-src": "'self' http:" }, "block-all-mixed-content"], reportOnly: false })); -app.use(lusca.xframe('SAMEORIGIN')); -app.use(lusca.p3p('ABCDEF')); -app.use(lusca.hsts({ maxAge: 31536000 })); -// ruleid:header_xss_lusca -app.use(lusca.xssProtection(false)); -app.use(lusca.nosniff()); -app.use(lusca.referrerPolicy('same-origin')); - -app.get('/', function (req, res) { - // ruleid:header_xss_generic - var x = 0; - // ruleid:header_xss_generic - res.writeHead(200, { 'x-xss-protection': 0 }); - - // ruleid:header_xss_generic - res.set('x-xss-protection', 0); - //do not match - res.set('x-xss-protection', 1); - // ruleid:header_xss_generic - res.set('X-XSS-Protection', 0); - //sgrep bug - https://github.com/returntocorp/sgrep/issues/512 - // ruleid:header_xss_generic - res.set({ - 'Content-Length': req.query.foo, - 'x-xss-protection': 0, - 'ETag': '12345' - }) - //sgrep bug - https://github.com/returntocorp/sgrep/issues/512 - // ruleid:header_xss_generic - res.writeHead(200, { 'x-xss-protection': 0 }) - res.set('X-XSS-Protection', x); - - // do not detect - res.set(ffff) -}); diff --git a/nodejsscan/header_xss_protection.yaml b/nodejsscan/header_xss_protection.yaml deleted file mode 100644 index 3be3c29..0000000 --- a/nodejsscan/header_xss_protection.yaml +++ /dev/null @@ -1,68 +0,0 @@ -rules: - - id: header_xss_lusca - patterns: - - pattern-inside: | - $X = require('lusca'); - ... - - pattern-not: | - $X.use(helmet()) - - pattern-either: - - pattern: | - $X.xssProtection(false) - - pattern: | - $X({ xssProtection: false}) - message: >- - X-XSS-Protection header is set to 0. This will disable the browser's XSS - Filter. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A6: Security Misconfiguration' - cwe: 'CWE-693: Protection Mechanism Failure' - - id: header_xss_generic - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $RES.header("=~/[X-XSS-Protection|x-xss-protection]/", 0, ...) - - pattern: | - $RES.set("=~/[X-XSS-Protection|x-xss-protection]/", 0, ...) - - pattern: > - $RES.set(..., { "=~/[X-XSS-Protection|x-xss-protection]/" : 0 }, - ...) - - pattern: > - $RES.writeHead(..., {"=~/[X-XSS-Protection|x-xss-protection]/": 0 - }, ...); - - pattern: | - $VAL = 0; - ... - $RES.header("=~/[X-XSS-Protection|x-xss-protection]/", $VAL, ...); - - pattern: | - $VAL = 0; - ... - $RES.set("=~/[X-XSS-Protection|x-xss-protection]/", $VAL, ...); - - pattern: > - $VAL = 0; - ... - $RES.set(..., { "=~/[X-XSS-Protection|x-xss-protection]/" : $VAL - }, ...); - - pattern: > - $VAL = 0; - ... - $RES.writeHead(..., {"=~/[X-XSS-Protection|x-xss-protection]/": - $VAL }, ...); - message: >- - X-XSS-Protection header is set to 0. This will disable the browser's XSS - Filter. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A6: Security Misconfiguration' - cwe: 'CWE-693: Protection Mechanism Failure' diff --git a/nodejsscan/host_header_injection.js b/nodejsscan/host_header_injection.js deleted file mode 100644 index 52192c8..0000000 --- a/nodejsscan/host_header_injection.js +++ /dev/null @@ -1,54 +0,0 @@ -// https://www.acunetix.com/blog/articles/automated-detection-of-host-header-attacks/ -app.get('/', function (req, res) { - - //semgrep string lateral support is pending - var foo = { - text: `reset url: https://${req.host}/password_reset/${token}` - }; - - //do not match - var x = 'https://' + foo - // do not match - var x = "https://" + req.foo + "/reset" + foo; - // do not match - var x = "https://" + z + "/reset"; - - - - // ruleid:host_header_injection - var url = 'http://' + req.host; - // ruleid:host_header_injection - var reset = 'https://' + req.host + '/password_reset'; - // ruleid:host_header_injection - var pass = "https://" + req.host + "/reset"; - - // ruleid:host_header_injection - var z = req.host; - var pass = "https://" + z + "/reset"; - - // ruleid:host_header_injection - var reset_url = "Reset password: Reset"; - // ruleid:host_header_injection - var foo = { - text: 'password: https://' + req.host + '/token/', - token: 'f2131ASDSADASoo', - }; - - // ruleid:host_header_injection - var foo = { - text: 'reset password: https://' + req['host'] + '/token/', - token: 'f2131ASDSADASoo', - }; - - // ruleid:host_header_injection - let x = "https://" + req['host'] + "/reset" + foo; - // ruleid:host_header_injection - x = "https://" + req("host") + "/reset" + foo + 'barr' + foo2; - - // ruleid:host_header_injection - var foo = { - text: 'reset password: https://' + req.host + '/resettoken/' + foo, - token: 'f2131ASDSADASoo', - }; - -}); diff --git a/nodejsscan/host_header_injection.yaml b/nodejsscan/host_header_injection.yaml deleted file mode 100644 index 3afde67..0000000 --- a/nodejsscan/host_header_injection.yaml +++ /dev/null @@ -1,55 +0,0 @@ -rules: - - id: host_header_injection - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $X = <... "=~/.*http[s]*:///" + $REQ.host ...>; - - pattern: | - $X = <... "=~/.*http[s]*:///" + $REQ["host"] ...>; - - pattern: | - $X = <... "=~/.*http[s]*:///" + $REQ("host") ...>; - - pattern: | - $X = { $Y: <... "=~/.*http[s]*:///" + $REQ.host ...>}; - - pattern: | - $X = { $Y: <... "=~/.*http[s]*:///" + $REQ["host"] ...>}; - - pattern: | - $X = { $Y: <... "=~/.*http[s]*:///" + $REQ("host") ...>}; - - pattern: | - $Z = $REQ.host; - ... - $X = <... "=~/.*http[s]*:///" + $Z ...>; - - pattern: | - $Z = $REQ["host"]; - ... - $X = <... "=~/.*http[s]*:///" + $Z ...>; - - pattern: | - $Z = $REQ("host"); - ... - $X = <... "=~/.*http[s]*:///" + $Z ...>; - - pattern: | - $Z = $REQ.host; - ... - $X = { $Y: <... "=~/.*http[s]*:///" + $REQ.host ...>}; - - pattern: | - $Z = $REQ["host"]; - ... - $X = { $Y: <... "=~/.*http[s]*:///" + $Z ...>}; - - pattern: | - $Z = $REQ("host"); - ... - $X = { $Y: <... "=~/.*http[s]*:///" + $REQ("host") ...>}; - message: >- - Using untrusted Host header for generating dynamic URLs can result in web - cache and or password reset poisoning. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: 'CWE-20: Improper Input Validation' diff --git a/nodejsscan/jwt_none_algorithm.js b/nodejsscan/jwt_none_algorithm.js deleted file mode 100644 index c304f09..0000000 --- a/nodejsscan/jwt_none_algorithm.js +++ /dev/null @@ -1,14 +0,0 @@ -// ruleid:node_jwt_none_algorithm -const jose = require("jose"); -const { JWK, JWT } = jose; -const token = JWT.verify('token-here', JWK.None); - -function verifyJwt() { - // ruleid:node_jwt_none_algorithm - let jwt = require("jsonwebtoken"); - let secret = 'some-secret'; - jwt.verify('token-here', secret, { algorithms: ['RS256', 'none'] }, function (err, payload) { - console.log(payload); - }); -} - diff --git a/nodejsscan/jwt_none_algorithm.yaml b/nodejsscan/jwt_none_algorithm.yaml deleted file mode 100644 index 4b0b7db..0000000 --- a/nodejsscan/jwt_none_algorithm.yaml +++ /dev/null @@ -1,45 +0,0 @@ -# Rule from : https://github.com/returntocorp/semgrep-rules/blob/develop/javascript/jwt-none-alg/jwt-none-alg.yaml -# https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/#Meet-the--None--Algorithm -rules: - - id: node_jwt_none_algorithm - patterns: - - pattern-either: - - pattern: | - $JWT = require("jsonwebtoken"); - ... - $T = $JWT.verify($P, $X, {algorithms:[...,'none',...]},...); - - pattern: | - $JWT = require("jsonwebtoken"); - ... - $T = $JWT.verify($P, $X, {algorithms:[...,'none',...]},...); - - pattern: | - $JWT = require("jsonwebtoken"); - ... - $JWT.verify($P, $X, {algorithms:[...,'none',...]},...); - - pattern: | - $JOSE = require("jose"); - ... - var { JWK, JWT } = $JOSE; - ... - $T = JWT.verify($P, JWK.None,...); - - pattern: | - $JOSE = require("jose"); - ... - var { JWK, JWT } = $JOSE; - ... - $T = JWT.verify($P, JWK.None,...); - - pattern: | - $JOSE = require("jose"); - ... - var { JWK, JWT } = $JOSE; - ... - JWT.verify($P, JWK.None,...); - message: >- - Algorithm is set to none for JWT token. This can nullify the integrity of - JWT signature. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A9: Using Components with Known Vulnerabilities' - cwe: 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' diff --git a/nodejsscan/layer7_object_dos.js b/nodejsscan/layer7_object_dos.js deleted file mode 100644 index e88bb8e..0000000 --- a/nodejsscan/layer7_object_dos.js +++ /dev/null @@ -1,31 +0,0 @@ -const express = require('express'); -const router = express.Router() - - -router.post("/list-users", (req, res) => { - var obj = req.body.users; - var someArr = []; - - // Potential DoS if obj.length is large. - // ruleid:layer7_object_dos - for (var i = 0; i < obj.length; i++) { - someArr.push(obj[i]); - } - -}); - - -module.exports = router - - -app.post("/foo", (req, res) => { - var obj = req.body; - - var ret = []; - - // Potential DoS if obj.length is large. - // ruleid:layer7_object_dos - for (var i = 0; i < obj.length; i++) { - ret.push(obj[i]); - } -}); \ No newline at end of file diff --git a/nodejsscan/layer7_object_dos.yaml b/nodejsscan/layer7_object_dos.yaml deleted file mode 100644 index d077a6e..0000000 --- a/nodejsscan/layer7_object_dos.yaml +++ /dev/null @@ -1,29 +0,0 @@ -rules: - - id: layer7_object_dos - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern-inside: | - $OBJ = $REQ.body; - ... - - pattern-inside: | - $OBJ = $REQ.body.$FOO; - ... - - pattern-either: - - pattern-inside: | - for(...){...}; - - pattern: | - $OBJ.length; - message: Layer7 Denial of Service. Looping over user controlled objects can result in DoS. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A6: Security Misconfiguration' - cwe: >- - CWE-400: Uncontrolled Resource Consumption \ No newline at end of file diff --git a/nodejsscan/logic_bypass.yaml b/nodejsscan/logic_bypass.yaml deleted file mode 100644 index cc6ff5b..0000000 --- a/nodejsscan/logic_bypass.yaml +++ /dev/null @@ -1,55 +0,0 @@ -rules: - - id: node_logic_bypass - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $REQ.$FOO.$BAR !== $REQ.$ZOO.$ZAR - - pattern: | - $REQ.$FOO.$BAR === $REQ.$ZOO.$ZAR - - pattern: | - $REQ.$FOO.$BAR >= $REQ.$ZOO.$ZAR - - pattern: | - $REQ.$FOO.$BAR <= $REQ.$ZOO.$ZAR - - pattern: | - $REQ.$FOO.$BAR < $REQ.$ZOO.$ZAR - - pattern: | - $REQ.$FOO.$BAR > $REQ.$ZOO.$ZAR - - pattern: | - $REQ.$FOO['...'] !== $REQ.$ZOO['...'] - - pattern: | - $REQ.$FOO['...'] === $REQ.$ZOO['...'] - - pattern: | - $REQ.$FOO['...'] >= $REQ.$ZOO['...'] - - pattern: | - $REQ.$FOO['...'] <= $REQ.$ZOO['...'] - - pattern: | - $REQ.$FOO['...'] < $REQ.$ZOO['...'] - - pattern: | - $REQ.$FOO['...'] > $REQ.$ZOO['...'] - - pattern: | - $REQ.$FOO('...') !== $REQ.$ZOO('...') - - pattern: | - $REQ.$FOO('...') === $REQ.$ZOO('...') - - pattern: | - $REQ.$FOO('...') >= $REQ.$ZOO('...') - - pattern: | - $REQ.$FOO('...') <= $REQ.$ZOO('...') - - pattern: | - $REQ.$FOO('...') < $REQ.$ZOO('...') - - pattern: | - $REQ.$FOO('...') > $REQ.$ZOO('...') - message: >- - User controlled data is used for application business logic decision - making. This expose protected data or functionality. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A5: Broken Access Control' - cwe: 'CWE-807: Reliance on Untrusted Inputs in a Security Decision' diff --git a/nodejsscan/logic_user_controlled_checks.js b/nodejsscan/logic_user_controlled_checks.js deleted file mode 100644 index 40b4b70..0000000 --- a/nodejsscan/logic_user_controlled_checks.js +++ /dev/null @@ -1,10 +0,0 @@ -var express = require('express'); -var app = express(); -app.get('/view/:id', function (req, res) { - - // ruleid:node_logic_bypass - if (req.cookies["user"] === req.params["id"]) { - showProfile(); - } - -}); diff --git a/nodejsscan/missing_good_controls.js b/nodejsscan/missing_good_controls.js deleted file mode 100644 index d5dc07b..0000000 --- a/nodejsscan/missing_good_controls.js +++ /dev/null @@ -1,35 +0,0 @@ -app.post('/entry', (req, res) => { - console.log(`Message received: ${req.body.message}`); - res.send(`CSRF token not used`); -}); - -app.post('/auth', function (request, response) { - var username = request.body.username; - var password = request.body.password; - if (username && password) { - connection.query('SELECT * FROM accounts WHERE username = ? AND password = ?', [username, password], function (error, results, fields) { - if (results.length > 0) { - request.session.loggedin = true; - request.session.username = username; - response.redirect('/home'); - } else { - response.send('Incorrect Username and/or Password!'); - } - response.end(); - }); - } else { - response.send('Please enter Username and Password!'); - response.end(); - } -}); - -// missing helmet -const helmet = require('helmet') -const xssFilter = require('x-xss-protection') -const noSniff = require('dont-sniff-mimetype') -const hsts = require('hsts') -const frameguard = require('frameguard') -const permittedCrossDomainPolicies = require('helmet-crossdomain') - -app.use(helmet.dnsPrefetchControl({ allow: true })) -app.use(dnsPrefetchControl({ allow: true })) \ No newline at end of file diff --git a/nodejsscan/nosql_injection.js b/nodejsscan/nosql_injection.js deleted file mode 100644 index 45c1293..0000000 --- a/nodejsscan/nosql_injection.js +++ /dev/null @@ -1,81 +0,0 @@ -var MongoClient = require('mongodb').MongoClient; -// mongo js injection https://lockmedown.com/securing-node-js-mongodb-security-injection-attacks/ -timelineRouter.route("/api/timeline") - .get(async function (req, res) { - try { - var foo = req.foo.bar; - const startDate = "01/01/2000"; - // ruleid:node_nosqli_js_injection - const endDate = req.query.end; - const query = { $where: "this.hidden == false" }; - - if (startDate && endDate) { - query["$where"] = "this.start >= new Date('" + startDate + "') && " + - "this.end <= new Date('" + endDate + "') &&" + - "this.hidden == false;"; - } - - const TimelineItem = await getTimelineItemModel(); - const timelineItems = await TimelineItem.find(query); - console.log(colors.yellow(`# of Timeline Items retrieved: ${timelineItems.length}`)); - return res.json({ timelineItems: timelineItems }); - - } catch (error) { - res.status(500).send("There was an error retrieving timeline items. Please try again later"); - } - }); - -// https://nullsweep.com/a-nosql-injection-primer-with-mongo/ -// ruleid:node_nosqli_js_injection -let username = req.query.username; -var query = { $where: `this.username == '${username}'` } -User.find(query, function (err, users) { - if (err) { - // Handle errors - } else { - res.render('userlookup', { title: 'User Lookup', users: users }); - } -}); - -app.post('/foo', function (req, res) { -// ruleid:node_nosqli_js_injection - var query = {}; - query['$where'] = `this.email == '${req.body.email}'`; - User.find(query, function (err, data) { - if (err) { - res.send(err); - } else if (data) { - res.send('User Login Successful'); - } else { - res.send('Wrong Username Password Combination'); - } - }) -}); - -app.post('/smth', function (req, res) { -// ruleid:node_nosqli_injection - var query = {}; - query['email'] = req.body.email; - User.findOne(query, function (err, data) { - if (err) { - res.send(err); - } else if (data) { - res.send('User Login Successful'); - } else { - res.send('Wrong Username Password Combination'); - } - }) -}); - -app.post('/login', function (req, res) { -// ruleid:node_nosqli_injection - User.findOne({ 'email': req.body.email, 'password': req.body.password }, function (err, data) { - if (err) { - res.send(err); - } else if (data) { - res.send('User Login Successful'); - } else { - res.send('Wrong Username Password Combination'); - } - }) -}); \ No newline at end of file diff --git a/nodejsscan/nosql_injection.yaml b/nodejsscan/nosql_injection.yaml deleted file mode 100644 index 450b4ec..0000000 --- a/nodejsscan/nosql_injection.yaml +++ /dev/null @@ -1,167 +0,0 @@ -rules: -- id: node_nosqli_js_injection - patterns: - - pattern-either: - - pattern: | - $OBJ.$FUNC({$where: <... $REQ.$FOO.$BAR ...>}, ...); - - pattern: | - $OBJ.$FUNC({$where: <... $REQ.$QUERY ...>}, ...); - - pattern: | - $NSQL = <... $REQ.$QUERY.$...>; - ... - $OBJ.$FUNC({$where: <... $NSQL ...>}, ...); - - pattern: | - $NSQL = <... $REQ.$QUERY ...>; - ... - $OBJ.$FUNC({$where: <... $NSQL ...>}, ...); - - pattern: | - $INP = $REQ.$FOO.$BAR; - ... - $QRY = {$where: <... $INP ...>}; - ... - $OBJ.$FUNC(<... $QRY ...>, ...); - - pattern: | - $INP = $REQ.$FOO; - ... - $QRY = {$where: <... $INP ...>}; - ... - $OBJ.$FUNC(<... $QRY ...>, ...); - - pattern: | - $QRY = {}; - ... - $QRY["$where"] = <... $REQ.$FOO ...>; - ... - $OBJ.$FUNC(<... $QRY ...>, ...); - - pattern: | - $QRY = {}; - ... - $QRY["$where"] = <... $REQ.$FOO.$BAR ...>; - ... - $OBJ.$FUNC(<... $QRY ...>, ...); - - pattern: | - $INP = $REQ.$FOO; - ... - $QRY = {}; - ... - $QRY["$where"] = <... $INP ...>; - ... - $OBJ.$FUNC(<... $QRY ...>, ...); - - pattern: | - $INP = $REQ.$FOO.$BAR; - ... - $QRY = {}; - ... - $QRY["$where"] = <... $INP ...>; - ... - $OBJ.$FUNC(<... $QRY ...>, ...); - - pattern: | - $QRY = {}; - ... - $INP = $REQ.$FOO; - ... - $QRY["$where"] = <... $INP ...>; - ... - $OBJ.$FUNC(<... $QRY ...>, ...); - - pattern: | - $QRY = {}; - ... - $INP = $REQ.$FOO.$BAR; - ... - $QRY["$where"] = <... $INP ...>; - ... - $OBJ.$FUNC(<... $QRY ...>, ...); - message: >- - Untrusted user input in MongoDB $where operator can result in NoSQL - JavaScript Injection. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: 'CWE-943: Improper Neutralization of Special Elements in Data Query Logic' -- id: node_nosqli_injection - patterns: - - pattern-either: - - pattern: | - $OBJ.findOne({$KEY : <... $REQ.$FOO.$BAR ...> }, ...); - - pattern: | - $OBJ.findOne({$KEY: <... $REQ.$FOO ...> }, ...); - - pattern: | - $INP = <... $REQ.$FOO.$BAR ...>; - ... - $OBJ.findOne({$KEY : <... $INP ...> }, ...); - - pattern: | - $INP = <... $REQ.$FOO ...>; - ... - $OBJ.findOne({$KEY: <... $INP ...> }, ...); - - pattern: | - $QUERY = {$KEY: <... $REQ.$FOO.$BAR ...>}; - ... - $OBJ.findOne($QUERY, ...); - - pattern: | - $QUERY = {$KEY: <... $REQ.$FOO ...>}; - ... - $OBJ.findOne($QUERY, ...); - - pattern: | - $INP = <... $REQ.$FOO.$BAR ...>; - ... - $QUERY = {$KEY : <... $INP ...> }; - ... - $OBJ.findOne(<... $QUERY ...>, ...); - - pattern: | - $INP = <... $REQ.$FOO ...>; - ... - $QUERY = {$KEY : <... $INP ...> }; - ... - $OBJ.findOne(<... $QUERY ...>, ...); - - pattern: | - $QUERY = {}; - ... - $QUERY[$KEY] = <... $REQ.$FOO.$BAR ...>; - ... - $OBJ.findOne($QUERY, ...); - - pattern: | - $QUERY = {}; - ... - $QUERY[$KEY] = <... $REQ.$FOO ...>; - ... - $OBJ.findOne($QUERY, ...); - - pattern: | - $INP = <... $REQ.$FOO.$BAR ...>; - ... - $QUERY = {}; - ... - $QUERY[$KEY] = <... $INP ...>; - ... - $OBJ.findOne(<... $QUERY ...>, ...); - - pattern: | - $INP = <... $REQ.$FOO ...>; - ... - $QUERY = {}; - ... - $QUERY[$KEY] = <... $INP ...>; - ... - $OBJ.findOne(<... $QUERY ...>, ...); - - pattern: | - $QUERY = {}; - ... - $INP = <... $REQ.$FOO.$BAR ...>; - ... - $QUERY[$KEY] = <... $INP ...>; - ... - $OBJ.findOne(<... $QUERY ...>, ...); - - pattern: | - $QUERY = {}; - ... - $INP = <... $REQ.$FOO ...>; - ... - $QUERY[$KEY] = <... $INP ...>; - ... - $OBJ.findOne(<... $QUERY ...>, ...); - message: Untrusted user input in findOne() function can result in NoSQL Injection. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: 'CWE-943: Improper Neutralization of Special Elements in Data Query Logic' diff --git a/nodejsscan/open_redirect.js b/nodejsscan/open_redirect.js deleted file mode 100644 index cdc6da0..0000000 --- a/nodejsscan/open_redirect.js +++ /dev/null @@ -1,130 +0,0 @@ -const express = require('express'); -const router = express.Router() - -router.use((req, res, next) => { - if (req.method === 'POST') { - console.log(JSON.stringify(req.session.data, null, 2)) - } - next() -}) - -router.post('/sprint18b/frequency', (req, res) => { - res.redirect('/sprint18b/payment') //GOOD -}); - -var express = require('express'); - -var app = express(); - -app.get('/some/path', function (req, res) { - // BAD: a request parameter is incorporated without validation into a URL redirect - // ruleid:express_open_redirect - res.redirect(302, req.param("target")); -}); - -app.get('/some/path1', function (req, res) { - // BAD: a request parameter is incorporated without validation into a URL redirect - // ruleid:express_open_redirect - res.redirect(300, req.param); -}); - -app.get('/some/path2', function (req, res) { - // BAD: a request parameter is incorporated without validation into a URL redirect - // ruleid:express_open_redirect - res.redirect(req.param["target"]); -}); - -app.get('/some/path3', function (req, res) { - // BAD: a request parameter is incorporated without validation into a URL redirect - // ruleid:express_open_redirect - res.redirect(req.body.url); -}); -app.get('/some/path4', function (req, res) { - // BAD subdomain control - // ruleid:express_open_redirect - res.redirect("sdcssf" + req.param("target")); -}); -app.get('/some/path5', function (req, res) { - // BAD: a request parameter is incorporated without validation into a URL redirect - // ruleid:express_open_redirect - res.redirect(req.param("target") + "/asdad"); -}); -app.all(function (req, res) { - // BAD: a request parameter is incorporated without validation into a URL redirect - // ruleid:express_open_redirect2 - res.header("Location", req.param["target"]); -}); - -app.all(function (req, res) { - // BAD: a request parameter is incorporated without validation into a URL redirect - // ruleid:express_open_redirect2 - res.header('Location', req.param("foo")); -}); - - -app.all(function (req, res) { - // ruleid:express_open_redirect2 - res.writeHead(200, { location: 'foo \rinvalid: bar' + req.foo + 'asdadasd', foo: bar }); -}); - - -app.all(function (req, res) { - // ruleid:express_open_redirect2 - res.writeHead(200, { 'location': req.foo }); -}); - -app.all(function (req, res) { - // BAD: a request parameter is incorporated without validation into a URL redirect - // ruleid:express_open_redirect2 - res.header('location', req.param("bar")); -}); - -app.get('/some/path', function (req, res) { - // ruleid:express_open_redirect - var target = req.param("target"); - // BAD: sanitization doesn't apply here - res.redirect(target); -}); - -app.get('/foo', function (req, res) { - // BAD: may be a global redirection - // ruleid:express_open_redirect - res.redirect((req.param('action') && req.param('action') != "") ? req.param('action') : "/google_contacts") -}); - -app.get('/yet/another/path', function (req, res) { - // BAD: a request parameter is incorporated without validation into a URL redirect - // ruleid:express_open_redirect - res.redirect(`${req.param("target")}/foo`); -}); - -app.get('/array/join', function (req, res) { - // BAD: request input becomes before query string - // ruleid:express_open_redirect - res.redirect([req.query.page, '?section=', req.query.section].join('')); -}); - -app.get('/call', function (req, res) { - sendUserToUrl(res, req.query.nextUrl); -}); - -function sendUserToUrl(res, nextUrl) { - // BAD: value comes from query parameter - res.redrect(nextUrl); -} - -app.get('/redirect/:user', function (req, res) { - - // ruleid:express_open_redirect - res.redirect('/' + req.params.user); // BAD - could go to //evil.com - // ruleid:express_open_redirect - res.redirect('//' + req.params.user); // BAD - could go to //evil.com - // ruleid:express_open_redirect - res.redirect('u' + req.params.user); // BAD - could go to u.evil.com - // ruleid:express_open_redirect - res.redirect('Fan999' + req.params.user); // BAD - could go to Fan999.evil.com - // ruleid:express_open_redirect - res.redirect('/' + ('/u' + req.params.user)); // BAD - could go to //u.evil.com, - //do not trigger - res.redirect('/' + foo) -}); \ No newline at end of file diff --git a/nodejsscan/open_redirect.yaml b/nodejsscan/open_redirect.yaml deleted file mode 100644 index efc83d1..0000000 --- a/nodejsscan/open_redirect.yaml +++ /dev/null @@ -1,83 +0,0 @@ -rules: - - id: express_open_redirect - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $X.redirect(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - $X.redirect(..., <... $REQ.$QUERY.$FOO ...>, ...) - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - $X.redirect(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY.$FOO ...>; - ... - $X.redirect(..., <... $INP ...>, ...); - message: >- - Untrusted user input in redirect() can result in Open Redirect - vulnerability. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: >- - CWE-601: URL Redirection to Untrusted Site ('Open Redirect') - - id: express_open_redirect2 - patterns: - - pattern-inside: | - $APP.$METHOD(..., function $FUNC($REQ, $RES) { ... }) - - pattern-either: - - pattern: | - $RES.header(..., "=~/[Ll]+ocation/", <... $REQ.$VAR ...>, ...) - - pattern: | - $RES.header(..., "=~/[Ll]+ocation/", <... $REQ.$VAR.$VARR ...>, ...) - - pattern: | - $RES.writeHead(..., "=~/[Ll]+ocation/", <... $REQ.$VAR ...>, ...) - - pattern: | - $RES.writeHead(..., "=~/[Ll]+ocation/", <... $REQ.$VAR.$VARR ...>, ...) - - pattern: | - $RES.writeHead(..., {"=~/[Ll]+ocation/": <... $REQ.$VAR ...> }, ...) - - pattern: | - $RES.writeHead(..., {"=~/[Ll]+ocation/": <... $REQ.$VAR.$VARR ...> }, ...) - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - $RES.header(..., "=~/[Ll]+ocation/", <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR.$VARR ...>; - ... - $RES.header(..., "=~/[Ll]+ocation/", <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - $RES.writeHead(..., "=~/[Ll]+ocation/", <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR.$VARR ...>; - ... - $RES.writeHead(..., "=~/[Ll]+ocation/", <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - $RES.writeHead(..., {"=~/[Ll]+ocation/": <... $INP ...> }, ...); - - pattern: | - $INP = <... $REQ.$VAR.$VARR ...>; - ... - $RES.writeHead(..., {"=~/[Ll]+ocation/": <... $INP ...> }, ...); - message: >- - Untrusted user input in response header('Location') can result in Open - Redirect vulnerability. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: >- - CWE-601: URL Redirection to Untrusted Site ('Open Redirect') diff --git a/nodejsscan/path_traversal.js b/nodejsscan/path_traversal.js deleted file mode 100644 index 58f7561..0000000 --- a/nodejsscan/path_traversal.js +++ /dev/null @@ -1,40 +0,0 @@ -var http = require('http'), - fileSystem = require('fs'), - path = require('path'); - -var config = require('../config'); -var Promise = require('bluebird'); -Promise.promisifyAll(fileSystem); - -var express = require('express'); -var app = express(); -app.get('/', function (req, res) { - // ruleid:generic_path_traversal - var filePath = path.join(__dirname, '/' + req.query.load); - var readStream = fileSystem.createReadStream(filePath); - // ruleid:generic_path_traversal - fileSystem.readFile(req.query.foo); - // ruleid:generic_path_traversal - console.log(fileSystem.readFileSync(req.query.nar, 'utf8')); - // ruleid:generic_path_traversal - var foo = req.query.y; - fileSystem.readFile(foo); - fileSystem.readFile(foo + "bar"); - readStream.pipe(res); -}); - -app.get('/foo', function (req, res) { - // ruleid:generic_path_traversal - var date = req.query.date; - var fileName = config.dirName + '/' + date; - var downloadFileName = 'log_' + fileName + '.txt'; - - fs.readFileAsync(fileName) - .then(function(data) { - res.download(fileName, downloadFileName); - }) -}) - -app.listen(8888); -// do not match -fileSystem.readFile(ddd); diff --git a/nodejsscan/path_traversal.yaml b/nodejsscan/path_traversal.yaml deleted file mode 100644 index 3b33c7a..0000000 --- a/nodejsscan/path_traversal.yaml +++ /dev/null @@ -1,128 +0,0 @@ -rules: - - id: generic_path_traversal - patterns: - - pattern-either: - - pattern-inside: | - $HTTP = require('http'); - ... - - pattern-inside: | - $EXPRESS = require('express'); - ... - - pattern-inside: | - $KOA = require('koa'); - ... - - pattern-inside: | - $ELEC = require('electron'); - ... - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $X.createReadStream(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - $X.createReadStream(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - $X.readFile(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - $X.readFile(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - $X.readFileSync(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - $X.readFileSync(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - $X.readFileAsync(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - $X.readFileAsync(..., <... $REQ.$QUERY ...>, ...) - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - $X.createReadStream(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - $X.createReadStream(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - $X.readFile(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - $X.readFile(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - $X.readFileSync(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - $X.readFileSync(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - $X.readFileAsync(..., <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - $X.readFileAsync(..., <... $INP ...>, ...); - - pattern: | - $Y = $REQ.$QUERY.$VAR; - ... - $INP = <... $Y ...>; - ... - $X.createReadStream(..., <... $INP ...>, ...); - - pattern: | - $Y = $REQ.$QUERY; - ... - $INP = <... $Y ...>; - ... - $X.createReadStream(..., <... $INP ...>, ...); - - pattern: | - $Y = $REQ.$QUERY.$VAR; - ... - $INP = <... $Y ...>; - ... - $X.readFile(..., <... $INP ...>, ...); - - pattern: | - $Y = $REQ.$QUERY; - ... - $INP = <... $Y ...>; - ... - $X.readFile(..., <... $INP ...>, ...); - - pattern: | - $Y = $REQ.$QUERY.$VAR; - ... - $INP = <... $Y ...>; - ... - $X.readFileSync(..., <... $INP ...>, ...); - - pattern: | - $Y = $REQ.$QUERY; - ... - $INP = <... $Y ...>; - ... - $X.readFileSync(..., <... $INP ...>, ...); - - pattern: | - $Y = $REQ.$QUERY.$VAR; - ... - $INP = <... $Y ...>; - ... - $X.readFileAsync(..., <... $INP ...>, ...); - - pattern: | - $Y = $REQ.$QUERY; - ... - $INP = <... $Y ...>; - ... - $X.readFileAsync(..., <... $INP ...>, ...); - message: >- - Untrusted user input in readFile()/readFileSync() can endup in Directory - Traversal Attacks. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A5: Broken Access Control' - cwe: 'CWE-23: Relative Path Traversal' \ No newline at end of file diff --git a/nodejsscan/regex_dos.js b/nodejsscan/regex_dos.js deleted file mode 100644 index 2b4b3cb..0000000 --- a/nodejsscan/regex_dos.js +++ /dev/null @@ -1,56 +0,0 @@ -const express = require('express'); -const router = express.Router() - - -router.get("/tstMe", (req, res) => { - var r = /([a-z]+)+$/; - // ruleid:regex_dos - let match = r.test(req.params.id); -}); - - -module.exports = router - -var http = require("http"); -var url = require("url"); -http.createServer(function (request, response) { - - var myArray = /d(b+)d/g.exec('cdbbdbsbz'); - var emailExpression = /^([a-zA-Z0-9_\.\-])+\@/ + foo + /(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; - - - // ruleid:regex_dos - var parsedUrl = url.parse(request.url, true); - console.timeEnd('benchmark'); - /^(([a-z])+.)+[A-Z]([a-z])+$/.test(parsedUrl.query); - - /^(([a-z])+.)+[A-Z]([a-z])+$/.test('a'.repeat(100)); - console.timeEnd('benchmark'); - let match = r.test(req.params.id); - // ruleid:regex_dos - /^(([a-z])+.)+[A-Z]([a-z])+$/.test(request.foo); - console.timeEnd('benchmark'); - // ruleid:regex_dos - var y = /^(([a-z])+.)+[A-Z]([a-z])+$/.test(request.foo['bar']); - console.timeEnd('benchmark'); - console.time('benchmark'); - var x = /^(([a-z])+.)+[A-Z]([a-z])+$/.test('a'.repeat(100)); - console.timeEnd('benchmark'); - - // ruleid:regex_dos - var myArray = /d(b+)d/g.exec(request.foo.bar); - response.end(); - - - // ruleid:regex_dos - var re = /(?:\d{3}|\(\d{3}\))([-\/\.])\d{3}\1\d{4}/; - var OK = re.exec(request.value); - if (!OK) { - console.error(request.value + ' isn\'t a phone number with area code!'); - } else { - console.log('Thanks, your phone number is ' + OK[0]); - } - -}).listen(8888); - - diff --git a/nodejsscan/regex_dos.yaml b/nodejsscan/regex_dos.yaml deleted file mode 100644 index c8e6ee1..0000000 --- a/nodejsscan/regex_dos.yaml +++ /dev/null @@ -1,67 +0,0 @@ -rules: - - id: regex_dos - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $REGEX.test(<... $REQ ...>) - - pattern: | - $REGEX.test(<... $REQ.$QUERY ...>) - - pattern: | - $REGEX.test(<... $REQ.$BODY.$PARAM ...>) - - pattern: | - $INP = <... $REQ ...>; - ... - $REGEX.test(<... $INP ...>); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - $REGEX.test(<... $INP ...>); - - pattern: | - $INP = <... $REQ.$BODY.$PARAM ...>; - ... - $REGEX.test(<... $INP ...>); - - pattern: | - /.../g.exec(<... $REQ ...>) - - pattern: | - /.../g.exec(<... $REQ.$QUERY ...>) - - pattern: | - /.../.exec(<... $REQ.$BODY.$PARAM ...>) - - pattern: | - $INP = <... $REQ ...>; - ... - /.../.exec(<... $INP ...>); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - /.../.exec(<... $INP ...>); - - pattern: | - $INP = <... $REQ.$BODY.$PARAM ...>; - ... - /.../.exec(<... $INP ...>); - - pattern: | - $RE = /.../; - ... - $RE.exec(<... $REQ ...>); - - pattern: | - $RE = /.../; - ... - $RE.exec(<... $REQ.$QUERY ...>); - - pattern: | - $RE = /.../; - ... - $RE.exec(<... $REQ.$BODY.$PARAM ...>); - message: >- - Ensure that the regex used to compare with user supplied input is safe - from regular expression denial of service. - languages: - - javascript - severity: WARNING - metadata: - owasp: 'A6: Security Misconfiguration' - cwe: 'CWE-185: Incorrect Regular Expression' diff --git a/nodejsscan/regex_injection.yaml b/nodejsscan/regex_injection.yaml deleted file mode 100644 index f970ab9..0000000 --- a/nodejsscan/regex_injection.yaml +++ /dev/null @@ -1,67 +0,0 @@ -rules: - - id: regex_injection_dos - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $INP = <... $REQ.$PARAM ...>; - ... - $RE = new RegExp(<... $INP ...>); - - pattern: | - $INP = <... $REQ.$PARAM.$BAR ...>; - ... - $RE = new RegExp(<... $INP ...>); - - pattern: | - new RegExp(<... $REQ.$PARAM ...>); - - pattern: | - new RegExp(<... $REQ.$PARAM.$BAR ...>); - - pattern: | - $INP = <... $REQ.$PARAM ...>; - ... - $RE = $STR.search(<... $INP ...>); - - pattern: | - $INP = <... $REQ.$PARAM.$FOO ...>; - ... - $RE = $STR.search(<... $INP ...>); - - pattern: | - $STR.search(<... $REQ.$PARAM ...>); - - pattern: | - $STR.search(<... $REQ.$PARAM.$BAR ...>); - - pattern: | - $INP = <... $REQ.$PARAM ...>; - ... - $RE = $STR.match(<... $INP ...>); - - pattern: | - $INP = <... $REQ.$PARAM.$FOO ...>; - ... - $RE = $STR.match(<... $INP ...>); - - pattern: | - $STR.match(<... $REQ.$PARAM ...>); - - pattern: | - $STR.match(<... $REQ.$PARAM.$BAR ...>); - - pattern: | - $INP = <... $REQ.$PARAM ...>; - ... - $RE = $STR.split(<... $INP ...>); - - pattern: | - $INP = <... $REQ.$PARAM.$FOO ...>; - ... - $RE = $STR.split(<... $INP ...>); - - pattern: | - $STR.split(<... $REQ.$PARAM ...>); - - pattern: | - $STR.split(<... $REQ.$PARAM.$BAR ...>); - message: >- - User controlled data in RegExp() can make the application vulnerable to - layer 7 DoS. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: 'CWE-400: Uncontrolled Resource Consumption' diff --git a/nodejsscan/regex_injection_dos.js b/nodejsscan/regex_injection_dos.js deleted file mode 100644 index ce51832..0000000 --- a/nodejsscan/regex_injection_dos.js +++ /dev/null @@ -1,11 +0,0 @@ -var express = require('express'); -var app = express(); - -app.get('/search', function (req, res) { - // ruleid:regex_injection_dos - var key = req.param("key"); - // Regex created from user input - var re = new RegExp("\\b" + key); -}); -//do not detect this -var re = new RegExp("\\b" + key + "=(.*)\n"); \ No newline at end of file diff --git a/nodejsscan/security_electron.js b/nodejsscan/security_electron.js deleted file mode 100644 index a690cbc..0000000 --- a/nodejsscan/security_electron.js +++ /dev/null @@ -1,65 +0,0 @@ -// ruleid:electron_disable_websecurity -const mainWindow = new BrowserWindow({ - webPreferences: { - webSecurity: false - } -}) - -// ruleid:electron_disable_websecurity -const config = { - webPreferences: { - webSecurity: false - } -} -var newwin = new BrowserWindow(config) - -// ruleid:electron_allow_http -const mainWindow = new BrowserWindow({ - webPreferences: { - allowRunningInsecureContent: true - } -}) - -// ruleid:electron_disable_websecurity -var x = new BrowserWindow({ - webPreferences: { - webSecurity: false, - allowRunningInsecureContent: true - } -}) - -// ruleid:electron_blink_integration -const mainWindow = new BrowserWindow({ - webPreferences: { - enableBlinkFeatures: 'ExecCommandInJavaScript' - } -}) - -// ruleid:electron_allow_http -const mainWindow = new BrowserWindow({ - webPreferences: { - allowRunningInsecureContent: true - } -}) - -// ruleid:electron_nodejs_integration -const mainWindow = new BrowserWindow({ - webPreferences: { - nodeIntegration: true, - nodeIntegrationInWorker: true - } -}) - -// ruleid:electron_context_isolation -const mainWindow = new BrowserWindow({ - webPreferences: { - contextIsolation: false, - preload: path.join(app.getAppPath(), 'preload.js') - } -}) -// ruleid:electron_experimental_features -const mainWindow = new BrowserWindow({ - webPreferences: { - experimentalFeatures: true - } -}) \ No newline at end of file diff --git a/nodejsscan/security_electronjs.yaml b/nodejsscan/security_electronjs.yaml deleted file mode 100644 index a80b9b8..0000000 --- a/nodejsscan/security_electronjs.yaml +++ /dev/null @@ -1,99 +0,0 @@ -rules: - - id: electron_disable_websecurity - patterns: - - pattern-either: - - pattern: | - new BrowserWindow({webPreferences: {webSecurity: false}}); - - pattern: | - var $X = {webPreferences: {webSecurity: false}}; - message: >- - Disabling webSecurity will disable the same-origin policy and allows the - execution of insecure code from any domain. - languages: - - javascript - severity: ERROR - metadata: - owasp: A6 - Security Misconfiguration - cwe: 'CWE-346: Origin Validation Error' - - id: electron_allow_http - patterns: - - pattern-either: - - pattern: > - new BrowserWindow({webPreferences: {allowRunningInsecureContent: - true}}); - - pattern: | - var $X = {webPreferences: {allowRunningInsecureContent: true}}; - message: >- - Application can load content over HTTP and that makes the app vulnerable - to Man in the middle attacks. - languages: - - javascript - severity: ERROR - metadata: - owasp: A6 - Security Misconfiguration - cwe: 'CWE-319: Cleartext Transmission of Sensitive Information' - - id: electron_blink_integration - patterns: - - pattern-either: - - pattern: | - new BrowserWindow({webPreferences: {enableBlinkFeatures: '...'}}); - - pattern: | - var $X = {webPreferences: {enableBlinkFeatures: '...'}}; - message: >- - Blink's expirimental features are enabled in this application. Some of the - features may affect the security of the application. - languages: - - javascript - severity: WARNING - metadata: - owasp: A6 - Security Misconfiguration - cwe: 'CWE-272: Least Privilege Violation' - - id: electron_nodejs_integration - patterns: - - pattern-either: - - pattern: | - new BrowserWindow({webPreferences: {nodeIntegration: true}}); - - pattern: | - var $X = {webPreferences: {nodeIntegration: true}}; - message: >- - Node integration exposes node.js APIs to the electron app and this can - introduce remote code execution vulnerabilities to the application if the - app is vulnerable to Cross Site Scripting (XSS). - languages: - - javascript - severity: WARNING - metadata: - owasp: A6 - Security Misconfiguration - cwe: 'CWE-272: Least Privilege Violation' - - id: electron_context_isolation - patterns: - - pattern-either: - - pattern: | - new BrowserWindow({webPreferences: {contextIsolation: false}}); - - pattern: | - var $X = {webPreferences: {contextIsolation: false}}; - message: >- - Disabling context isolation can introduce Prototype Pollution - vulnerabilities. - languages: - - javascript - severity: WARNING - metadata: - owasp: A6 - Security Misconfiguration - cwe: 'CWE-693: Protection Mechanism Failure' - - id: electron_experimental_features - patterns: - - pattern-either: - - pattern: | - new BrowserWindow({webPreferences: {experimentalFeatures: true}}); - - pattern: | - var $X = {webPreferences: {experimentalFeatures: true}}; - message: >- - Experimental features are not expected to be in production ready - applications. - languages: - - javascript - severity: WARNING - metadata: - owasp: A6 - Security Misconfiguration - cwe: 'CWE-272: Least Privilege Violation' diff --git a/nodejsscan/server_side_template_injection.js b/nodejsscan/server_side_template_injection.js deleted file mode 100644 index 49a841b..0000000 --- a/nodejsscan/server_side_template_injection.js +++ /dev/null @@ -1,45 +0,0 @@ -var handlebars = require('handlebars'), - fs = require('fs'), - Sqrl = require('squirrelly'); -// do not match -var template = handlebars.compile(source); - -app.get('/', function (req, res) { - var storeName = "console.log(process.pid)" // this should be a user-controlled string - function getStoreName() { - return storeName; - } - var scope = { - getStoreName: getStoreName - } - - fs.readFile('example.html', 'utf-8', function (error, source) { - // ruleid:server_side_template_injection - var template = handlebars.compile(source + req.foo); - // ruleid:server_side_template_injection - handlebars.compile(source + req.foo.bar); - - - var myTemplate = 'Hi, my name is {{name}}' - // ruleid:server_side_template_injection - var temp = myTemplate + req.foo['bar'] - var compiled = Sqrl.Compile(temp) - - - // ruleid:server_side_template_injection - var xx = source.replace('', req.foo) - handlebars.compile(xx) - - - // ruleid:server_side_template_injection - var x = source + req.foo; - var z = 2; - handlebars.compile(x); - - var html = template(data); - console.log(html) - }); - - //do not match - var template = handlebars.compile(source); -}); \ No newline at end of file diff --git a/nodejsscan/server_side_template_injection.yaml b/nodejsscan/server_side_template_injection.yaml deleted file mode 100644 index 193d72f..0000000 --- a/nodejsscan/server_side_template_injection.yaml +++ /dev/null @@ -1,78 +0,0 @@ -rules: - - id: server_side_template_injection - patterns: - - pattern-either: - - pattern-inside: | - $HB = require('handlebars'); - ... - - pattern-inside: | - $HB = require('pug'); - ... - - pattern-inside: | - $HB = require('hamljs'); - ... - - pattern-inside: | - $HB = require('ejs'); - ... - - pattern-inside: | - $HB = require('squirrelly'); - ... - - pattern-inside: | - $HB = require('eta'); - ... - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $HB.compile(..., <... $REQ.$FOO ...>, ...) - - pattern: | - $HB.compile(..., <... $REQ.$FOO.$BAR ...>, ...) - - pattern: | - $X = <... $REQ.$FOO ...>; - ... - $HB.compile(..., <... $X ...>, ...); - - pattern: | - $X = <... $REQ.$FOO.$BAR ...>; - ... - $HB.compile(..., <... $X ...>, ...); - - pattern: | - $X = $SOURCE.replace('...', <... $REQ.$FOO ...>, ...); - ... - $HB.compile(..., <... $X ...>, ...); - - pattern: | - $X = $SOURCE.replace('...', <... $REQ.$FOO.$BAR ...>, ...); - ... - $HB.compile(..., <... $X ...>, ...); - - pattern: | - $HB.Compile(..., <... $REQ.$FOO ...>, ...) - - pattern: | - $HB.Compile(..., <... $REQ.$FOO.$BAR ...>, ...) - - pattern: | - $X = <... $REQ.$FOO ...>; - ... - $HB.Compile(..., <... $X ...>, ...); - - pattern: | - $X = <... $REQ.$FOO.$BAR ...>; - ... - $HB.Compile(..., <... $X ...>, ...); - - pattern: | - $X = $SOURCE.replace('...', <... $REQ.$FOO ...>, ...); - ... - $HB.Compile(..., <... $X ...>, ...); - - pattern: | - $X = $SOURCE.replace('...', <... $REQ.$FOO.$BAR ...>, ...); - ... - $HB.Compile(..., <... $X ...>, ...); - message: >- - Untrusted user input in templating engine's compile() function can result - in Remote Code Execution via server side template injection. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: CWE-94 Improper Control of Generation of Code ('Code Injection') diff --git a/nodejsscan/sql_injection.yaml b/nodejsscan/sql_injection.yaml deleted file mode 100644 index d814f84..0000000 --- a/nodejsscan/sql_injection.yaml +++ /dev/null @@ -1,39 +0,0 @@ -rules: - - id: node_sqli_injection - patterns: - - pattern-either: - - pattern: | - $CON.query(<... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - $CON.query(<... $REQ.$QUERY ...>, ...) - - pattern: | - $SQL = <... $REQ.$QUERY.$VAR ...>; - ... - $CON.query(<... $SQL ...>, ...); - - pattern: | - $SQL = <... $REQ.$QUERY ...>; - ... - $CON.query(<... $SQL ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - $SQL = <... $INP ...>; - ... - $CON.query(<... $SQL ...>, ...); - - pattern: | - $INP = <... $REQ.$QUERY ...>; - ... - $SQL = <... $INP ...>; - ... - $CON.query(<... $SQL ...>, ...); - message: >- - Untrusted input concatinated with raw SQL query can result in SQL - Injection. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: >- - CWE-89: Improper Neutralization of Special Elements used in an SQL - Command ('SQL Injection') diff --git a/nodejsscan/sqli_node.js b/nodejsscan/sqli_node.js deleted file mode 100644 index f98eb79..0000000 --- a/nodejsscan/sqli_node.js +++ /dev/null @@ -1,66 +0,0 @@ -var mysql = require('mysql'); - -const pg = require('pg'); -// ruleid:node_sqli_injection -connection.query("SELECT * FROM bank_accounts WHERE dob = '" + req.body.dob + "' AND bank_account = '" + req.body.account_number + "'", function (error, results) { }); - -const sequelize = require('../conn'); -router.post('/', function (req, res) { - // ruleid:node_sqli_injection - var query = 'SELECT * FROM person WHERE id = \'' + - req.body.input + '\''; - sequelize.query(query, { - type: sequelize.QueryTypes.SELECT, - model: Foo - }) - .then(function (foo) { - res.json({ message: person }); - }) - .catch(function (err) { - res.json({ message: err.toString() }); - }); -}); - -var connection = mysql.createConnection({ - host: 'localhost', - user: user, - password: pass, - database: 'technicalkeeda', - debug: false, -}); -connection.connect(); - -// ruleid:node_sqli_injection -var employeeId = req.foo; -var sql = "SELECT * FROM trn_employee WHERE employee_id = " + employeeId; - -connection.query(sql, function (error, results, fields) { - if (error) { - throw error; - } - console.log(results); -}); - -connection.connect(function (err) { - // ruleid:node_sqli_injection - connection.query('SELECT * FROM users WHERE id = ' + req.foo('bar'), (err, res) => { }); -}); - -connection.end(); - -const pgcon = new pg.Client({ host: host, user: user, password: pass, database: db }); -pgcon.connect(); -// ruleid:node_sqli_injection -var inp = req.foo["id"]; -pgcon.query('SELECT * FROM users WHERE id = ' + inp, (err, res) => { }); - - -const pg = require('pg'); -const pool = new pg.Pool(config); -function handler(req, res) { - // ruleid:node_sqli_injection - var query1 = "SELECT FOO,BAR FROM TABLE WHERE CAT='" - + req.foo.bar + "' ORDER BY FOO"; - pool.query(query1, [], function (err, results) { - }); -} \ No newline at end of file diff --git a/nodejsscan/ssrf_node.js b/nodejsscan/ssrf_node.js deleted file mode 100644 index 3805f5a..0000000 --- a/nodejsscan/ssrf_node.js +++ /dev/null @@ -1,143 +0,0 @@ -var express = require('express'); -const request = require('request'); -var needle = require('needle'); -var app = express(); -const bent = require('bent') -const getJSON = bent('json') -const getBuffer = bent('buffer') -var urllib = require('urllib'); -const http = require('http'); -let axios = require('axios'); -var http = require('https'); - -app.get('/', function (req, res) { - // ruleid:node_ssrf - request(req.query.name, function (error, response, body) { - console.error('error:', error); // Print the error if one occurred - console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received - console.log('body:', body); // Print the HTML for the Google homepage. - }); - - // ruleid:node_ssrf - needle('get', req.vbody.foo).then(res => { - console.log(res.body); - }) - .catch(err => { - console.error(err); - }); - - - // ruleid:node_ssrf - urllib.request(req.foo, function (err, data, res) { - if (err) { - throw err; // you need to handle error - } - console.log(res.statusCode); - console.log(res.headers); - // data is Buffer instance - console.log(data.toString()); - }); - -}); - -app.get('/', function (req, res) { - - // ruleid:node_ssrf - needle.get(req.foo, function (error, response) { - if (!error && response.statusCode == 200) - console.log(response.body); - }); - - - // ruleid:node_ssrf - needle.post(req.foo) - .pipe(out) - .on('finish', () => { - console.log('pipe done'); - }); - - //Do not match this - needle.get('http://google.com') - .pipe(out) - .on('finish', () => { - console.log('pipe done'); - }); - - //Do not match this to reduce false positives - needle.get(somvar) - .pipe(out) - .on('finish', () => { - console.log('pipe done'); - }); - - - // ruleid:node_ssrf - axios.get(req.foo.bar) - .then(function (response) { - // handle success - console.log(response); - }) - .catch(function (error) { - // handle error - console.log(error); - }) - .finally(function () { - // always executed - }); - - - // ruleid:node_ssrf - let obj = await getJSON(req.foo); - // ruleid:node_ssrf - let buffer = await getBuffer(req.foo); - - // ruleid:node_ssrf - fetch(req.post.doo, { method: 'POST', body: 'a=1' }) - .then(res => res.json()) // expecting a json response - .then(json => console.log(json)); - -}); - -app.listen(8000); - -// do not match -needle.get(foo, function (error, response) { - if (!error && response.statusCode == 200) - console.log(response.body); -}); - - -var net = require('net'); - - -app.get('/', function (req, res) { - var client = new net.Socket(); - // ruleid:node_ssrf - client.connect(1337, req.body.host, function () { - console.log('Connected'); - client.write('Hello, server! Love, Client.'); - }); - - client.on('data', function (data) { - console.log('Received: ' + data); - client.destroy(); // kill client after server's response - }); - - client.on('close', function () { - console.log('Connection closed'); - }); - - - - - // ruleid:node_ssrf - const fk = http.get({ host: req.foo }); - req.end(); - req.once('response', (res) => { - const ip = req.socket.localAddress; - const port = req.socket.localPort; - console.log(`Your IP address is ${ip} and your source port is ${port}.`); - // Consume response object - }); - -}); \ No newline at end of file diff --git a/nodejsscan/ssrf_node.yaml b/nodejsscan/ssrf_node.yaml deleted file mode 100644 index 8b52cb8..0000000 --- a/nodejsscan/ssrf_node.yaml +++ /dev/null @@ -1,191 +0,0 @@ -rules: - - id: node_ssrf - patterns: - - pattern-either: - - pattern-inside: | - $PKG = require('request'); - ... - - pattern-inside: | - $PKG = require('axios'); - ... - - pattern-inside: | - $PKG = require('needle'); - ... - - pattern-inside: | - $PKG = require('bent'); - ... - - pattern-inside: | - $PKG = require('urllib'); - ... - - pattern-inside: | - $PKG = require('net'); - ... - - pattern-inside: | - $PKG = require('https'); - ... - - pattern-inside: | - $PKG = require('superagent'); - ... - - pattern-inside: | - $PKG = require('got'); - ... - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $PKG.get(<... $REQ.$VAR ...>, ...) - - pattern: | - $PKG.get(<... $REQ.$VAR.$FOO ...>, ...) - - pattern: | - $PKG.post(<... $REQ.$VAR ...>, ...) - - pattern: | - $PKG.post(<... $REQ.$VAR.$FOO ...>, ...) - - pattern: | - $PKG.put(<... $REQ.$VAR ...>, ...) - - pattern: | - $PKG.put(<... $REQ.$VAR.$FOO ...>, ...) - - pattern: | - needle("=~/[get|post|put|GET|POST|PUT]+/", <... $REQ.$VAR.$FOO ...>, ...) - - pattern: | - needle("=~/[get|post|put|GET|POST|PUT]+/", <... $REQ.$VAR ...>, ...) - - pattern: | - request(<... $REQ.$VAR ...>, ...) - - pattern: | - request(<... $REQ.$VAR.$FOO ...>, ...) - - pattern: | - $PKG.request(<... $REQ.$VAR ...>, ...) - - pattern: | - $PKG.request(<... $REQ.$VAR.$FOO ...>, ...) - - pattern: | - getJSON(<... $REQ.$VAR ...>, ...) - - pattern: | - getJSON(<... $REQ.$VAR.$FOO ...>, ...) - - pattern: | - getBuffer(<... $REQ.$VAR ...>, ...) - - pattern: | - getBuffer(<... $REQ.$VAR.$FOO ...>, ...) - - pattern: | - fetch(<... $REQ.$VAR ...>, ...) - - pattern: | - fetch(<... $REQ.$VAR.$FOO ...>, ...) - - pattern: | - $SOCKET.connect($PORT, <... $REQ.$VAR ...>, ...) - - pattern: | - $SOCKET.connect($PORT, <... $REQ.$VAR.$FOO ...>, ...) - - pattern: | - $PKG.get(..., {host: <... $REQ.$VAR ...>}, ...) - - pattern: | - $PKG.get(..., {host: <... $REQ.$VAR.$FOO ...>}, ...) - - pattern: | - $PKG.get(..., {hostname: <... $REQ.$VAR ...>}, ...) - - pattern: | - $PKG.get(..., {hostname: <... $REQ.$VAR.$FOO ...>}, ...) - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - $PKG.get(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR.$FOO ...>; - ... - $PKG.get(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - $PKG.post(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR.$FOO ...>; - ... - $PKG.post(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - $PKG.put(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR.$FOO ...>; - ... - $PKG.put(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR.$FOO ...>; - ... - needle("=~/[get|post|put|GET|POST|PUT]+/", <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - needle("=~/[get|post|put|GET|POST|PUT]+/", <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - request(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR.$FOO ...>; - ... - request(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - $PKG.request(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR.$FOO ...>; - ... - $PKG.request(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - getJSON(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR.$FOO ...>; - ... - getJSON(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - getBuffer(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR.$FOO ...>; - ... - getBuffer(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - fetch(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR.$FOO ...>; - ... - fetch(<... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - $SOCKET.connect($PORT, <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR.$FOO ...>; - ... - $SOCKET.connect($PORT, <... $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - $PKG.get(..., {host: <... $INP ...>}, ...); - - pattern: | - $INP = <... $REQ.$VAR.$FOO ...>; - ... - $PKG.get(..., {host: <... $INP ...>}, ...); - - pattern: | - $INP = <... $REQ.$VAR ...>; - ... - $PKG.get(..., {hostname: <... $INP ...>}, ...); - - pattern: | - $INP = <... $REQ.$VAR.$FOO ...>; - ... - $PKG.get(..., {hostname: <... $INP ...>}, ...); - message: >- - User controlled URL in http client libraries can result in Server Side - Request Forgery (SSRF). - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: 'CWE-918: Server-Side Request Forgery (SSRF)' diff --git a/nodejsscan/timing_attack_node.yaml b/nodejsscan/timing_attack_node.yaml deleted file mode 100644 index 559d4d1..0000000 --- a/nodejsscan/timing_attack_node.yaml +++ /dev/null @@ -1,494 +0,0 @@ -# Rule inspired from: https://github.com/nodesecurity/eslint-plugin-security/blob/master/rules/detect-possible-timing-attacks.js -rules: - - id: node_timing_attack - patterns: - - pattern-not: if ($Z == null) { ... }; - - pattern-not: if ($Z === null) { ... }; - - pattern-not: if ($Z != null) { ... }; - - pattern-not: if ($Z !== null) { ... }; - - pattern-not: if ($Q != undefined) { ... }; - - pattern-not: if ($Q !== undefined) { ... }; - - pattern-not: if ($Q == undefined) { ... }; - - pattern-not: if ($Q === undefined) { ... }; - - pattern-not: return $Y == null; - - pattern-not: return $Y === null; - - pattern-not: return $Y != null; - - pattern-not: return $Y !== null; - - pattern-not: return $Y == undefined; - - pattern-not: return $Y === undefined; - - pattern-not: return $Y != undefined; - - pattern-not: return $Y !== undefined; - - pattern-either: - - pattern: | - if (password == $X) { - ... - } - - pattern: | - if ($X == password) { - ... - } - - pattern: | - if (password === $X) { - ... - } - - pattern: | - if ($X === password) { - ... - } - - pattern: | - if (pass == $X) { - ... - } - - pattern: | - if ($X == pass) { - ... - } - - pattern: | - if (pass === $X) { - ... - } - - pattern: | - if ($X === pass) { - ... - } - - pattern: | - if (secret == $X) { - ... - } - - pattern: | - if ($X == secret) { - ... - } - - pattern: | - if (secret === $X) { - ... - } - - pattern: | - if ($X === secret) { - ... - } - - pattern: | - if (api == $X) { - ... - } - - pattern: | - if ($X == api) { - ... - } - - pattern: | - if (api === $X) { - ... - } - - pattern: | - if ($X === api) { - ... - } - - pattern: | - if (apiKey == $X) { - ... - } - - pattern: | - if ($X == apiKey) { - ... - } - - pattern: | - if (apiKey === $X) { - ... - } - - pattern: | - if ($X === apiKey) { - ... - } - - pattern: | - if (apiSecret == $X) { - ... - } - - pattern: | - if ($X == apiSecret) { - ... - } - - pattern: | - if (apiSecret === $X) { - ... - } - - pattern: | - if ($X === apiSecret) { - ... - } - - pattern: | - if (token == $X) { - ... - } - - pattern: | - if ($X == token) { - ... - } - - pattern: | - if (token === $X) { - ... - } - - pattern: | - if ($X === token) { - ... - } - - pattern: | - if (hash == $X) { - ... - } - - pattern: | - if ($X == hash) { - ... - } - - pattern: | - if (hash === $X) { - ... - } - - pattern: | - if ($X === hash) { - ... - } - - pattern: | - if (auth_token == $X) { - ... - } - - pattern: | - if ($X == auth_token) { - ... - } - - pattern: | - if (auth_token === $X) { - ... - } - - pattern: | - if ($X === auth_token) { - ... - } - - pattern: | - if (password != $X) { - ... - } - - pattern: | - if ($X != password) { - ... - } - - pattern: | - if (password !== $X) { - ... - } - - pattern: | - if ($X !== password) { - ... - } - - pattern: | - if (pass != $X) { - ... - } - - pattern: | - if ($X != pass) { - ... - } - - pattern: | - if (pass !== $X) { - ... - } - - pattern: | - if ($X !== pass) { - ... - } - - pattern: | - if (secret != $X) { - ... - } - - pattern: | - if ($X != secret) { - ... - } - - pattern: | - if (secret !== $X) { - ... - } - - pattern: | - if ($X !== secret) { - ... - } - - pattern: | - if (api != $X) { - ... - } - - pattern: | - if ($X != api) { - ... - } - - pattern: | - if (api !== $X) { - ... - } - - pattern: | - if ($X !== api) { - ... - } - - pattern: | - if (apiKey != $X) { - ... - } - - pattern: | - if ($X != apiKey) { - ... - } - - pattern: | - if (apiKey !== $X) { - ... - } - - pattern: | - if ($X !== apiKey) { - ... - } - - pattern: | - if (apiSecret != $X) { - ... - } - - pattern: | - if ($X != apiSecret) { - ... - } - - pattern: | - if (apiSecret !== $X) { - ... - } - - pattern: | - if ($X !== apiSecret) { - ... - } - - pattern: | - if (token != $X) { - ... - } - - pattern: | - if ($X != token) { - ... - } - - pattern: | - if (token !== $X) { - ... - } - - pattern: | - if ($X !== token) { - ... - } - - pattern: | - if (hash != $X) { - ... - } - - pattern: | - if ($X != hash) { - ... - } - - pattern: | - if (hash !== $X) { - ... - } - - pattern: | - if ($X !== hash) { - ... - } - - pattern: | - if (auth_token != $X) { - ... - } - - pattern: | - if ($X != auth_token) { - ... - } - - pattern: | - if (auth_token !== $X) { - ... - } - - pattern: | - if ($X !== auth_token) { - ... - } - - pattern: | - return $X === auth_token; - - pattern: | - return auth_token === $X; - - pattern: | - return $X === token; - - pattern: | - return token === $X; - - pattern: | - return $X === hash; - - pattern: | - return hash === $X; - - pattern: | - return $X === password; - - pattern: | - return password === $X; - - pattern: | - return $X === pass; - - pattern: | - return pass === $X; - - pattern: | - return $X === apiKey; - - pattern: | - return apiKey === $X; - - pattern: | - return $X === apiSecret; - - pattern: | - return apiSecret === $X; - - pattern: | - return $X === api_key; - - pattern: | - return api_key === $X; - - pattern: | - return $X === api_secret; - - pattern: | - return api_secret === $X; - - pattern: | - return $X === secret; - - pattern: | - return secret === $X; - - pattern: | - return $X === api; - - pattern: | - return api === $X; - - pattern: | - return $X == auth_token; - - pattern: | - return auth_token == $X; - - pattern: | - return $X == token; - - pattern: | - return token == $X; - - pattern: | - return $X == hash; - - pattern: | - return hash == $X; - - pattern: | - return $X == password; - - pattern: | - return password == $X; - - pattern: | - return $X == pass; - - pattern: | - return pass == $X; - - pattern: | - return $X == apiKey; - - pattern: | - return apiKey == $X; - - pattern: | - return $X == apiSecret; - - pattern: | - return apiSecret == $X; - - pattern: | - return $X == api_key; - - pattern: | - return api_key == $X; - - pattern: | - return $X == api_secret; - - pattern: | - return api_secret == $X; - - pattern: | - return $X == secret; - - pattern: | - return secret == $X; - - pattern: | - return $X == api; - - pattern: | - return api == $X; - - pattern: | - return $X !== auth_token; - - pattern: | - return auth_token !== $X; - - pattern: | - return $X !== token; - - pattern: | - return token !== $X; - - pattern: | - return $X !== hash; - - pattern: | - return hash !== $X; - - pattern: | - return $X !== password; - - pattern: | - return password !== $X; - - pattern: | - return $X !== pass; - - pattern: | - return pass !== $X; - - pattern: | - return $X !== apiKey; - - pattern: | - return apiKey !== $X; - - pattern: | - return $X !== apiSecret; - - pattern: | - return apiSecret !== $X; - - pattern: | - return $X !== api_key; - - pattern: | - return api_key !== $X; - - pattern: | - return $X !== api_secret; - - pattern: | - return api_secret !== $X; - - pattern: | - return $X !== secret; - - pattern: | - return secret !== $X; - - pattern: | - return $X !== api; - - pattern: | - return api !== $X; - - pattern: | - return $X != auth_token; - - pattern: | - return auth_token != $X; - - pattern: | - return $X != token; - - pattern: | - return token != $X; - - pattern: | - return $X != hash; - - pattern: | - return hash != $X; - - pattern: | - return $X != password; - - pattern: | - return password != $X; - - pattern: | - return $X != pass; - - pattern: | - return pass != $X; - - pattern: | - return $X != apiKey; - - pattern: | - return apiKey != $X; - - pattern: | - return $X != apiSecret; - - pattern: | - return apiSecret != $X; - - pattern: | - return $X != api_key; - - pattern: | - return api_key != $X; - - pattern: | - return $X != api_secret; - - pattern: | - return api_secret != $X; - - pattern: | - return $X != secret; - - pattern: | - return secret != $X; - - pattern: | - return $X != api; - - pattern: | - return api != $X; - message: >- - String comparisons using '===', '!==', '!=' and '==' is vulnerable to timing attacks. - More info: https://snyk.io/blog/node-js-timing-attack-ccc-ctf/ - languages: - - javascript - severity: WARNING - metadata: - owasp: 'A9: Using Components with Known Vulnerabilities' - cwe: 'CWE-208: Observable Timing Discrepancy' diff --git a/nodejsscan/tls_node.js b/nodejsscan/tls_node.js deleted file mode 100644 index f9555c6..0000000 --- a/nodejsscan/tls_node.js +++ /dev/null @@ -1,56 +0,0 @@ -var request = require('request'); -var use_key = 'e0ee2bc6d1979f49c6437e27b06a0101'; - -//corresponding function for each api call to tortuga gateway, allows easy calling and can store user key - -module.exports = { - - 'status': function (callback) { - // ruleid:node_tls_reject - process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; - request.get('https://dev.app.idt.net/v1/status?user_key=' + use_key, function (err, response, body) { - if (err) callback(err); - - var status = JSON.parse(body); - callback(err, status); - }) - }, - 'fund': function (json, callback) { - // ruleid:node_tls_reject - process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; - request.post({ - uri: 'https://dev.app.idt.net/v1/charges?user_key=' + use_key, - json: json, - method: 'POST' - }, - function (err, response, body) { - if (err) callback(err); - - callback(err, response); - }) - - }, -} - - -var http = require('http'); -var curl = require('node-curl'); - -http.createServer(function (request, response) { - - var url = 'https://url'; - url += request.url; - - console.log(url); - - - // ruleid:node_curl_ssl_verify_disable - curl(url, - { - SSL_VERIFYPEER: 0 - }, - function (err) { - response.end(this.body); - }) - -}).listen(8000); \ No newline at end of file diff --git a/nodejsscan/tls_node.yaml b/nodejsscan/tls_node.yaml deleted file mode 100644 index a22881e..0000000 --- a/nodejsscan/tls_node.yaml +++ /dev/null @@ -1,28 +0,0 @@ -rules: - - id: node_tls_reject - patterns: - - pattern-either: - - pattern: | - $X.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' - - pattern: | - $X.env['NODE_TLS_REJECT_UNAUTHORIZED']= '0' - message: >- - Setting 'NODE_TLS_REJECT_UNAUTHORIZED' to 0 will allow node server to - accept self signed certificates and is not an secure behaviour. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A6: Security Misconfiguration' - cwe: 'CWE-295: Improper Certificate Validation' - - id: node_curl_ssl_verify_disable - patterns: - - pattern: | - $X(..., {SSL_VERIFYPEER : 0}, ...) - message: SSL Certificate verification for node-curl is disabled. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A6: Security Misconfiguration' - cwe: 'CWE-599: Missing Validation of OpenSSL Certificate' diff --git a/nodejsscan/xml_entity_expansion.js b/nodejsscan/xml_entity_expansion.js deleted file mode 100644 index fd35d01..0000000 --- a/nodejsscan/xml_entity_expansion.js +++ /dev/null @@ -1,5 +0,0 @@ -app.get('/expat', function (req, res) { - // ruleid:node_entity_expansion - var parser = new expat.Parser(); - parser.write(req.param("xml")); -}) \ No newline at end of file diff --git a/nodejsscan/xml_entity_expansion_dos.yaml b/nodejsscan/xml_entity_expansion_dos.yaml deleted file mode 100644 index f095bdc..0000000 --- a/nodejsscan/xml_entity_expansion_dos.yaml +++ /dev/null @@ -1,31 +0,0 @@ -rules: - - id: node_entity_expansion - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $PARSER = new expat.Parser(); - ... - $PARSER.write(..., <... $REQ.$QUERY ...>, ...); - - pattern: | - $PARSER = new expat.Parser(); - ... - $PARSER.write(..., <... $REQ.$QUERY.$FOO ...>, ...); - - pattern: | - $PARSER = new expat.Parser(); - ... - $PARSER.write(..., <... $REQ.$QUERY.$FOO.$FILE ...>, ...); - message: >- - User controlled data in XML Parsers can result in - XML Internal Entity Processing vulnerabilities like in DoS. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A4: XML External Entities (XXE)' - cwe: "CWE-776: Improper Restriction of Recursive Entity References in DTDs ('XML Entity Expansion')" diff --git a/nodejsscan/xpathi_node.js b/nodejsscan/xpathi_node.js deleted file mode 100644 index 6560958..0000000 --- a/nodejsscan/xpathi_node.js +++ /dev/null @@ -1,19 +0,0 @@ -var xpath = require('xpath'); -var express = require('express'); - -var app = express(); - -app.get('/xpath', function (req, res) { - // ruleid:node_xpath_injection - var expr = xpath.parse("//persons/user[name/text()='" + req.param("name") + "']/details/text()"); - // ruleid:node_xpath_injection - expr = xpath.parse("//persons/user[name/text()='" + req.param.name + "']/details/text()"); - // ruleid:node_xpath_injection - expr = xpath.parse("//persons/user[name/text()='" + req["name"] + "']/details/text()"); - // ruleid:node_xpath_injection - var foo = req.param; - expr = xpath.parse("//persons/user[name/text()='" + foo + "']/details/text()"); - //do not match - expr = JSON.parse("{'foo':" + req.param + "}"); - res.redirect('/home') -}); diff --git a/nodejsscan/xpathi_node.yaml b/nodejsscan/xpathi_node.yaml deleted file mode 100644 index 31ecbfe..0000000 --- a/nodejsscan/xpathi_node.yaml +++ /dev/null @@ -1,57 +0,0 @@ -rules: - - id: node_xpath_injection - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $XPATH.parse(<... "=~/[\/\/].+/" + $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - $XPATH.parse(<... "=~/[\/\/].+/" + $REQ.$PARAM ...>, ...) - - pattern: | - $XPATH.parse(<... "=~/[\/\/].+/" + $REQ.$PARAM["..."] ...>, ...) - - pattern: | - $XPATH.parse(<... "=~/[\/\/].+/" + $REQ.$PARAM("...") ...>, ...) - - pattern: | - $XPATH.parse(<... "=~/[\/\/].+/" + $REQ["..."] ...>, ...) - - pattern: | - $XPATH.parse(<... "=~/[\/\/].+/" + $REQ("...") ...>, ...) - - pattern: | - $INP = <... $REQ.$QUERY.$VAR ...>; - ... - $XPATH.parse(<... "=~/[\/\/].+/" + $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$PARAM...>; - ... - $XPATH.parse(<... "=~/[\/\/].+/" + $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$PARAM["..."] ...>; - ... - $XPATH.parse(<... "=~/[\/\/].+/" + $INP ...>, ...); - - pattern: | - $INP = <... $REQ.$PARAM("...") ...>; - ... - $XPATH.parse(<... "=~/[\/\/].+/" + $INP ...>, ...); - - pattern: | - $INP = <... $REQ["..."] ...>; - ... - $XPATH.parse(<... "=~/[\/\/].+/" + $INP ...>, ...); - - pattern: | - $INP = <... $REQ("...") ...>; - ... - $XPATH.parse(<... "=~/[\/\/].+/" + $INP ...>, ...); - message: >- - User controlled data in xpath.parse() can result in XPATH injection - vulnerability. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: >- - CWE-643: Improper Neutralization of Data within XPath Expressions - ('XPath Injection') diff --git a/nodejsscan/xss_node.js b/nodejsscan/xss_node.js deleted file mode 100644 index 336848e..0000000 --- a/nodejsscan/xss_node.js +++ /dev/null @@ -1,115 +0,0 @@ - -const express = require('express') -const router = express.Router() - -router.get('/greeting', (req, res) => { - // ruleid:express_xss - const { name } = req.query; - res.send('

Hello :' + name + "

") -}) - -//template handle escaping -router.get('/greet-template', (req, res) => { - name = req.query.name - res.render('index', { user_name: name }); -}) - -module.exports = router - - -app.get('/', function (req, res) { - // ruleid:express_xss - var user = req.query.name; - - msg = "Hi " + user - res.send('Response
' + msg); -}); - - -var msg = ''; -app.get('/3', function (req, res) { - // ruleid:express_xss - var user = req.query.name; - - msg = "Hi " + user - res.send('Response
' + msg); -}); - -app.get('/2', function (req, res) { - // ruleid:express_xss - var user = { user: req.query.name }; - res.send('Response
' + user.name); -}); - -app.get('/1', function (req, res) { - // ruleid:express_xss - var user = req.query.name; - var msg = []; - msg.push(user); - res.send('Response
' + msg[0]); -}); - -app.get('/4', function (req, res) { - var user = req.query.name; - var header = ""; - var msg = 'Hi ' + user; - var footer = ""; - var output = header + msg + footer; - res.send(output); -}); - - - - - -var express = require('express'); -var app = express(); -app.get('/', function (req, res) { - // ruleid:express_xss - var resp = req.query.name; - res.send('Response
' + resp); -}); -app.get('/3', function (req, res) { - // ruleid:express_xss - var resp = req.query.name; - res.write('Response
' + resp); -}); - -app.get('/3', function (req, res) { - // ruleid:express_xss - var resp = req.foo; - var x = 1; - res.write('Response
' + resp); -}); - -app.get('/xss', function (req, res) { - // ruleid:express_xss - var html = "ASadad" + req.query.name + "Asdadads" - res.write('Response
' + html); -}); -app.get('/xss', function (req, res) { - // ruleid:express_xss - res.write('Response
' + req.query('doo')); -}); -app.get('/xss', function (req, res) { - // ruleid:express_xss - res.write('Response
' + req.query.name); -}); - -app.get('/noxss', function (req, res) { - var resp = req.query.name; - res.write('Response
'); -}); - -app.get('/noxs2s', function (req, res) { - var resp = req.query.name; - res.write('Response
' + foo); -}); - -app.get('/xss', function (req, res) { - // ruleid:express_xss - var resp = req.query.name; - var html = "ASadad" + resp + "Asdadads" - res.write('Response
' + html); -}); -app.listen(8000); \ No newline at end of file diff --git a/nodejsscan/xss_node.yaml b/nodejsscan/xss_node.yaml deleted file mode 100644 index 741fa55..0000000 --- a/nodejsscan/xss_node.yaml +++ /dev/null @@ -1,137 +0,0 @@ -rules: - - id: express_xss - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $RES.write(..., <... $REQ.$QUERY ...>, ...); - - pattern: | - $RES.write(..., <... $REQ.$QUERY.$FOO ...>, ...); - - pattern: | - $RES.send(..., <... $REQ.$QUERY ...>, ...); - - pattern: | - $RES.send(..., <... $REQ.$QUERY.$FOO ...>, ...); - - pattern: | - $LOCALVAR = <... $REQ.$QUERY ...>; - ... - $RES.write(..., <... $LOCALVAR ...>, ...); - - pattern: | - $LOCALVAR = <... $REQ.$QUERY.$FOO ...>; - ... - $RES.write(..., <... $LOCALVAR ...>, ...); - - pattern: | - $LOCALVAR = <... $REQ.$QUERY.$VAR ...>; - ... - $RES.send(..., <... $LOCALVAR ...>, ...); - - pattern: | - $LOCALVAR = <... $REQ.$QUERY ...>; - ... - $RES.send(..., <... $LOCALVAR ...>, ...); - - pattern: | - var {$LOCALVAR} = <... $REQ.$QUERY.$FOO ...>; - ... - $RES.write(..., <... $LOCALVAR ...>, ...); - - pattern: | - var {$LOCALVAR} = <... $REQ.$QUERY.$VAR ...>; - ... - $RES.send(..., <... $LOCALVAR ...>, ...); - - pattern: | - var {$LOCALVAR} = <... $REQ.$QUERY ...>; - ... - $RES.send(..., <... $LOCALVAR ...>, ...); - - pattern: | - $LOCALVAR = {$KEY: <... $REQ.$QUERY ...>}; - ... - $RES.write(..., <... $LOCALVAR ...>, ...); - - pattern: | - $LOCALVAR = {$KEY: <... $REQ.$QUERY.$FOO ...>}; - ... - $RES.write(..., <... $LOCALVAR ...>, ...); - - pattern: | - $LOCALVAR = {$KEY: <... $REQ.$QUERY.$VAR ...>}; - ... - $RES.send(..., <... $LOCALVAR ...>, ...); - - pattern: | - $LOCALVAR = {$KEY: <... $REQ.$QUERY ...>}; - ... - $RES.send(..., <... $LOCALVAR ...>, ...); - - pattern: | - $LOCALVAR.push(<... $REQ.$QUERY ...>); - ... - $RES.write(..., <... $LOCALVAR ...>, ...); - - pattern: | - $LOCALVAR.push(<... $REQ.$QUERY.$FOO ...>); - ... - $RES.write(..., <... $LOCALVAR ...>, ...); - - pattern: | - $LOCALVAR.push(<... $REQ.$QUERY.$VAR ...>); - ... - $RES.send(..., <... $LOCALVAR ...>, ...); - - pattern: | - $LOCALVAR.push(<... $REQ.$QUERY ...>); - ... - $RES.send(..., <... $LOCALVAR ...>, ...); - - pattern: | - $LOCALVAR = <... $REQ.$QUERY ...>; - ... - $ARR.push(<...$LOCALVAR...>); - ... - $RES.write(..., <... $ARR ...>, ...); - - pattern: | - $LOCALVAR = <... $REQ.$QUERY.$FOO ...>; - ... - $ARR.push(<...$LOCALVAR...>); - ... - $RES.write(..., <... $ARR ...>, ...); - - pattern: | - $LOCALVAR = <... $REQ.$QUERY.$VAR ...>; - ... - $ARR.push(<...$LOCALVAR...>); - ... - $RES.send(..., <... $ARR ...>, ...); - - pattern: | - $LOCALVAR = <... $REQ.$QUERY ...>; - ... - $ARR.push(<...$LOCALVAR...>); - ... - $RES.send(..., <... $ARR ...>, ...); - - pattern: | - $LOCALVAR = <... $REQ.$QUERY ...>; - ... - $OUT = <... $LOCALVAR ...>; - ... - $RES.write(..., <... $OUT ...>, ...); - - pattern: | - $LOCALVAR = <... $REQ.$QUERY.$FOO ...>; - ... - $OUT = <... $LOCALVAR ...>; - ... - $RES.write(..., <... $OUT ...>, ...); - - pattern: | - $LOCALVAR = <... $REQ.$QUERY.$VAR ...>; - ... - $OUT = <... $LOCALVAR ...>; - ... - $RES.send(..., <... $OUT ...>, ...); - - pattern: | - $LOCALVAR = <... $REQ.$QUERY ...>; - ... - $OUT = <... $LOCALVAR ...>; - ... - $RES.send(..., <... $OUT ...>, ...); - message: >- - Untrusted User Input in Response will result in Reflected Cross Site - Scripting Vulnerability. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: >- - CWE-79: Improper Neutralization of Input During Web Page Generation - ('Cross-site Scripting') diff --git a/nodejsscan/xss_templates.js b/nodejsscan/xss_templates.js deleted file mode 100644 index ccc132a..0000000 --- a/nodejsscan/xss_templates.js +++ /dev/null @@ -1,25 +0,0 @@ -function name() { - var x = '

hell0

' - // ruleid:handlebars_safestring - var y = new Handlebars.SafeString(x); - // ruleid:handlebars_safestring - return new Handlebars.SafeString(''); -} - -function test2() { - var x = 'foooo' - var z = new Handlebars; - // ruleid:handlebars_safestring - var xx = z.SafeString(x) - return xx; -} - - -// ruleid:handlebars_noescape -var template = Handlebars.compile(source, { noEscape: true }); -var template = "This is {{target}}"; -var target = "user's pictures"; -// ruleid:handlebars_noescape -var result = Handlerbars.compile(template, { noEscape: true })({ target: target }); -// ruleid:squirrelly_autoescape -Sqrl.autoEscaping(false) \ No newline at end of file diff --git a/nodejsscan/xss_templates.yaml b/nodejsscan/xss_templates.yaml deleted file mode 100644 index ef6b5d5..0000000 --- a/nodejsscan/xss_templates.yaml +++ /dev/null @@ -1,44 +0,0 @@ -rules: - - id: handlebars_safestring - pattern-either: - - pattern: $X.SafeString(...) - - pattern: new Handlebars.SafeString(...) - message: >- - Handlebars SafeString will not escape the data passed through it. - Untrusted user input passing through SafeString can cause XSS. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: >- - CWE-79: Improper Neutralization of Input During Web Page Generation - ('Cross-site Scripting') - - id: handlebars_noescape - patterns: - - pattern: | - $X.compile(..., {noEscape: true}, ...) - message: >- - Disabling Escaping in Handlebars is not a secure behaviour. This can - introduce XSS vulnerabilties. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: >- - CWE-80: Improper Neutralization of Script-Related HTML Tags in a Web - Page (Basic XSS) - - id: squirrelly_autoescape - pattern: $X.autoEscaping(false) - message: >- - Handlebars SafeString will not escape the data passed through it. - Untrusted user input passing through SafeString can cause XSS. - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A1: Injection' - cwe: >- - CWE-79: Improper Neutralization of Input During Web Page Generation - ('Cross-site Scripting') \ No newline at end of file diff --git a/nodejsscan/xxe_node.js b/nodejsscan/xxe_node.js deleted file mode 100644 index f440c82..0000000 --- a/nodejsscan/xxe_node.js +++ /dev/null @@ -1,57 +0,0 @@ - -const libxmljs = require('libxmljs'); - -app.get('/noent', function (req, res) { - // entity expansion - // ruleid:node_xxe - libxmljs.parseXml(req.param("xml"), { noent: true }); -}); - - -app.get('/sax', function (req, res) { - // SAX parser expands external entities - // ruleid:node_xxe - const parser = new libxmljs.SaxParser(); - const x = 1 - parser.parseString(req.param("xml")); -}); - - -app.get('/saxpush/parser', function (req, res) { - // SAX parser expands external entities - // ruleid:node_xxe - const parser = new libxmljs.SaxPushParser(); - const x = 1 - parser.push(req.param("some-xml")); -}); - - -app.get('/sax', function (req, res) { - // SAX parser expands external entities - const parser = new libxmljs.SaxParser(); - const x = 1 - // ruleid:node_xxe - var products = parser.parseXmlString(req.files.products.data, { noent: true, noblanks: true }) -}) - -const express = require('express') -const libxmljs = require('libxml') -const db = require('db'); -const router = express.Router() - -router.post('/upload-products', (req, res) => { - // ruleid:node_xxe - const XMLfile = req.files.products.data; - const products = libxmljs.parseXmlString(XMLfile, { noent: true, noblanks: true }) - - products.root().childNodes().forEach(product => { - let newProduct = new db.Product() - newProduct.name = product.childNodes()[0].text() - newProduct.description = product.childNodes()[3].text() - newProduct.save() - }); - - res.send('Thanks') -}) - -module.exports = router \ No newline at end of file diff --git a/nodejsscan/xxe_node.yaml b/nodejsscan/xxe_node.yaml deleted file mode 100644 index d1b1c09..0000000 --- a/nodejsscan/xxe_node.yaml +++ /dev/null @@ -1,110 +0,0 @@ -rules: - - id: node_xxe - patterns: - - pattern-either: - - pattern-inside: function ($REQ, $RES, ...) {...} - - pattern-inside: function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} - - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - - pattern-either: - - pattern: | - $LIBXML.parseXmlString(..., <... $REQ.$QUERY.$VAR.$FILE ...>, ...) - - pattern: | - $LIBXML.parseXmlString(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - $LIBXML.parseXmlString(..., <... $REQ.$QUERY ...>, ...) - - pattern: > - $FOO = <... $REQ.$QUERY.$VAR.$FILE ...>; ... - $LIBXML.parseXmlString(..., <... $FOO ...>, ...); - - pattern: > - $FOO = <... $REQ.$QUERY.$VAR ...>; ... $LIBXML.parseXmlString(..., - <... $FOO ...>, ...); - - pattern: > - $FOO = <... $REQ.$QUERY ...>; ... $LIBXML.parseXmlString(..., <... - $FOO ...>, ...); - - pattern: | - $LIBXML.parseXml(..., <... $REQ.$QUERY.$VAR.$FILE ...>, ...) - - pattern: | - $LIBXML.parseXml(..., <... $REQ.$QUERY.$VAR ...>, ...) - - pattern: | - $LIBXML.parseXml(..., <... $REQ.$QUERY ...>, ...) - - pattern: > - $FOO = <... $REQ.$QUERY.$VAR.$FILE ...>; ... $LIBXML.parseXml(..., - <... $FOO ...>, ...); - - pattern: > - $FOO = <... $REQ.$QUERY.$VAR ...>; ... $LIBXML.parseXml(..., <... - $FOO ...>, ...); - - pattern: | - $FOO = <... $REQ.$QUERY ...>; - ... - $LIBXML.parseXml(..., <... $FOO ...>, ...); - - pattern: | - $PARSER = new libxmljs.SaxParser(); - ... - $PARSER.parseString(..., <... $REQ.$QUERY ...>, ...); - - pattern: | - $PARSER = new libxmljs.SaxParser(); - ... - $PARSER.parseString(..., <... $REQ.$QUERY.$BAR ...>, ...); - - pattern: | - $PARSER = new libxmljs.SaxParser(); - ... - $PARSER.parseString(..., <... $REQ.$QUERY.$BAR.$FILE ...>, ...); - - pattern: | - $PARSER = new libxmljs.SaxPushParser(); - ... - $PARSER.push(..., <... $REQ.$QUERY ...>, ...); - - pattern: | - $PARSER = new libxmljs.SaxPushParser(); - ... - $PARSER.push(..., <... $REQ.$QUERY.$FOO ...> , ...); - - pattern: | - $PARSER = new libxmljs.SaxPushParser(); - ... - $PARSER.push(..., <... $REQ.$QUERY.$FOO.$FILE ...> , ...); - - pattern: | - $PARSER = new libxmljs.SaxParser(); - ... - $FOO = <... $REQ.$QUERY ...>; - ... - $PARSER.parseString(..., <... $FOO ...>, ...); - - pattern: | - $PARSER = new libxmljs.SaxParser(); - ... - $FOO = <... $REQ.$QUERY.$BAR ...>; - ... - $PARSER.parseString(..., <... $FOO ...>, ...); - - pattern: | - $PARSER = new libxmljs.SaxParser(); - ... - $FOO = <... $REQ.$QUERY.$BAR.$FILE ...>; - ... - $PARSER.parseString(..., <... $FOO ...>, ...); - - pattern: | - $PARSER = new libxmljs.SaxPushParser(); - ... - $FOO = <... $REQ.$QUERY ...>; - ... - $PARSER.push(..., <... $FOO ...>, ...); - - pattern: | - $PARSER = new libxmljs.SaxPushParser(); - ... - $FOO = <... $REQ.$QUERY.$BAR ...>; - ... - $PARSER.push(..., <... $FOO ...> , ...); - - pattern: | - $PARSER = new libxmljs.SaxPushParser(); - ... - $FOO = <... $REQ.$QUERY.$BAR.$FILE ...>; - ... - $PARSER.push(..., <... $FOO ...> , ...); - message: >- - User controlled data in XML parsers can result in XML External or Internal - Entity (XXE) Processing vulnerabilities - languages: - - javascript - severity: ERROR - metadata: - owasp: 'A4: XML External Entities (XXE)' - cwe: 'CWE-611: Improper Restriction of XML External Entity Reference' From 2edbc6210a3aa4679a47163dc2848d410d68851b Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Sat, 26 Sep 2020 21:53:55 -0400 Subject: [PATCH 2/3] add rules --- rules/crypto/crypto_node.js | 53 ++ rules/crypto/crypto_node.yaml | 72 ++ rules/crypto/crypto_timing_attacks.js | 56 ++ rules/crypto/timing_attack_node.yaml | 494 ++++++++++++ rules/crypto/tls_node.js | 56 ++ rules/crypto/tls_node.yaml | 28 + rules/database/nosql_find_injection.js | 29 + rules/database/nosql_find_injection.yaml | 80 ++ rules/database/nosql_injection.js | 53 ++ rules/database/nosql_injection.yaml | 69 ++ rules/database/sql_injection.yaml | 39 + rules/database/sqli_node.js | 66 ++ rules/dos/express_bodyparser_dos.js | 16 + rules/dos/express_bodyparser_dos.yaml | 19 + rules/dos/layer7_object_dos.js | 31 + rules/dos/layer7_object_dos.yaml | 28 + rules/dos/regex_dos.js | 56 ++ rules/dos/regex_dos.yaml | 67 ++ rules/dos/regex_injection.yaml | 67 ++ rules/dos/regex_injection_dos.js | 11 + rules/electronjs/security_electron.js | 65 ++ rules/electronjs/security_electronjs.yaml | 99 +++ rules/eval/eval_deserialize.js | 31 + rules/eval/eval_deserialize.yaml | 33 + rules/eval/eval_drpc_deserialize.yaml | 23 + rules/eval/eval_grpc_insecure.js | 45 ++ rules/eval/eval_node.js | 19 + rules/eval/eval_node.yaml | 69 ++ rules/eval/eval_require.js | 38 + rules/eval/eval_require.yaml | 29 + rules/eval/eval_sandbox.js | 36 + rules/eval/eval_sandbox.yaml | 44 ++ rules/eval/eval_vm2_injection.js | 89 +++ rules/eval/eval_vm2_injection.yaml | 257 +++++++ rules/eval/eval_vm_injection.js | 87 +++ rules/eval/eval_vm_injection.yaml | 331 ++++++++ rules/eval/eval_yaml_deserialize.js | 14 + rules/eval/eval_yaml_deserialize.yaml | 17 + rules/eval/server_side_template_injection.js | 45 ++ .../eval/server_side_template_injection.yaml | 78 ++ rules/exec/exec_os_command.js | 118 +++ rules/exec/exec_os_command.yaml | 91 +++ rules/exec/exec_shelljs.js | 14 + rules/exec/exec_shelljs.yaml | 36 + rules/generic/error_disclosure.yaml | 51 ++ rules/generic/error_info_disclosure.js | 23 + rules/generic/hardcoded_passport.js | 238 ++++++ rules/generic/hardcoded_passport.yaml | 716 ++++++++++++++++++ rules/generic/hardcoded_secrets.js | 51 ++ rules/generic/hardcoded_secrets.yaml | 87 +++ rules/generic/logic_bypass.yaml | 55 ++ rules/generic/logic_user_controlled_checks.js | 10 + rules/good/good_anti_csrf.yaml | 19 + rules/good/good_helmet_checks.yaml | 236 ++++++ rules/good/good_ratelimiting.yaml | 14 + rules/good/header_helmet_disabled.js | 10 + rules/headers/header_cookie.js | 131 ++++ rules/headers/header_cookie.yaml | 382 ++++++++++ rules/headers/header_cors_star.js | 41 + rules/headers/header_cors_star.yaml | 70 ++ rules/headers/header_helmet_disabled.js | 10 + rules/headers/header_helmet_disabled.yaml | 36 + rules/headers/header_injection.js | 63 ++ rules/headers/header_injection.yaml | 55 ++ rules/headers/header_xss_protection.js | 52 ++ rules/headers/header_xss_protection.yaml | 68 ++ rules/headers/host_header_injection.js | 54 ++ rules/headers/host_header_injection.yaml | 55 ++ rules/jwt/hardcoded_jwt.js | 119 +++ rules/jwt/hardcoded_jwt_express.js | 24 + rules/jwt/jwt_exposed_credentials.js | 114 +++ rules/jwt/jwt_exposed_credentials.yaml | 221 ++++++ rules/jwt/jwt_exposed_data.js | 8 + rules/jwt/jwt_exposed_data.yaml | 27 + rules/jwt/jwt_express_hardcoded.yaml | 32 + rules/jwt/jwt_hardcoded.yaml | 74 ++ rules/jwt/jwt_none_algorithm.js | 14 + rules/jwt/jwt_none_algorithm.yaml | 45 ++ rules/jwt/jwt_not_revoked.js | 16 + rules/jwt/jwt_not_revoked.yaml | 21 + rules/memory/buffer_noassert.js | 2 + rules/memory/buffer_noassert.yaml | 43 ++ rules/redirect/open_redirect.js | 130 ++++ rules/redirect/open_redirect.yaml | 83 ++ rules/ssrf/ssrf_node.js | 143 ++++ rules/ssrf/ssrf_node.yaml | 191 +++++ rules/ssrf/ssrf_phantomjs.js | 122 +++ rules/ssrf/ssrf_phantomjs.yaml | 71 ++ rules/ssrf/ssrf_playwright.js | 58 ++ rules/ssrf/ssrf_playwright.yaml | 101 +++ rules/ssrf/ssrf_puppeteer.js | 69 ++ rules/ssrf/ssrf_puppeteer.yaml | 101 +++ rules/ssrf/ssrf_wkhtmltoimage.js | 18 + rules/ssrf/ssrf_wkhtmltoimage.yaml | 34 + rules/ssrf/ssrf_wkhtmltopdf.js | 17 + rules/ssrf/ssrf_wkhtmltopdf.yaml | 34 + rules/traversal/archive_path_overwrite.js | 82 ++ rules/traversal/archive_path_overwrite.yaml | 158 ++++ rules/traversal/path_traversal.js | 40 + rules/traversal/path_traversal.yaml | 128 ++++ .../traversal/path_traversal_join_resolve.js | 27 + rules/traversal/resolve_path_traversal.yaml | 44 ++ rules/xml/xml_entity_expansion.js | 5 + rules/xml/xml_entity_expansion_dos.yaml | 31 + rules/xml/xpathi_node.js | 19 + rules/xml/xpathi_node.yaml | 57 ++ rules/xml/xxe_expat.js | 28 + rules/xml/xxe_expat.yaml | 49 ++ rules/xml/xxe_node.js | 57 ++ rules/xml/xxe_node.yaml | 110 +++ rules/xml/xxe_sax.js | 38 + rules/xml/xxe_sax.yaml | 23 + rules/xml/xxe_xml2json.js | 28 + rules/xml/xxe_xml2json.yaml | 32 + rules/xss/xss_mustache_escape.yaml | 12 + rules/xss/xss_mustache_escape_disabled.js | 2 + rules/xss/xss_node.js | 115 +++ rules/xss/xss_node.yaml | 137 ++++ rules/xss/xss_serialize_js.yaml | 22 + rules/xss/xss_serializejs.js | 13 + rules/xss/xss_templates.js | 25 + rules/xss/xss_templates.yaml | 44 ++ 122 files changed, 8883 insertions(+) create mode 100644 rules/crypto/crypto_node.js create mode 100644 rules/crypto/crypto_node.yaml create mode 100644 rules/crypto/crypto_timing_attacks.js create mode 100644 rules/crypto/timing_attack_node.yaml create mode 100644 rules/crypto/tls_node.js create mode 100644 rules/crypto/tls_node.yaml create mode 100644 rules/database/nosql_find_injection.js create mode 100644 rules/database/nosql_find_injection.yaml create mode 100644 rules/database/nosql_injection.js create mode 100644 rules/database/nosql_injection.yaml create mode 100644 rules/database/sql_injection.yaml create mode 100644 rules/database/sqli_node.js create mode 100644 rules/dos/express_bodyparser_dos.js create mode 100644 rules/dos/express_bodyparser_dos.yaml create mode 100644 rules/dos/layer7_object_dos.js create mode 100644 rules/dos/layer7_object_dos.yaml create mode 100644 rules/dos/regex_dos.js create mode 100644 rules/dos/regex_dos.yaml create mode 100644 rules/dos/regex_injection.yaml create mode 100644 rules/dos/regex_injection_dos.js create mode 100644 rules/electronjs/security_electron.js create mode 100644 rules/electronjs/security_electronjs.yaml create mode 100644 rules/eval/eval_deserialize.js create mode 100644 rules/eval/eval_deserialize.yaml create mode 100644 rules/eval/eval_drpc_deserialize.yaml create mode 100644 rules/eval/eval_grpc_insecure.js create mode 100644 rules/eval/eval_node.js create mode 100644 rules/eval/eval_node.yaml create mode 100644 rules/eval/eval_require.js create mode 100644 rules/eval/eval_require.yaml create mode 100644 rules/eval/eval_sandbox.js create mode 100644 rules/eval/eval_sandbox.yaml create mode 100644 rules/eval/eval_vm2_injection.js create mode 100644 rules/eval/eval_vm2_injection.yaml create mode 100644 rules/eval/eval_vm_injection.js create mode 100644 rules/eval/eval_vm_injection.yaml create mode 100644 rules/eval/eval_yaml_deserialize.js create mode 100644 rules/eval/eval_yaml_deserialize.yaml create mode 100644 rules/eval/server_side_template_injection.js create mode 100644 rules/eval/server_side_template_injection.yaml create mode 100644 rules/exec/exec_os_command.js create mode 100644 rules/exec/exec_os_command.yaml create mode 100644 rules/exec/exec_shelljs.js create mode 100644 rules/exec/exec_shelljs.yaml create mode 100644 rules/generic/error_disclosure.yaml create mode 100644 rules/generic/error_info_disclosure.js create mode 100644 rules/generic/hardcoded_passport.js create mode 100644 rules/generic/hardcoded_passport.yaml create mode 100644 rules/generic/hardcoded_secrets.js create mode 100644 rules/generic/hardcoded_secrets.yaml create mode 100644 rules/generic/logic_bypass.yaml create mode 100644 rules/generic/logic_user_controlled_checks.js create mode 100644 rules/good/good_anti_csrf.yaml create mode 100644 rules/good/good_helmet_checks.yaml create mode 100644 rules/good/good_ratelimiting.yaml create mode 100644 rules/good/header_helmet_disabled.js create mode 100644 rules/headers/header_cookie.js create mode 100644 rules/headers/header_cookie.yaml create mode 100644 rules/headers/header_cors_star.js create mode 100644 rules/headers/header_cors_star.yaml create mode 100644 rules/headers/header_helmet_disabled.js create mode 100644 rules/headers/header_helmet_disabled.yaml create mode 100644 rules/headers/header_injection.js create mode 100644 rules/headers/header_injection.yaml create mode 100644 rules/headers/header_xss_protection.js create mode 100644 rules/headers/header_xss_protection.yaml create mode 100644 rules/headers/host_header_injection.js create mode 100644 rules/headers/host_header_injection.yaml create mode 100644 rules/jwt/hardcoded_jwt.js create mode 100644 rules/jwt/hardcoded_jwt_express.js create mode 100644 rules/jwt/jwt_exposed_credentials.js create mode 100644 rules/jwt/jwt_exposed_credentials.yaml create mode 100644 rules/jwt/jwt_exposed_data.js create mode 100644 rules/jwt/jwt_exposed_data.yaml create mode 100644 rules/jwt/jwt_express_hardcoded.yaml create mode 100644 rules/jwt/jwt_hardcoded.yaml create mode 100644 rules/jwt/jwt_none_algorithm.js create mode 100644 rules/jwt/jwt_none_algorithm.yaml create mode 100644 rules/jwt/jwt_not_revoked.js create mode 100644 rules/jwt/jwt_not_revoked.yaml create mode 100644 rules/memory/buffer_noassert.js create mode 100644 rules/memory/buffer_noassert.yaml create mode 100644 rules/redirect/open_redirect.js create mode 100644 rules/redirect/open_redirect.yaml create mode 100644 rules/ssrf/ssrf_node.js create mode 100644 rules/ssrf/ssrf_node.yaml create mode 100644 rules/ssrf/ssrf_phantomjs.js create mode 100644 rules/ssrf/ssrf_phantomjs.yaml create mode 100644 rules/ssrf/ssrf_playwright.js create mode 100644 rules/ssrf/ssrf_playwright.yaml create mode 100644 rules/ssrf/ssrf_puppeteer.js create mode 100644 rules/ssrf/ssrf_puppeteer.yaml create mode 100644 rules/ssrf/ssrf_wkhtmltoimage.js create mode 100644 rules/ssrf/ssrf_wkhtmltoimage.yaml create mode 100644 rules/ssrf/ssrf_wkhtmltopdf.js create mode 100644 rules/ssrf/ssrf_wkhtmltopdf.yaml create mode 100644 rules/traversal/archive_path_overwrite.js create mode 100644 rules/traversal/archive_path_overwrite.yaml create mode 100644 rules/traversal/path_traversal.js create mode 100644 rules/traversal/path_traversal.yaml create mode 100644 rules/traversal/path_traversal_join_resolve.js create mode 100644 rules/traversal/resolve_path_traversal.yaml create mode 100644 rules/xml/xml_entity_expansion.js create mode 100644 rules/xml/xml_entity_expansion_dos.yaml create mode 100644 rules/xml/xpathi_node.js create mode 100644 rules/xml/xpathi_node.yaml create mode 100644 rules/xml/xxe_expat.js create mode 100644 rules/xml/xxe_expat.yaml create mode 100644 rules/xml/xxe_node.js create mode 100644 rules/xml/xxe_node.yaml create mode 100644 rules/xml/xxe_sax.js create mode 100644 rules/xml/xxe_sax.yaml create mode 100644 rules/xml/xxe_xml2json.js create mode 100644 rules/xml/xxe_xml2json.yaml create mode 100644 rules/xss/xss_mustache_escape.yaml create mode 100644 rules/xss/xss_mustache_escape_disabled.js create mode 100644 rules/xss/xss_node.js create mode 100644 rules/xss/xss_node.yaml create mode 100644 rules/xss/xss_serialize_js.yaml create mode 100644 rules/xss/xss_serializejs.js create mode 100644 rules/xss/xss_templates.js create mode 100644 rules/xss/xss_templates.yaml diff --git a/rules/crypto/crypto_node.js b/rules/crypto/crypto_node.js new file mode 100644 index 0000000..cbce6ce --- /dev/null +++ b/rules/crypto/crypto_node.js @@ -0,0 +1,53 @@ +var key = new Buffer('8CBDEC62EB4DCA778F842B02503011B2', 'hex') +var src = new Buffer('0002123401010100000000000000c631', 'hex') +// ruleid:node_aes_ecb +cipher = crypto.createCipheriv("aes-128-ecb", key, '') +cipher.setAutoPadding(false) +result = cipher.update(src).toString('hex'); +result += cipher.final().toString('hex'); +"result : " + result + +// ruleid:node_sha1 +require("crypto") + .createHash("sha1") + .update("Man oh man do I love node!") + .digest("hex"); + +// ruleid:node_md5 +require("crypto") + .createHash("md5") + .update("Man oh man do I love node!") + .digest("hex"); + +function encrypt(text) { + let iv = crypto.randomBytes(IV_LENGTH); + // ruleid:node_aes_ecb + let cipher = crypto.createCipheriv('aes-256-ecb', Buffer.from(ENCRYPTION_KEY), iv); + // ruleid:node_aes_ecb + let cipher = crypto.createCipheriv('aes-192-ecb', Buffer.from(ENCRYPTION_KEY), iv); + // ruleid:node_aes_ecb + let cipher = crypto.createCipheriv('aes-128-ecb', Buffer.from(ENCRYPTION_KEY), iv); + let encrypted = cipher.update(text); + + encrypted = Buffer.concat([encrypted, cipher.final()]); + + return iv.toString('hex') + ':' + encrypted.toString('hex'); +} + +function decrypt(text) { + let textParts = text.split(':'); + let iv = Buffer.from(textParts.shift(), 'hex'); + let encryptedText = Buffer.from(textParts.join(':'), 'hex'); + // ruleid:node_aes_ecb + let decipher = crypto.createDecipheriv('aes-128-ecb', Buffer.from(ENCRYPTION_KEY), iv); + let decrypted = decipher.update(encryptedText); +} + +// ruleid:node_insecure_random_generator +crypto.pseudoRandomBytes(1); // +//Math based random insecure +// ruleid:node_insecure_random_generator +const val = Math.random(); + +// ruleid:node_weak_crypto +var des = crypto.createCipher('des', key); \ No newline at end of file diff --git a/rules/crypto/crypto_node.yaml b/rules/crypto/crypto_node.yaml new file mode 100644 index 0000000..f021b65 --- /dev/null +++ b/rules/crypto/crypto_node.yaml @@ -0,0 +1,72 @@ +rules: + - id: node_md5 + patterns: + - pattern: | + $X.createHash("md5") + message: >- + MD5 is a a weak hash which is known to have collision. Use a strong + hashing function. + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A9: Using Components with Known Vulnerabilities' + cwe: 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' + - id: node_sha1 + patterns: + - pattern: | + $X.createHash("sha1") + message: >- + SHA1 is a a weak hash which is known to have collision. Use a strong + hashing function. + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A9: Using Components with Known Vulnerabilities' + cwe: 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' + - id: node_aes_ecb + patterns: + - pattern-either: + - pattern: | + $X.createCipheriv("=~/^aes-([0-9]+)-ecb$/", ...) + - pattern: | + $X.createDecipheriv("=~/^aes-([0-9]+)-ecb$/", ...) + message: >- + AES with ECB mode is deterministic in nature and not suitable for + encrypting large amount of repetitive data. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A9: Using Components with Known Vulnerabilities' + cwe: 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' + - id: node_weak_crypto + patterns: + - pattern-either: + - pattern: | + $X.createCipher('des', ...) + message: >- + A weak or broken cryptographic algorithm was identified. Using these + functions will introduce vulnerabilities or downgrade the security of your application. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A9: Using Components with Known Vulnerabilities' + cwe: 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' + - id: node_insecure_random_generator + patterns: + - pattern-either: + - pattern: | + $X.pseudoRandomBytes(...) + - pattern: | + Math.random(...) + message: >- + crypto.pseudoRandomBytes()/Math.random() is a cryptographically weak random number generator. + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A9: Using Components with Known Vulnerabilities' + cwe: 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' diff --git a/rules/crypto/crypto_timing_attacks.js b/rules/crypto/crypto_timing_attacks.js new file mode 100644 index 0000000..b7b8b0a --- /dev/null +++ b/rules/crypto/crypto_timing_attacks.js @@ -0,0 +1,56 @@ +if (name == 'test') { + acces = 1; +} + +// ruleid:node_timing_attack +if (password == 'mypass') { + correct = 1; +} + +// ruleid:node_timing_attack +if ('test' == password) { + correct = 2; +} + +// ruleid:node_timing_attack +if ('test' === password) { + correct = 2; +} + +// ruleid:node_timing_attack +if (password == test) + x = 1; + + + +// https://stackoverflow.com/a/47518578/2927282 +import { pbkdf2Sync, randomBytes } from 'crypto'; + +export class Auth { + iters = 1e1; // TODO: increase later + keylen = 64; + digest = 'sha512'; + + create(password) { + const salt = randomBytes(128).toString('base64'); // <- salt + // salt was not base64 before being used by pbkdf2 + + const hash = pbkdf2Sync(password, salt, this.iters, this.keylen, this.digest).toString('base64'); + + return [salt, hash, this.iters].join('::'); + } + + verify(stored, password) { + const [salt, hash, iters] = stored.split('::'); + const verify = pbkdf2Sync(password, salt, parseInt(iters, 10), this.keylen, this.digest); + + // ruleid:node_timing_attack + return hash === verify.toString('base64'); + } +} + +function isAuthenticated(user, token) { + var correctToken = FetchUserTokenFromDB(user); + // ruleid:node_timing_attack + return token === correctToken; +} \ No newline at end of file diff --git a/rules/crypto/timing_attack_node.yaml b/rules/crypto/timing_attack_node.yaml new file mode 100644 index 0000000..559d4d1 --- /dev/null +++ b/rules/crypto/timing_attack_node.yaml @@ -0,0 +1,494 @@ +# Rule inspired from: https://github.com/nodesecurity/eslint-plugin-security/blob/master/rules/detect-possible-timing-attacks.js +rules: + - id: node_timing_attack + patterns: + - pattern-not: if ($Z == null) { ... }; + - pattern-not: if ($Z === null) { ... }; + - pattern-not: if ($Z != null) { ... }; + - pattern-not: if ($Z !== null) { ... }; + - pattern-not: if ($Q != undefined) { ... }; + - pattern-not: if ($Q !== undefined) { ... }; + - pattern-not: if ($Q == undefined) { ... }; + - pattern-not: if ($Q === undefined) { ... }; + - pattern-not: return $Y == null; + - pattern-not: return $Y === null; + - pattern-not: return $Y != null; + - pattern-not: return $Y !== null; + - pattern-not: return $Y == undefined; + - pattern-not: return $Y === undefined; + - pattern-not: return $Y != undefined; + - pattern-not: return $Y !== undefined; + - pattern-either: + - pattern: | + if (password == $X) { + ... + } + - pattern: | + if ($X == password) { + ... + } + - pattern: | + if (password === $X) { + ... + } + - pattern: | + if ($X === password) { + ... + } + - pattern: | + if (pass == $X) { + ... + } + - pattern: | + if ($X == pass) { + ... + } + - pattern: | + if (pass === $X) { + ... + } + - pattern: | + if ($X === pass) { + ... + } + - pattern: | + if (secret == $X) { + ... + } + - pattern: | + if ($X == secret) { + ... + } + - pattern: | + if (secret === $X) { + ... + } + - pattern: | + if ($X === secret) { + ... + } + - pattern: | + if (api == $X) { + ... + } + - pattern: | + if ($X == api) { + ... + } + - pattern: | + if (api === $X) { + ... + } + - pattern: | + if ($X === api) { + ... + } + - pattern: | + if (apiKey == $X) { + ... + } + - pattern: | + if ($X == apiKey) { + ... + } + - pattern: | + if (apiKey === $X) { + ... + } + - pattern: | + if ($X === apiKey) { + ... + } + - pattern: | + if (apiSecret == $X) { + ... + } + - pattern: | + if ($X == apiSecret) { + ... + } + - pattern: | + if (apiSecret === $X) { + ... + } + - pattern: | + if ($X === apiSecret) { + ... + } + - pattern: | + if (token == $X) { + ... + } + - pattern: | + if ($X == token) { + ... + } + - pattern: | + if (token === $X) { + ... + } + - pattern: | + if ($X === token) { + ... + } + - pattern: | + if (hash == $X) { + ... + } + - pattern: | + if ($X == hash) { + ... + } + - pattern: | + if (hash === $X) { + ... + } + - pattern: | + if ($X === hash) { + ... + } + - pattern: | + if (auth_token == $X) { + ... + } + - pattern: | + if ($X == auth_token) { + ... + } + - pattern: | + if (auth_token === $X) { + ... + } + - pattern: | + if ($X === auth_token) { + ... + } + - pattern: | + if (password != $X) { + ... + } + - pattern: | + if ($X != password) { + ... + } + - pattern: | + if (password !== $X) { + ... + } + - pattern: | + if ($X !== password) { + ... + } + - pattern: | + if (pass != $X) { + ... + } + - pattern: | + if ($X != pass) { + ... + } + - pattern: | + if (pass !== $X) { + ... + } + - pattern: | + if ($X !== pass) { + ... + } + - pattern: | + if (secret != $X) { + ... + } + - pattern: | + if ($X != secret) { + ... + } + - pattern: | + if (secret !== $X) { + ... + } + - pattern: | + if ($X !== secret) { + ... + } + - pattern: | + if (api != $X) { + ... + } + - pattern: | + if ($X != api) { + ... + } + - pattern: | + if (api !== $X) { + ... + } + - pattern: | + if ($X !== api) { + ... + } + - pattern: | + if (apiKey != $X) { + ... + } + - pattern: | + if ($X != apiKey) { + ... + } + - pattern: | + if (apiKey !== $X) { + ... + } + - pattern: | + if ($X !== apiKey) { + ... + } + - pattern: | + if (apiSecret != $X) { + ... + } + - pattern: | + if ($X != apiSecret) { + ... + } + - pattern: | + if (apiSecret !== $X) { + ... + } + - pattern: | + if ($X !== apiSecret) { + ... + } + - pattern: | + if (token != $X) { + ... + } + - pattern: | + if ($X != token) { + ... + } + - pattern: | + if (token !== $X) { + ... + } + - pattern: | + if ($X !== token) { + ... + } + - pattern: | + if (hash != $X) { + ... + } + - pattern: | + if ($X != hash) { + ... + } + - pattern: | + if (hash !== $X) { + ... + } + - pattern: | + if ($X !== hash) { + ... + } + - pattern: | + if (auth_token != $X) { + ... + } + - pattern: | + if ($X != auth_token) { + ... + } + - pattern: | + if (auth_token !== $X) { + ... + } + - pattern: | + if ($X !== auth_token) { + ... + } + - pattern: | + return $X === auth_token; + - pattern: | + return auth_token === $X; + - pattern: | + return $X === token; + - pattern: | + return token === $X; + - pattern: | + return $X === hash; + - pattern: | + return hash === $X; + - pattern: | + return $X === password; + - pattern: | + return password === $X; + - pattern: | + return $X === pass; + - pattern: | + return pass === $X; + - pattern: | + return $X === apiKey; + - pattern: | + return apiKey === $X; + - pattern: | + return $X === apiSecret; + - pattern: | + return apiSecret === $X; + - pattern: | + return $X === api_key; + - pattern: | + return api_key === $X; + - pattern: | + return $X === api_secret; + - pattern: | + return api_secret === $X; + - pattern: | + return $X === secret; + - pattern: | + return secret === $X; + - pattern: | + return $X === api; + - pattern: | + return api === $X; + - pattern: | + return $X == auth_token; + - pattern: | + return auth_token == $X; + - pattern: | + return $X == token; + - pattern: | + return token == $X; + - pattern: | + return $X == hash; + - pattern: | + return hash == $X; + - pattern: | + return $X == password; + - pattern: | + return password == $X; + - pattern: | + return $X == pass; + - pattern: | + return pass == $X; + - pattern: | + return $X == apiKey; + - pattern: | + return apiKey == $X; + - pattern: | + return $X == apiSecret; + - pattern: | + return apiSecret == $X; + - pattern: | + return $X == api_key; + - pattern: | + return api_key == $X; + - pattern: | + return $X == api_secret; + - pattern: | + return api_secret == $X; + - pattern: | + return $X == secret; + - pattern: | + return secret == $X; + - pattern: | + return $X == api; + - pattern: | + return api == $X; + - pattern: | + return $X !== auth_token; + - pattern: | + return auth_token !== $X; + - pattern: | + return $X !== token; + - pattern: | + return token !== $X; + - pattern: | + return $X !== hash; + - pattern: | + return hash !== $X; + - pattern: | + return $X !== password; + - pattern: | + return password !== $X; + - pattern: | + return $X !== pass; + - pattern: | + return pass !== $X; + - pattern: | + return $X !== apiKey; + - pattern: | + return apiKey !== $X; + - pattern: | + return $X !== apiSecret; + - pattern: | + return apiSecret !== $X; + - pattern: | + return $X !== api_key; + - pattern: | + return api_key !== $X; + - pattern: | + return $X !== api_secret; + - pattern: | + return api_secret !== $X; + - pattern: | + return $X !== secret; + - pattern: | + return secret !== $X; + - pattern: | + return $X !== api; + - pattern: | + return api !== $X; + - pattern: | + return $X != auth_token; + - pattern: | + return auth_token != $X; + - pattern: | + return $X != token; + - pattern: | + return token != $X; + - pattern: | + return $X != hash; + - pattern: | + return hash != $X; + - pattern: | + return $X != password; + - pattern: | + return password != $X; + - pattern: | + return $X != pass; + - pattern: | + return pass != $X; + - pattern: | + return $X != apiKey; + - pattern: | + return apiKey != $X; + - pattern: | + return $X != apiSecret; + - pattern: | + return apiSecret != $X; + - pattern: | + return $X != api_key; + - pattern: | + return api_key != $X; + - pattern: | + return $X != api_secret; + - pattern: | + return api_secret != $X; + - pattern: | + return $X != secret; + - pattern: | + return secret != $X; + - pattern: | + return $X != api; + - pattern: | + return api != $X; + message: >- + String comparisons using '===', '!==', '!=' and '==' is vulnerable to timing attacks. + More info: https://snyk.io/blog/node-js-timing-attack-ccc-ctf/ + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A9: Using Components with Known Vulnerabilities' + cwe: 'CWE-208: Observable Timing Discrepancy' diff --git a/rules/crypto/tls_node.js b/rules/crypto/tls_node.js new file mode 100644 index 0000000..f9555c6 --- /dev/null +++ b/rules/crypto/tls_node.js @@ -0,0 +1,56 @@ +var request = require('request'); +var use_key = 'e0ee2bc6d1979f49c6437e27b06a0101'; + +//corresponding function for each api call to tortuga gateway, allows easy calling and can store user key + +module.exports = { + + 'status': function (callback) { + // ruleid:node_tls_reject + process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; + request.get('https://dev.app.idt.net/v1/status?user_key=' + use_key, function (err, response, body) { + if (err) callback(err); + + var status = JSON.parse(body); + callback(err, status); + }) + }, + 'fund': function (json, callback) { + // ruleid:node_tls_reject + process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + request.post({ + uri: 'https://dev.app.idt.net/v1/charges?user_key=' + use_key, + json: json, + method: 'POST' + }, + function (err, response, body) { + if (err) callback(err); + + callback(err, response); + }) + + }, +} + + +var http = require('http'); +var curl = require('node-curl'); + +http.createServer(function (request, response) { + + var url = 'https://url'; + url += request.url; + + console.log(url); + + + // ruleid:node_curl_ssl_verify_disable + curl(url, + { + SSL_VERIFYPEER: 0 + }, + function (err) { + response.end(this.body); + }) + +}).listen(8000); \ No newline at end of file diff --git a/rules/crypto/tls_node.yaml b/rules/crypto/tls_node.yaml new file mode 100644 index 0000000..dec2290 --- /dev/null +++ b/rules/crypto/tls_node.yaml @@ -0,0 +1,28 @@ +rules: + - id: node_tls_reject + patterns: + - pattern-either: + - pattern: | + $X.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' + - pattern: | + $X.env['NODE_TLS_REJECT_UNAUTHORIZED']= '0' + message: >- + Setting 'NODE_TLS_REJECT_UNAUTHORIZED' to 0 will allow node server to + accept self signed certificates and is not a secure behaviour. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-295: Improper Certificate Validation' + - id: node_curl_ssl_verify_disable + patterns: + - pattern: | + $X(..., {SSL_VERIFYPEER : 0}, ...) + message: SSL Certificate verification for node-curl is disabled. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-599: Missing Validation of OpenSSL Certificate' diff --git a/rules/database/nosql_find_injection.js b/rules/database/nosql_find_injection.js new file mode 100644 index 0000000..5d9364e --- /dev/null +++ b/rules/database/nosql_find_injection.js @@ -0,0 +1,29 @@ + + +app.post('/smth', function (req, res) { + // ruleid:node_nosqli_injection + var query = {}; + query['email'] = req.body.email; + User.findOne(query, function (err, data) { + if (err) { + res.send(err); + } else if (data) { + res.send('User Login Successful'); + } else { + res.send('Wrong Username Password Combination'); + } + }) +}); + +app.post('/login', function (req, res) { + // ruleid:node_nosqli_injection + User.findOne({ 'email': req.body.email, 'password': req.body.password }, function (err, data) { + if (err) { + res.send(err); + } else if (data) { + res.send('User Login Successful'); + } else { + res.send('Wrong Username Password Combination'); + } + }) +}); \ No newline at end of file diff --git a/rules/database/nosql_find_injection.yaml b/rules/database/nosql_find_injection.yaml new file mode 100644 index 0000000..e28773a --- /dev/null +++ b/rules/database/nosql_find_injection.yaml @@ -0,0 +1,80 @@ +rules: +- id: node_nosqli_injection + patterns: + - pattern-not-inside: | + $SANITIZE = require('mongo-sanitize'); + ... + $SANITIZE(...); + ... + - pattern-either: + - pattern: | + $OBJ.findOne({$KEY : <... $REQ.$FOO.$BAR ...> }, ...); + - pattern: | + $OBJ.findOne({$KEY: <... $REQ.$FOO ...> }, ...); + - pattern: | + $INP = <... $REQ.$FOO.$BAR ...>; + ... + $OBJ.findOne({$KEY : <... $INP ...> }, ...); + - pattern: | + $INP = <... $REQ.$FOO ...>; + ... + $OBJ.findOne({$KEY: <... $INP ...> }, ...); + - pattern: | + $QUERY = {$KEY: <... $REQ.$FOO.$BAR ...>}; + ... + $OBJ.findOne($QUERY, ...); + - pattern: | + $QUERY = {$KEY: <... $REQ.$FOO ...>}; + ... + $OBJ.findOne($QUERY, ...); + - pattern: | + $INP = <... $REQ.$FOO.$BAR ...>; + ... + $QUERY = {$KEY : <... $INP ...> }; + ... + $OBJ.findOne(<... $QUERY ...>, ...); + - pattern: | + $INP = <... $REQ.$FOO ...>; + ... + $QUERY = {$KEY : <... $INP ...> }; + ... + $OBJ.findOne(<... $QUERY ...>, ...); + - pattern: | + $QUERY[$KEY] = <... $REQ.$FOO.$BAR ...>; + ... + $OBJ.findOne($QUERY, ...); + - pattern: | + $QUERY[$KEY] = <... $REQ.$FOO ...>; + ... + $OBJ.findOne($QUERY, ...); + - pattern: | + $INP = <... $REQ.$FOO.$BAR ...>; + ... + $QUERY[$KEY] = <... $INP ...>; + ... + $OBJ.findOne(<... $QUERY ...>, ...); + - pattern: | + $INP = <... $REQ.$FOO ...>; + ... + $QUERY[$KEY] = <... $INP ...>; + ... + $OBJ.findOne(<... $QUERY ...>, ...); + - pattern: | + $INP = <... $REQ.$FOO.$BAR ...>; + ... + $QUERY[$KEY] = <... $INP ...>; + ... + $OBJ.findOne(<... $QUERY ...>, ...); + - pattern: | + $INP = <... $REQ.$FOO ...>; + ... + $QUERY[$KEY] = <... $INP ...>; + ... + $OBJ.findOne(<... $QUERY ...>, ...); + message: Untrusted user input in findOne() function can result in NoSQL Injection. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-943: Improper Neutralization of Special Elements in Data Query Logic' \ No newline at end of file diff --git a/rules/database/nosql_injection.js b/rules/database/nosql_injection.js new file mode 100644 index 0000000..7094656 --- /dev/null +++ b/rules/database/nosql_injection.js @@ -0,0 +1,53 @@ +var MongoClient = require('mongodb').MongoClient; +// mongo js injection https://lockmedown.com/securing-node-js-mongodb-security-injection-attacks/ +timelineRouter.route("/api/timeline") + .get(async function (req, res) { + try { + var foo = req.foo.bar; + const startDate = "01/01/2000"; + // ruleid:node_nosqli_js_injection + const endDate = req.query.end; + const query = { $where: "this.hidden == false" }; + + if (startDate && endDate) { + query["$where"] = "this.start >= new Date('" + startDate + "') && " + + "this.end <= new Date('" + endDate + "') &&" + + "this.hidden == false;"; + } + + const TimelineItem = await getTimelineItemModel(); + const timelineItems = await TimelineItem.find(query); + console.log(colors.yellow(`# of Timeline Items retrieved: ${timelineItems.length}`)); + return res.json({ timelineItems: timelineItems }); + + } catch (error) { + res.status(500).send("There was an error retrieving timeline items. Please try again later"); + } + }); + +// https://nullsweep.com/a-nosql-injection-primer-with-mongo/ +// ruleid:node_nosqli_js_injection +let username = req.query.username; +var query = { $where: `this.username == '${username}'` } +User.find(query, function (err, users) { + if (err) { + // Handle errors + } else { + res.render('userlookup', { title: 'User Lookup', users: users }); + } +}); + +app.post('/foo', function (req, res) { + // ruleid:node_nosqli_js_injection + var query = {}; + query['$where'] = `this.email == '${req.body.email}'`; + User.find(query, function (err, data) { + if (err) { + res.send(err); + } else if (data) { + res.send('User Login Successful'); + } else { + res.send('Wrong Username Password Combination'); + } + }) +}); \ No newline at end of file diff --git a/rules/database/nosql_injection.yaml b/rules/database/nosql_injection.yaml new file mode 100644 index 0000000..9a8f559 --- /dev/null +++ b/rules/database/nosql_injection.yaml @@ -0,0 +1,69 @@ +rules: +- id: node_nosqli_js_injection + patterns: + - pattern-either: + - pattern: | + $OBJ.$FUNC({$where: <... $REQ.$FOO.$BAR ...>}, ...); + - pattern: | + $OBJ.$FUNC({$where: <... $REQ.$QUERY ...>}, ...); + - pattern: | + $NSQL = <... $REQ.$QUERY.$...>; + ... + $OBJ.$FUNC({$where: <... $NSQL ...>}, ...); + - pattern: | + $NSQL = <... $REQ.$QUERY ...>; + ... + $OBJ.$FUNC({$where: <... $NSQL ...>}, ...); + - pattern: | + $INP = $REQ.$FOO.$BAR; + ... + $QRY = {$where: <... $INP ...>}; + ... + $OBJ.$FUNC(<... $QRY ...>, ...); + - pattern: | + $INP = $REQ.$FOO; + ... + $QRY = {$where: <... $INP ...>}; + ... + $OBJ.$FUNC(<... $QRY ...>, ...); + - pattern: | + $QRY["$where"] = <... $REQ.$FOO ...>; + ... + $OBJ.$FUNC(<... $QRY ...>, ...); + - pattern: | + $QRY["$where"] = <... $REQ.$FOO.$BAR ...>; + ... + $OBJ.$FUNC(<... $QRY ...>, ...); + - pattern: | + $INP = $REQ.$FOO; + ... + $QRY["$where"] = <... $INP ...>; + ... + $OBJ.$FUNC(<... $QRY ...>, ...); + - pattern: | + $INP = $REQ.$FOO.$BAR; + ... + $QRY["$where"] = <... $INP ...>; + ... + $OBJ.$FUNC(<... $QRY ...>, ...); + - pattern: | + $INP = $REQ.$FOO; + ... + $QRY["$where"] = <... $INP ...>; + ... + $OBJ.$FUNC(<... $QRY ...>, ...); + - pattern: | + $INP = $REQ.$FOO.$BAR; + ... + $QRY["$where"] = <... $INP ...>; + ... + $OBJ.$FUNC(<... $QRY ...>, ...); + message: >- + Untrusted user input in MongoDB $where operator can result in NoSQL + JavaScript Injection. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-943: Improper Neutralization of Special Elements in Data Query Logic' diff --git a/rules/database/sql_injection.yaml b/rules/database/sql_injection.yaml new file mode 100644 index 0000000..d814f84 --- /dev/null +++ b/rules/database/sql_injection.yaml @@ -0,0 +1,39 @@ +rules: + - id: node_sqli_injection + patterns: + - pattern-either: + - pattern: | + $CON.query(<... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + $CON.query(<... $REQ.$QUERY ...>, ...) + - pattern: | + $SQL = <... $REQ.$QUERY.$VAR ...>; + ... + $CON.query(<... $SQL ...>, ...); + - pattern: | + $SQL = <... $REQ.$QUERY ...>; + ... + $CON.query(<... $SQL ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + $SQL = <... $INP ...>; + ... + $CON.query(<... $SQL ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + $SQL = <... $INP ...>; + ... + $CON.query(<... $SQL ...>, ...); + message: >- + Untrusted input concatinated with raw SQL query can result in SQL + Injection. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-89: Improper Neutralization of Special Elements used in an SQL + Command ('SQL Injection') diff --git a/rules/database/sqli_node.js b/rules/database/sqli_node.js new file mode 100644 index 0000000..f98eb79 --- /dev/null +++ b/rules/database/sqli_node.js @@ -0,0 +1,66 @@ +var mysql = require('mysql'); + +const pg = require('pg'); +// ruleid:node_sqli_injection +connection.query("SELECT * FROM bank_accounts WHERE dob = '" + req.body.dob + "' AND bank_account = '" + req.body.account_number + "'", function (error, results) { }); + +const sequelize = require('../conn'); +router.post('/', function (req, res) { + // ruleid:node_sqli_injection + var query = 'SELECT * FROM person WHERE id = \'' + + req.body.input + '\''; + sequelize.query(query, { + type: sequelize.QueryTypes.SELECT, + model: Foo + }) + .then(function (foo) { + res.json({ message: person }); + }) + .catch(function (err) { + res.json({ message: err.toString() }); + }); +}); + +var connection = mysql.createConnection({ + host: 'localhost', + user: user, + password: pass, + database: 'technicalkeeda', + debug: false, +}); +connection.connect(); + +// ruleid:node_sqli_injection +var employeeId = req.foo; +var sql = "SELECT * FROM trn_employee WHERE employee_id = " + employeeId; + +connection.query(sql, function (error, results, fields) { + if (error) { + throw error; + } + console.log(results); +}); + +connection.connect(function (err) { + // ruleid:node_sqli_injection + connection.query('SELECT * FROM users WHERE id = ' + req.foo('bar'), (err, res) => { }); +}); + +connection.end(); + +const pgcon = new pg.Client({ host: host, user: user, password: pass, database: db }); +pgcon.connect(); +// ruleid:node_sqli_injection +var inp = req.foo["id"]; +pgcon.query('SELECT * FROM users WHERE id = ' + inp, (err, res) => { }); + + +const pg = require('pg'); +const pool = new pg.Pool(config); +function handler(req, res) { + // ruleid:node_sqli_injection + var query1 = "SELECT FOO,BAR FROM TABLE WHERE CAT='" + + req.foo.bar + "' ORDER BY FOO"; + pool.query(query1, [], function (err, results) { + }); +} \ No newline at end of file diff --git a/rules/dos/express_bodyparser_dos.js b/rules/dos/express_bodyparser_dos.js new file mode 100644 index 0000000..473b997 --- /dev/null +++ b/rules/dos/express_bodyparser_dos.js @@ -0,0 +1,16 @@ +const express = require('express') + , cors = require('cors') + , bodyParser = require('body-parser'); + +var app = express(); + +app.configure(function () { + app.set('port', process.env.PORT || 3000); + app.set('views', __dirname + '/views'); + app.set('view engine', 'jade'); + app.use(express.favicon()); + app.use(express.logger('dev')); + // ruleid:express_bodyparser + app.use(express.bodyParser()); + app.use(cors()); +}); \ No newline at end of file diff --git a/rules/dos/express_bodyparser_dos.yaml b/rules/dos/express_bodyparser_dos.yaml new file mode 100644 index 0000000..22c223e --- /dev/null +++ b/rules/dos/express_bodyparser_dos.yaml @@ -0,0 +1,19 @@ +rules: + - id: express_bodyparser + patterns: + - pattern-inside: + $APP = express(); + ... + - pattern-inside: | + $APP.use(...); + - pattern: + $X.bodyParser(...) + message: >- + POST Request to Express Body Parser 'bodyParser()' can create Temporary + files and consume space. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A9: Using Components with Known Vulnerabilities' + cwe: 'CWE-400: Uncontrolled Resource Consumption' diff --git a/rules/dos/layer7_object_dos.js b/rules/dos/layer7_object_dos.js new file mode 100644 index 0000000..e88bb8e --- /dev/null +++ b/rules/dos/layer7_object_dos.js @@ -0,0 +1,31 @@ +const express = require('express'); +const router = express.Router() + + +router.post("/list-users", (req, res) => { + var obj = req.body.users; + var someArr = []; + + // Potential DoS if obj.length is large. + // ruleid:layer7_object_dos + for (var i = 0; i < obj.length; i++) { + someArr.push(obj[i]); + } + +}); + + +module.exports = router + + +app.post("/foo", (req, res) => { + var obj = req.body; + + var ret = []; + + // Potential DoS if obj.length is large. + // ruleid:layer7_object_dos + for (var i = 0; i < obj.length; i++) { + ret.push(obj[i]); + } +}); \ No newline at end of file diff --git a/rules/dos/layer7_object_dos.yaml b/rules/dos/layer7_object_dos.yaml new file mode 100644 index 0000000..5d9699f --- /dev/null +++ b/rules/dos/layer7_object_dos.yaml @@ -0,0 +1,28 @@ +rules: + - id: layer7_object_dos + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern-inside: | + $OBJ = $REQ.body; + ... + - pattern-inside: | + $OBJ = $REQ.body.$FOO; + ... + - pattern-inside: | + for(...){...}; + - pattern: | + $OBJ.length; + message: Layer7 Denial of Service. Looping over user controlled objects can result in DoS. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: >- + CWE-400: Uncontrolled Resource Consumption \ No newline at end of file diff --git a/rules/dos/regex_dos.js b/rules/dos/regex_dos.js new file mode 100644 index 0000000..2b4b3cb --- /dev/null +++ b/rules/dos/regex_dos.js @@ -0,0 +1,56 @@ +const express = require('express'); +const router = express.Router() + + +router.get("/tstMe", (req, res) => { + var r = /([a-z]+)+$/; + // ruleid:regex_dos + let match = r.test(req.params.id); +}); + + +module.exports = router + +var http = require("http"); +var url = require("url"); +http.createServer(function (request, response) { + + var myArray = /d(b+)d/g.exec('cdbbdbsbz'); + var emailExpression = /^([a-zA-Z0-9_\.\-])+\@/ + foo + /(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; + + + // ruleid:regex_dos + var parsedUrl = url.parse(request.url, true); + console.timeEnd('benchmark'); + /^(([a-z])+.)+[A-Z]([a-z])+$/.test(parsedUrl.query); + + /^(([a-z])+.)+[A-Z]([a-z])+$/.test('a'.repeat(100)); + console.timeEnd('benchmark'); + let match = r.test(req.params.id); + // ruleid:regex_dos + /^(([a-z])+.)+[A-Z]([a-z])+$/.test(request.foo); + console.timeEnd('benchmark'); + // ruleid:regex_dos + var y = /^(([a-z])+.)+[A-Z]([a-z])+$/.test(request.foo['bar']); + console.timeEnd('benchmark'); + console.time('benchmark'); + var x = /^(([a-z])+.)+[A-Z]([a-z])+$/.test('a'.repeat(100)); + console.timeEnd('benchmark'); + + // ruleid:regex_dos + var myArray = /d(b+)d/g.exec(request.foo.bar); + response.end(); + + + // ruleid:regex_dos + var re = /(?:\d{3}|\(\d{3}\))([-\/\.])\d{3}\1\d{4}/; + var OK = re.exec(request.value); + if (!OK) { + console.error(request.value + ' isn\'t a phone number with area code!'); + } else { + console.log('Thanks, your phone number is ' + OK[0]); + } + +}).listen(8888); + + diff --git a/rules/dos/regex_dos.yaml b/rules/dos/regex_dos.yaml new file mode 100644 index 0000000..478fe9b --- /dev/null +++ b/rules/dos/regex_dos.yaml @@ -0,0 +1,67 @@ +rules: + - id: regex_dos + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $REGEX.test(<... $REQ ...>) + - pattern: | + $REGEX.test(<... $REQ.$QUERY ...>) + - pattern: | + $REGEX.test(<... $REQ.$BODY.$PARAM ...>) + - pattern: | + $INP = <... $REQ ...>; + ... + $REGEX.test(<... $INP ...>); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + $REGEX.test(<... $INP ...>); + - pattern: | + $INP = <... $REQ.$BODY.$PARAM ...>; + ... + $REGEX.test(<... $INP ...>); + - pattern: | + /.../g.exec(<... $REQ ...>) + - pattern: | + /.../g.exec(<... $REQ.$QUERY ...>) + - pattern: | + /.../.exec(<... $REQ.$BODY.$PARAM ...>) + - pattern: | + $INP = <... $REQ ...>; + ... + /.../.exec(<... $INP ...>); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + /.../.exec(<... $INP ...>); + - pattern: | + $INP = <... $REQ.$BODY.$PARAM ...>; + ... + /.../.exec(<... $INP ...>); + - pattern: | + $RE = /.../; + ... + $RE.exec(<... $REQ ...>); + - pattern: | + $RE = /.../; + ... + $RE.exec(<... $REQ.$QUERY ...>); + - pattern: | + $RE = /.../; + ... + $RE.exec(<... $REQ.$BODY.$PARAM ...>); + message: >- + Ensure that the regex used to compare with user supplied input is safe + from regular expression denial of service. + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-185: Incorrect Regular Expression' \ No newline at end of file diff --git a/rules/dos/regex_injection.yaml b/rules/dos/regex_injection.yaml new file mode 100644 index 0000000..71df9f7 --- /dev/null +++ b/rules/dos/regex_injection.yaml @@ -0,0 +1,67 @@ +rules: + - id: regex_injection_dos + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $INP = <... $REQ.$PARAM ...>; + ... + $RE = new RegExp(<... $INP ...>); + - pattern: | + $INP = <... $REQ.$PARAM.$BAR ...>; + ... + $RE = new RegExp(<... $INP ...>); + - pattern: | + new RegExp(<... $REQ.$PARAM ...>); + - pattern: | + new RegExp(<... $REQ.$PARAM.$BAR ...>); + - pattern: | + $INP = <... $REQ.$PARAM ...>; + ... + $RE = $STR.search(<... $INP ...>); + - pattern: | + $INP = <... $REQ.$PARAM.$FOO ...>; + ... + $RE = $STR.search(<... $INP ...>); + - pattern: | + $STR.search(<... $REQ.$PARAM ...>); + - pattern: | + $STR.search(<... $REQ.$PARAM.$BAR ...>); + - pattern: | + $INP = <... $REQ.$PARAM ...>; + ... + $RE = $STR.match(<... $INP ...>); + - pattern: | + $INP = <... $REQ.$PARAM.$FOO ...>; + ... + $RE = $STR.match(<... $INP ...>); + - pattern: | + $STR.match(<... $REQ.$PARAM ...>); + - pattern: | + $STR.match(<... $REQ.$PARAM.$BAR ...>); + - pattern: | + $INP = <... $REQ.$PARAM ...>; + ... + $RE = $STR.split(<... $INP ...>); + - pattern: | + $INP = <... $REQ.$PARAM.$FOO ...>; + ... + $RE = $STR.split(<... $INP ...>); + - pattern: | + $STR.split(<... $REQ.$PARAM ...>); + - pattern: | + $STR.split(<... $REQ.$PARAM.$BAR ...>); + message: >- + User controlled data in RegExp() can make the application vulnerable to + layer 7 DoS. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-400: Uncontrolled Resource Consumption' \ No newline at end of file diff --git a/rules/dos/regex_injection_dos.js b/rules/dos/regex_injection_dos.js new file mode 100644 index 0000000..ce51832 --- /dev/null +++ b/rules/dos/regex_injection_dos.js @@ -0,0 +1,11 @@ +var express = require('express'); +var app = express(); + +app.get('/search', function (req, res) { + // ruleid:regex_injection_dos + var key = req.param("key"); + // Regex created from user input + var re = new RegExp("\\b" + key); +}); +//do not detect this +var re = new RegExp("\\b" + key + "=(.*)\n"); \ No newline at end of file diff --git a/rules/electronjs/security_electron.js b/rules/electronjs/security_electron.js new file mode 100644 index 0000000..a690cbc --- /dev/null +++ b/rules/electronjs/security_electron.js @@ -0,0 +1,65 @@ +// ruleid:electron_disable_websecurity +const mainWindow = new BrowserWindow({ + webPreferences: { + webSecurity: false + } +}) + +// ruleid:electron_disable_websecurity +const config = { + webPreferences: { + webSecurity: false + } +} +var newwin = new BrowserWindow(config) + +// ruleid:electron_allow_http +const mainWindow = new BrowserWindow({ + webPreferences: { + allowRunningInsecureContent: true + } +}) + +// ruleid:electron_disable_websecurity +var x = new BrowserWindow({ + webPreferences: { + webSecurity: false, + allowRunningInsecureContent: true + } +}) + +// ruleid:electron_blink_integration +const mainWindow = new BrowserWindow({ + webPreferences: { + enableBlinkFeatures: 'ExecCommandInJavaScript' + } +}) + +// ruleid:electron_allow_http +const mainWindow = new BrowserWindow({ + webPreferences: { + allowRunningInsecureContent: true + } +}) + +// ruleid:electron_nodejs_integration +const mainWindow = new BrowserWindow({ + webPreferences: { + nodeIntegration: true, + nodeIntegrationInWorker: true + } +}) + +// ruleid:electron_context_isolation +const mainWindow = new BrowserWindow({ + webPreferences: { + contextIsolation: false, + preload: path.join(app.getAppPath(), 'preload.js') + } +}) +// ruleid:electron_experimental_features +const mainWindow = new BrowserWindow({ + webPreferences: { + experimentalFeatures: true + } +}) \ No newline at end of file diff --git a/rules/electronjs/security_electronjs.yaml b/rules/electronjs/security_electronjs.yaml new file mode 100644 index 0000000..7c3667f --- /dev/null +++ b/rules/electronjs/security_electronjs.yaml @@ -0,0 +1,99 @@ +rules: + - id: electron_disable_websecurity + patterns: + - pattern-either: + - pattern: | + new BrowserWindow({webPreferences: {webSecurity: false}}); + - pattern: | + var $X = {webPreferences: {webSecurity: false}}; + message: >- + Disabling webSecurity will disable the same-origin policy and allows the + execution of insecure code from any domain. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-346: Origin Validation Error' + - id: electron_allow_http + patterns: + - pattern-either: + - pattern: > + new BrowserWindow({webPreferences: {allowRunningInsecureContent: + true}}); + - pattern: | + var $X = {webPreferences: {allowRunningInsecureContent: true}}; + message: >- + Application can load content over HTTP and that makes the app vulnerable + to Man in the middle attacks. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-319: Cleartext Transmission of Sensitive Information' + - id: electron_blink_integration + patterns: + - pattern-either: + - pattern: | + new BrowserWindow({webPreferences: {enableBlinkFeatures: '...'}}); + - pattern: | + var $X = {webPreferences: {enableBlinkFeatures: '...'}}; + message: >- + Blink's expirimental features are enabled in this application. Some of the + features may affect the security of the application. + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-272: Least Privilege Violation' + - id: electron_nodejs_integration + patterns: + - pattern-either: + - pattern: | + new BrowserWindow({webPreferences: {nodeIntegration: true}}); + - pattern: | + var $X = {webPreferences: {nodeIntegration: true}}; + message: >- + Node integration exposes node.js APIs to the electron app and this can + introduce remote code execution vulnerabilities to the application if the + app is vulnerable to Cross Site Scripting (XSS). + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-272: Least Privilege Violation' + - id: electron_context_isolation + patterns: + - pattern-either: + - pattern: | + new BrowserWindow({webPreferences: {contextIsolation: false}}); + - pattern: | + var $X = {webPreferences: {contextIsolation: false}}; + message: >- + Disabling context isolation can introduce Prototype Pollution + vulnerabilities. + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-693: Protection Mechanism Failure' + - id: electron_experimental_features + patterns: + - pattern-either: + - pattern: | + new BrowserWindow({webPreferences: {experimentalFeatures: true}}); + - pattern: | + var $X = {webPreferences: {experimentalFeatures: true}}; + message: >- + Experimental features are not expected to be in production ready + applications. + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-272: Least Privilege Violation' diff --git a/rules/eval/eval_deserialize.js b/rules/eval/eval_deserialize.js new file mode 100644 index 0000000..9783851 --- /dev/null +++ b/rules/eval/eval_deserialize.js @@ -0,0 +1,31 @@ +var express = require('express'); +var cookieParser = require('cookie-parser'); +var escape = require('escape-html'); +var serialize = require('node-serialize'); +const serialize2 = require('serialize-to-js') + + +var app = express(); +app.use(cookieParser()) + +app.get('/', function (req, res) { + if (req.cookies.profile) { + var str = new Buffer(req.cookies.profile, 'base64').toString(); + // ruleid:node_deserialize + var obj = serialize.unserialize(str); + // ruleid:serializetojs_deserialize + serialize2.deserialize(str); + if (obj.username) { + res.send("Hello " + escape(obj.username)); + } + } else { + res.cookie('profile', "eyJ1c2VybmFtZSI6ImFqaW4iLCJjb3VudHJ5IjoiaW5kaWEiLCJjaXR5IjoiYmFuZ2Fsb3JlIn0=", { + maxAge: 900000, + httpOnly: true + }); + } + res.send("Hello World"); +}); +app.listen(3000); +// ruleid:serializetojs_deserialize +require('serialize-to-js').deserialize(str); \ No newline at end of file diff --git a/rules/eval/eval_deserialize.yaml b/rules/eval/eval_deserialize.yaml new file mode 100644 index 0000000..3d13a59 --- /dev/null +++ b/rules/eval/eval_deserialize.yaml @@ -0,0 +1,33 @@ +rules: + - id: serializetojs_deserialize + patterns: + - pattern-inside: | + require('serialize-to-js'); + ... + - pattern: | + $X.deserialize(...) + message: >- + User controlled data in 'unserialize()' or 'deserialize()' function can + result in Object Injection or Remote Code Injection. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A8: Insecure Deserialization' + cwe: 'CWE-502: Deserialization of Untrusted Data' + - id: node_deserialize + patterns: + - pattern-inside: | + require('node-serialize'); + ... + - pattern: | + $X.unserialize(...) + message: >- + User controlled data in 'unserialize()' or 'deserialize()' function can + result in Object Injection or Remote Code Injection. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A8: Insecure Deserialization' + cwe: 'CWE-502: Deserialization of Untrusted Data' \ No newline at end of file diff --git a/rules/eval/eval_drpc_deserialize.yaml b/rules/eval/eval_drpc_deserialize.yaml new file mode 100644 index 0000000..7baae09 --- /dev/null +++ b/rules/eval/eval_drpc_deserialize.yaml @@ -0,0 +1,23 @@ +rules: + - id: grpc_insecure_connection + patterns: + - pattern-inside: | + require('grpc'); + ... + - pattern-either: + - pattern: | + $GRPC($ADDR, ..., $CREDENTIALS.createInsecure(), ...); + - pattern: | + $CREDS = <... $CREDENTIALS.createInsecure() ...>; + ... + $GRPC($ADDR, ..., $CREDS, ...); + message: >- + Found an insecure gRPC connection. This creates a connection without + encryption to a gRPC client/server. A malicious attacker could + tamper with the gRPC message, which could compromise the machine. + metadata: + owasp: 'A8: Insecure Deserialization' + cwe: 'CWE-502: Deserialization of Untrusted Data' + severity: ERROR + languages: + - javascript diff --git a/rules/eval/eval_grpc_insecure.js b/rules/eval/eval_grpc_insecure.js new file mode 100644 index 0000000..185c099 --- /dev/null +++ b/rules/eval/eval_grpc_insecure.js @@ -0,0 +1,45 @@ +function test1() { + // ruleid: grpc_insecure_connection + var grpc = require('grpc'); + + var booksProto = grpc.load('books.proto'); + + var client = new booksProto.books.BookService('127.0.0.1:50051', grpc.credentials.createInsecure()); + + client.list({}, function (error, books) { + if (error) + console.log('Error: ', error); + else + console.log(books); + }); +} + +function test2() { + // ruleid: grpc_insecure_connection + var { credentials, load, Client } = require('grpc'); + + var creds = someFunc() || credentials.createInsecure(); + + var client = new Client('127.0.0.1:50051', creds); + + client.list({}, function (error, books) { + if (error) + console.log('Error: ', error); + else + console.log(books); + }); +} + +function test3() { + // ruleid: grpc_insecure_connection + var grpc = require('grpc'); + + var booksProto = grpc.load('books.proto'); + + var server = new grpc.Server(); + + server.addProtoService(booksProto.books.BookService.service, {}); + + server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure()); + server.start(); +} diff --git a/rules/eval/eval_node.js b/rules/eval/eval_node.js new file mode 100644 index 0000000..ef055e3 --- /dev/null +++ b/rules/eval/eval_node.js @@ -0,0 +1,19 @@ +var express = require('express'); +var app = express(); +app.get('/', function (req, res) { + // ruleid:eval_nodejs + var resp = eval("(" + req.query.name + ")"); + // ruleid:eval_nodejs + var z = new Function('arg1', 'arg2', req.query.name) + z(1, 2); + // ruleid:eval_nodejs + setTimeout('alert(' + req.body.name, 0); + // ruleid:eval_nodejs + setInterval(req.body.name, 0); + res.send('Response
'); +}); +app.listen(8000); +eval("outside_express" + req.foo) +setTimeout('alert(' + req.body.name, 0); +setInterval(req.body.name, 0); +new Function('arg1', 'arg2', req.query.name) \ No newline at end of file diff --git a/rules/eval/eval_node.yaml b/rules/eval/eval_node.yaml new file mode 100644 index 0000000..375450a --- /dev/null +++ b/rules/eval/eval_node.yaml @@ -0,0 +1,69 @@ +rules: + - id: eval_nodejs + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + new Function(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + new Function(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + eval(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + eval(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + setTimeout(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + setTimeout(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + setInterval(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + setInterval(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + new Function(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + new Function(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + eval(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + eval(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + setTimeout(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + setTimeout(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + setInterval(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + setInterval(..., <... $INP ...>, ...); + message: >- + User controlled data in eval() or similar functions may result in Server + Side Injection or Remote Code Injection + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-95: Improper Neutralization of Directives in Dynamically Evaluated + Code ('Eval Injection') \ No newline at end of file diff --git a/rules/eval/eval_require.js b/rules/eval/eval_require.js new file mode 100644 index 0000000..29abd97 --- /dev/null +++ b/rules/eval/eval_require.js @@ -0,0 +1,38 @@ +const express = require('express') +const app = express() +const port = 3000 + +const hardcodedPath = 'lib/func.js' + +function testController1(req, res) { + try { + // ruleid: eval_require + require(req.query.controllerFullPath)(req, res); + } catch (err) { + this.log.error(err); + } + res.end('ok') +}; +app.get('/test1', testController1) + +let testController2 = function (req, res) { + // ruleid: eval_require + const func = require(req.body) + return res.send(func()) +} +app.get('/test2', testController2) + +var testController3 = null; +testController3 = function (req, res) { + // ruleid: eval_require + const func = require(req.body) + return res.send(func()) +} +app.get('/test3', testController3) + + (function (req, res) { + // ruleid: eval_require + const func = require(req.body) + return res.send(func()) + })(req, res) + diff --git a/rules/eval/eval_require.yaml b/rules/eval/eval_require.yaml new file mode 100644 index 0000000..322e974 --- /dev/null +++ b/rules/eval/eval_require.yaml @@ -0,0 +1,29 @@ +rules: + - id: eval_require + patterns: + - pattern-either: + - pattern-inside: 'function ($REQ, $RES, ...) {...}' + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + require(<... $INP ...>); + - pattern: | + $INP = <... $REQ.$QUERY.$FOO ...>; + ... + require(<... $INP ...>); + - pattern: require(<... $REQ.$QUERY.$FOO ...>) + - pattern: require(<... $REQ.$BODY ...>) + message: >- + Untrusted user input in `require()` function allows an attacker to load + arbitrary code. + severity: ERROR + languages: + - javascript + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-706: Use of Incorrectly-Resolved Name or Reference' \ No newline at end of file diff --git a/rules/eval/eval_sandbox.js b/rules/eval/eval_sandbox.js new file mode 100644 index 0000000..3dfde10 --- /dev/null +++ b/rules/eval/eval_sandbox.js @@ -0,0 +1,36 @@ +const Sandbox = require('sandbox'); +const express = require('express'); +const app = express(); +const port = 3000; + +const cb = () => { + console.log('ok') +} + +app.get('/', (req, res) => res.send('Hello World!')) + +app.get('/test1', function (req, res) { + // ruleid:sandbox_code_injection + const s = new Sandbox(); + s.run('lol(' + req.query.userInput + ')', cb); + res.send('Hello world'); +}) + +app.get('/test2', function (req, res) { + // ruleid:sandbox_code_injection + const s = new Sandbox(); + var code = 'lol(' + req.query.userInput + ')' + s.run(code, cb); + res.send('Hello world'); +}) + +app.get('/test3', function (req, res) { + // ruleid:sandbox_code_injection + const s = new Sandbox(); + s.run(`lol(${req.query.userInput})`, cb); + res.send('Hello world'); +}) + + + +app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`)) \ No newline at end of file diff --git a/rules/eval/eval_sandbox.yaml b/rules/eval/eval_sandbox.yaml new file mode 100644 index 0000000..cac20c2 --- /dev/null +++ b/rules/eval/eval_sandbox.yaml @@ -0,0 +1,44 @@ +rules: + - id: sandbox_code_injection + patterns: + - pattern-inside: | + require('sandbox'); + ... + - pattern-either: + - pattern-inside: 'function ($REQ, $RES, ...) {...}' + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: | + $S.run(<... $REQ.$QUERY.$FOO ...>,...); + - pattern: | + $CODE = <... $REQ.$QUERY.$FOO ...>; + ... + $S.run(<... $CODE ...>,...); + - pattern: | + new $SANDBOX(...).run(<... $REQ.$QUERY.$FOO ...>,...); + - pattern: | + $CODE = <... $REQ.$QUERY.$FOO ...>; + ... + new $SANDBOX(...).run(<... $CODE ...>,...); + - pattern: | + $S.run(<... $REQ.$BODY ...>,...); + - pattern: | + $CODE = <... $REQ.$BODY ...>; + ... + $S.run(<... $CODE ...>,...); + - pattern: | + new $SANDBOX(...).run(<... $REQ.$BODY ...>,...); + - pattern: |- + $CODE = <... $REQ.$BODY ...>; + ... + new $SANDBOX(...).run(<... $CODE ...>,...); + message: Unrusted data in `sandbox` can result in code injection. + severity: ERROR + languages: + - javascript + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-94: Improper Control of Generation of Code (Code Injection)' diff --git a/rules/eval/eval_vm2_injection.js b/rules/eval/eval_vm2_injection.js new file mode 100644 index 0000000..33e70c3 --- /dev/null +++ b/rules/eval/eval_vm2_injection.js @@ -0,0 +1,89 @@ +const fs = require('fs'); +const { VM, NodeVM } = require('vm2'); +const express = require('express') +const app = express() +const port = 3000 + +app.get('/', (req, res) => res.send('Hello World!')) + +app.get('/test1', (req, res) => { + // ruleid:vm2_code_injection + code = ` + console.log(${req.query.input}) + `; + + const sandbox = { + setTimeout, + fs: { + watch: fs.watch + } + }; + + new VM({ + timeout: 40 * 1000, + sandbox + }).run(code); + + res.send('hello world'); +}) + +app.get('/test2', function (req, res) { + const sandbox = { + setTimeout, + fs: { + watch: fs.watch + } + }; + + + const nodeVM = new NodeVM({ timeout: 40 * 1000, sandbox }); + // ruleid:vm2_code_injection + nodeVM.run('console.log(' + req.query.input + ')') + + res.send('hello world'); +}) + +app.get('/test3', function (req, res) { + const sandbox = { + setTimeout, + fs: { + watch: fs.watch + } + }; + + const nodeVM = new NodeVM({ timeout: 40 * 1000, sandbox }); + // ruleid:vm2_code_injection + const script = new VMScript(`console.log(${req.query.input})`) + nodeVM.run(script) + + res.send('hello world') +}) + + +app.get('/test4', async function test1(req, res) { + code = ` + console.log("Hello world") + `; + + // ruleid:vm2_context_injection + const sandbox = { + setTimeout, + watch: req.query.input + }; + + return new VM({ timeout: 40 * 1000, sandbox }).run(code); +}) + +app.post('/test5', function test2(req, res) { + // ruleid:vm2_context_injection + const sandbox = { + setTimeout, + input: req.body + }; + + const nodeVM = new NodeVM({ timeout: 40 * 1000, sandbox }); + return nodeVM.run('console.log("Hello world")') +}) + + +app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`)) \ No newline at end of file diff --git a/rules/eval/eval_vm2_injection.yaml b/rules/eval/eval_vm2_injection.yaml new file mode 100644 index 0000000..9add7b8 --- /dev/null +++ b/rules/eval/eval_vm2_injection.yaml @@ -0,0 +1,257 @@ +rules: + - id: vm2_code_injection + patterns: + - pattern-inside: | + require('vm2'); + ... + - pattern-either: + - pattern-inside: 'function ($REQ, $RES, ...) {...}' + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: | + $VM.run(<... $REQ.$QUERY.$FOO ...>,...); + - pattern: | + $CODE = <... $REQ.$QUERY.$FOO ...>; + ... + $VM.run(<... $CODE ...>,...); + - pattern: | + new VM(...).run(<... $REQ.$QUERY.$FOO ...>,...); + - pattern: | + new NodeVM(...).run(<... $REQ.$QUERY.$FOO ...>,...); + - pattern: | + $CODE = <... $REQ.$QUERY.$FOO ...>; + ... + new NodeVM(...).run(<... $CODE ...>,...); + - pattern: | + $CODE = <... $REQ.$QUERY.$FOO ...>; + ... + new VMScript(<... $CODE ...>,...); + - pattern: | + $VM.run(<... $REQ.$BODY ...>,...); + - pattern: | + $CODE = <... $REQ.$BODY ...>; + ... + $VM.run(<... $CODE ...>,...); + - pattern: | + new VM(...).run(<... $REQ.$BODY ...>,...); + - pattern: | + $CODE = <... $REQ.$BODY ...>; + ... + new VM(...).run($CODE,...); + - pattern: | + new NodeVM(...).run(<... $REQ.$BODY ...>,...); + - pattern: | + $CODE = <... $REQ.$BODY ...>; + ... + new NodeVM(...).run(<... $CODE ...>,...); + - pattern: | + $CODE = <... $REQ.$BODY ...>; + ... + new VMScript(<... $CODE ...>,...); + message: Untrusted user input reaching `vm2` can result in code injection. + severity: WARNING + languages: + - javascript + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-94: Improper Control of Generation of Code (Code Injection)' + - id: vm2_context_injection + patterns: + - pattern-inside: | + require('vm2'); + ... + - pattern-either: + - pattern-inside: 'function ($REQ, $RES, ...) {...}' + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: | + new VM({sandbox: <... $REQ.$QUERY.$FOO ...>},...); + - pattern: | + $CONTEXT = <... $REQ.$QUERY.$FOO ...>; + ... + new VM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $CONTEXT = <... {$NAME:$REQ.$QUERY.$FOO} ...>; + ... + new VM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$QUERY.$FOO ...>}; + ... + new VM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $VAR = <... $REQ.$QUERY.$FOO ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + new VM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $OPTS = {sandbox: <... $REQ.$QUERY.$FOO ...>}; + ... + new VM($OPTS,...); + - pattern: | + $CONTEXT = <... $REQ.$QUERY.$FOO ...>; + ... + $OPTS = {sandbox: <... $CONTEXT ...>}; + ... + new VM($OPTS,...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$QUERY.$FOO ...>}; + ... + $OPTS = {sandbox: <... $CONTEXT ...>}; + ... + new VM($OPTS,...); + - pattern: | + $VAR = <... $REQ.$QUERY.$FOO ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $OPTS = {sandbox: <... $CONTEXT ...>}; + ... + new VM($OPTS,...); + - pattern: | + new NodeVM({sandbox: <... $REQ.$QUERY.$FOO ...>},...); + - pattern: | + $CONTEXT = <... $REQ.$QUERY.$FOO ...>; + ... + new NodeVM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $CONTEXT = <... {$NAME:$REQ.$QUERY.$FOO} ...>; + ... + new NodeVM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$QUERY.$FOO ...>}; + ... + new NodeVM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $VAR = <... $REQ.$QUERY.$FOO ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + new NodeVM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $OPTS = {sandbox: <... $REQ.$QUERY.$FOO ...>}; + ... + new NodeVM($OPTS,...); + - pattern: | + $CONTEXT = <... $REQ.$QUERY.$FOO ...>; + ... + $OPTS = {sandbox: <... $CONTEXT ...>}; + ... + new NodeVM($OPTS,...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$QUERY.$FOO ...>}; + ... + $OPTS = {sandbox: <... $CONTEXT ...>}; + ... + new NodeVM($OPTS,...); + - pattern: | + $VAR = <... $REQ.$QUERY.$FOO ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $OPTS = {sandbox: <... $CONTEXT ...>}; + ... + new NodeVM($OPTS,...); + - pattern: | + new VM({sandbox: <... $REQ.$BODY ...>},...); + - pattern: | + $CONTEXT = <... $REQ.$BODY ...>; + ... + new VM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $CONTEXT = <... {$NAME:$REQ.$BODY} ...>; + ... + new VM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$BODY ...>}; + ... + new VM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $VAR = <... $REQ.$BODY ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + new VM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $OPTS = {sandbox: <... $REQ.$BODY ...>}; + ... + new VM($OPTS,...); + - pattern: | + $CONTEXT = <... $REQ.$BODY ...>; + ... + $OPTS = {sandbox: <... $CONTEXT ...>}; + ... + new VM($OPTS,...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$BODY ...>}; + ... + $OPTS = {sandbox: <... $CONTEXT ...>}; + ... + new VM($OPTS,...); + - pattern: | + $VAR = <... $REQ.$BODY ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $OPTS = {sandbox: <... $CONTEXT ...>}; + ... + new VM($OPTS,...); + - pattern: | + new NodeVM({sandbox: <... $REQ.$BODY ...>},...); + - pattern: | + $CONTEXT = <... $REQ.$BODY ...>; + ... + new NodeVM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $CONTEXT = <... {$NAME:$REQ.$BODY} ...>; + ... + new NodeVM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$BODY ...>}; + ... + new NodeVM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $VAR = <... $REQ.$BODY ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + new NodeVM({sandbox: <... $CONTEXT ...>},...); + - pattern: | + $OPTS = {sandbox: <... $REQ.$BODY ...>}; + ... + new NodeVM($OPTS,...); + - pattern: | + $CONTEXT = <... $REQ.$BODY ...>; + ... + $OPTS = {sandbox: <... $CONTEXT ...>}; + ... + new NodeVM($OPTS,...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$BODY ...>}; + ... + $OPTS = {sandbox: <... $CONTEXT ...>}; + ... + new NodeVM($OPTS,...); + - pattern: |- + $VAR = <... $REQ.$BODY ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $OPTS = {sandbox: <... $CONTEXT ...>}; + ... + new NodeVM($OPTS,...); + message: >- + Untrusted user input reaching `vm2` sandbox can result in context + injection. + severity: ERROR + languages: + - javascript + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-94: Improper Control of Generation of Code (Code Injection)' diff --git a/rules/eval/eval_vm_injection.js b/rules/eval/eval_vm_injection.js new file mode 100644 index 0000000..c3e7554 --- /dev/null +++ b/rules/eval/eval_vm_injection.js @@ -0,0 +1,87 @@ +const vm = require('vm') + +let ctrl1 = function test1(req, res) { + // ruleid:vm_runincontext_injection + var input = req.query.something || '' + var sandbox = { + foo: input + } + vm.createContext(sandbox) + vm.runInContext('safeEval(orderLinesData)', sandbox, { timeout: 2000 }) + res.send('hello world') +} +app.get('/', ctrl1) + +app.get('/', (req, res) => { + // ruleid:vm_runincontext_injection + var sandbox = { + foo: req.query.userInput + } + vm.createContext(sandbox) + vm.runInContext('safeEval(orderLinesData)', sandbox, { timeout: 2000 }) + res.send('hello world') +}) + +var ctrl2 = null; +ctrl2 = function test2(req, res) { + // ruleid:vm_runinnewcontext_injection + var input = req.query.something || '' + var sandbox = { + foo: input + } + vm.runInNewContext('safeEval(orderLinesData)', sandbox, { timeout: 2000 }) + res.send('hello world') +} +app.get('/', ctrl2) + + +app.get('/', function (req, res) { + // ruleid:vm_runinnewcontext_injection + var sandbox = { + foo: req.query.userInput + } + vm.runInNewContext('safeEval(orderLinesData)', sandbox, { timeout: 2000 }) + res.send('hello world') +}) + + +app.get('/', function (req, res) { + // ruleid:vm_code_injection + const code = ` + var x = ${req.query.userInput}; + ` + vm.runInThisContext(code) + res.send('hello world') +}) + + +app.get('/', function test4(req, res) { + const parsingContext = vm.createContext({ name: 'world' }) + // ruleid:vm_code_injection + const code = `return 'hello ' + ${req.query.userInput}` + let fn = vm.compileFunction(code, [], { parsingContext }) + res.send('hello world') +}) + + +app.get('/', (req, res) => { + // ruleid:vm_compilefunction_injection + const context = vm.createContext({ name: req.query.userInput }) + let code = `return 'hello ' name` + const fn = vm.compileFunction(code, [], { parsingContext: context }) + res.send('hello world') +}) + +app.get('/', function (req, res) { + // ruleid:vm_code_injection + const script = new vm.Script(` + function add(a, b) { + return a + ${req.query.userInput}; + } + + const x = add(1, 2); + `); + + script.runInThisContext(); + res.send('hello world') +}) diff --git a/rules/eval/eval_vm_injection.yaml b/rules/eval/eval_vm_injection.yaml new file mode 100644 index 0000000..0b732c7 --- /dev/null +++ b/rules/eval/eval_vm_injection.yaml @@ -0,0 +1,331 @@ +rules: + - id: vm_runincontext_injection + patterns: + - pattern-inside: | + require('vm'); + ... + - pattern-either: + - pattern-inside: 'function ($REQ, $RES, ...) {...}' + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: | + $CONTEXT = <... $REQ.$QUERY.$FOO ...>; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$QUERY.$FOO ...>}; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = <... {$NAME:$REQ.$QUERY.$FOO} ...>; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $VAR = <... $REQ.$QUERY.$FOO ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = <... $REQ.$QUERY.$FOO ...>; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = <... {$NAME:$REQ.$QUERY.$FOO} ...>; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$QUERY.$FOO ...>}; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $VAR = <... $REQ.$QUERY.$FOO ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = <... $REQ.$BODY ...>; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$BODY ...>}; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = <... {$NAME:$REQ.$BODY} ...>; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $VAR = <... $REQ.$BODY ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = <... $REQ.$BODY ...>; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = <... {$NAME:$REQ.$BODY} ...>; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$BODY ...>}; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $VAR = <... $REQ.$BODY ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $VM.runInContext($CODE,<... $CONTEXT ...>,...); + message: Untrusted user input in `vm.runInContext()` can result in code injection. + severity: ERROR + languages: + - javascript + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-94: Improper Control of Generation of Code (Code Injection)' + - id: vm_runinnewcontext_injection + patterns: + - pattern-inside: | + require('vm'); + ... + - pattern-either: + - pattern-inside: 'function ($REQ, $RES, ...) {...}' + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: | + $VM.runInNewContext($CODE,<... $REQ.$QUERY.$FOO ...>,...); + - pattern: | + $CONTEXT = <... $REQ.$QUERY.$FOO ...>; + ... + $VM.runInNewContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$QUERY.$FOO ...>}; + ... + $VM.runInNewContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = <... {$NAME:$REQ.$QUERY.$FOO} ...>; + ... + $VM.runInNewContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $VAR = <... $REQ.$QUERY.$FOO ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $VM.runInNewContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $VM.runInNewContext($CODE,<... $REQ.$BODY ...>,...); + - pattern: | + $CONTEXT = <... $REQ.$BODY ...>; + ... + $VM.runInNewContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$BODY ...>}; + ... + $VM.runInNewContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $CONTEXT = <... {$NAME:$REQ.$BODY} ...>; + ... + $VM.runInNewContext($CODE,<... $CONTEXT ...>,...); + - pattern: | + $VAR = <... $REQ.$BODY ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $VM.runInNewContext($CODE,<... $CONTEXT ...>,...); + message: >- + Untrusted user input in `vm.runInNewContext()` can result in code + injection. + severity: ERROR + languages: + - javascript + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-94: Improper Control of Generation of Code (Code Injection)' + - id: vm_compilefunction_injection + patterns: + - pattern-inside: | + require('vm'); + ... + - pattern-either: + - pattern-inside: 'function ($REQ, $RES, ...) {...}' + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: > + $VM.compileFunction($CODE,$PARAMS,{parsingContext: <... $REQ.$QUERY.$FOO ...>},...); + - pattern: > + $CONTEXT = <... $REQ.$QUERY.$FOO ...>; + ... + $VM.compileFunction($CODE,$PARAMS,{parsingContext: <... $CONTEXT ...>},...); + - pattern: > + $CONTEXT = <... {$NAME:$REQ.$QUERY.$FOO} ...>; + ... + $VM.compileFunction($CODE,$PARAMS,{parsingContext: <... $CONTEXT ...>},...); + - pattern: > + $CONTEXT = {$NAME: <... $REQ.$QUERY.$FOO ...>}; + ... + $VM.compileFunction($CODE,$PARAMS,{parsingContext: <... $CONTEXT ...>},...); + - pattern: > + $VAR = <... $REQ.$QUERY.$FOO ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $VM.compileFunction($CODE,$PARAMS,{parsingContext: <... $CONTEXT ...>},...); + - pattern: | + $OPTS = {parsingContext: <... $REQ.$QUERY.$FOO ...>}; + ... + $VM.compileFunction($CODE,$PARAMS,$OPTS,...); + - pattern: | + $CONTEXT = <... $REQ.$QUERY.$FOO ...>; + ... + $OPTS = {parsingContext: <... $CONTEXT ...>}; + ... + $VM.compileFunction($CODE,$PARAMS,$OPTS,...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$QUERY.$FOO ...>}; + ... + $OPTS = {parsingContext: <... $CONTEXT ...>}; + ... + $VM.compileFunction($CODE,$PARAMS,$OPTS,...); + - pattern: | + $VAR = <... $REQ.$QUERY.$FOO ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $OPTS = {parsingContext: <... $CONTEXT ...>}; + ... + $VM.compileFunction($CODE,$PARAMS,$OPTS,...); + - pattern: > + $VM.compileFunction($CODE,$PARAMS,{parsingContext: <... $REQ.$BODY ...>},...); + - pattern: > + $CONTEXT = <... $REQ.$BODY ...>; + ... + $VM.compileFunction($CODE,$PARAMS,{parsingContext: <... $CONTEXT ...>},...); + - pattern: > + $CONTEXT = <... {$NAME:$REQ.$BODY} ...>; + ... + $VM.compileFunction($CODE,$PARAMS,{parsingContext: <... $CONTEXT ...>},...); + - pattern: > + $CONTEXT = {$NAME: <... $REQ.$BODY ...>}; + ... + $VM.compileFunction($CODE,$PARAMS,{parsingContext: <... $CONTEXT ...>},...); + - pattern: > + $VAR = <... $REQ.$BODY ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $VM.compileFunction($CODE,$PARAMS,{parsingContext: <... $CONTEXT ...>},...); + - pattern: | + $OPTS = {parsingContext: <... $REQ.$BODY ...>}; + ... + $VM.compileFunction($CODE,$PARAMS,$OPTS,...); + - pattern: | + $CONTEXT = <... $REQ.$BODY ...>; + ... + $OPTS = {parsingContext: <... $CONTEXT ...>}; + ... + $VM.compileFunction($CODE,$PARAMS,$OPTS,...); + - pattern: | + $CONTEXT = {$NAME: <... $REQ.$BODY ...>}; + ... + $OPTS = {parsingContext: <... $CONTEXT ...>}; + ... + $VM.compileFunction($CODE,$PARAMS,$OPTS,...); + - pattern: | + $VAR = <... $REQ.$BODY ...>; + ... + $CONTEXT = {$NAME: <... $VAR ...>}; + ... + $OPTS = {parsingContext: <... $CONTEXT ...>}; + ... + $VM.compileFunction($CODE,$PARAMS,$OPTS,...); + message: >- + Untrusted user input in `vm.compileFunction()` can result in code + injection. + severity: ERROR + languages: + - javascript + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-94: Improper Control of Generation of Code (Code Injection)' + - id: vm_code_injection + patterns: + - pattern-inside: | + $VM = require('vm'); + ... + - pattern-either: + - pattern-inside: 'function ($REQ, $RES, ...) {...}' + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: '$VM.runInContext(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$VM.runInContext(<... $REQ.$BODY ...>,...)' + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $VM.runInContext($INPUT,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $VM.runInContext($INPUT,...); + - pattern: '$VM.runInNewContext(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$VM.runInNewContext(<... $REQ.$BODY ...>,...)' + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $VM.runInNewContext($INPUT,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $VM.runInNewContext($INPUT,...); + - pattern: '$VM.runInThisContext(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$VM.runInThisContext(<... $REQ.$BODY ...>,...)' + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $VM.runInThisContext($INPUT,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $VM.runInThisContext($INPUT,...); + - pattern: '$VM.compileFunction(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$VM.compileFunction(<... $REQ.$BODY ...>,...)' + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $VM.compileFunction($INPUT,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $VM.compileFunction($INPUT,...); + - pattern: 'new $VM.Script(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: 'new $VM.Script(<... $REQ.$BODY ...>,...)' + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + new $VM.Script($INPUT,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + new $VM.Script($INPUT,...); + message: Untrusted user input reaching `vm` can result in code injection. + severity: ERROR + languages: + - javascript + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-94: Improper Control of Generation of Code (Code Injection)' diff --git a/rules/eval/eval_yaml_deserialize.js b/rules/eval/eval_yaml_deserialize.js new file mode 100644 index 0000000..b816a69 --- /dev/null +++ b/rules/eval/eval_yaml_deserialize.js @@ -0,0 +1,14 @@ +var untrusted_code = '"toString": ! "function (){very_evil_thing();}"'; +var notneeded = 1; +// I'm just converting that string, what could possibly go wrong? +// ruleid:yaml_deserialize +require('js-yaml').load(untrusted_code) + '' + +var yaml = require('js-yaml') + +const yaml2 = require('js-yaml') + +// ruleid:yaml_deserialize +yaml.load(untrusted_code) +// ruleid:yaml_deserialize +yaml2.load(untrusted_code) \ No newline at end of file diff --git a/rules/eval/eval_yaml_deserialize.yaml b/rules/eval/eval_yaml_deserialize.yaml new file mode 100644 index 0000000..5ef6b08 --- /dev/null +++ b/rules/eval/eval_yaml_deserialize.yaml @@ -0,0 +1,17 @@ +rules: + - id: yaml_deserialize + patterns: + - pattern-inside: | + require('js-yaml'); + ... + - pattern: | + $X.load(...) + message: >- + User controlled data in 'yaml.load()' function can result in Remote Code + Injection. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A8: Insecure Deserialization' + cwe: 'CWE-502: Deserialization of Untrusted Data' \ No newline at end of file diff --git a/rules/eval/server_side_template_injection.js b/rules/eval/server_side_template_injection.js new file mode 100644 index 0000000..49a841b --- /dev/null +++ b/rules/eval/server_side_template_injection.js @@ -0,0 +1,45 @@ +var handlebars = require('handlebars'), + fs = require('fs'), + Sqrl = require('squirrelly'); +// do not match +var template = handlebars.compile(source); + +app.get('/', function (req, res) { + var storeName = "console.log(process.pid)" // this should be a user-controlled string + function getStoreName() { + return storeName; + } + var scope = { + getStoreName: getStoreName + } + + fs.readFile('example.html', 'utf-8', function (error, source) { + // ruleid:server_side_template_injection + var template = handlebars.compile(source + req.foo); + // ruleid:server_side_template_injection + handlebars.compile(source + req.foo.bar); + + + var myTemplate = 'Hi, my name is {{name}}' + // ruleid:server_side_template_injection + var temp = myTemplate + req.foo['bar'] + var compiled = Sqrl.Compile(temp) + + + // ruleid:server_side_template_injection + var xx = source.replace('', req.foo) + handlebars.compile(xx) + + + // ruleid:server_side_template_injection + var x = source + req.foo; + var z = 2; + handlebars.compile(x); + + var html = template(data); + console.log(html) + }); + + //do not match + var template = handlebars.compile(source); +}); \ No newline at end of file diff --git a/rules/eval/server_side_template_injection.yaml b/rules/eval/server_side_template_injection.yaml new file mode 100644 index 0000000..b65e6e2 --- /dev/null +++ b/rules/eval/server_side_template_injection.yaml @@ -0,0 +1,78 @@ +rules: + - id: server_side_template_injection + patterns: + - pattern-either: + - pattern-inside: | + require('handlebars'); + ... + - pattern-inside: | + require('pug'); + ... + - pattern-inside: | + require('hamljs'); + ... + - pattern-inside: | + require('ejs'); + ... + - pattern-inside: | + require('squirrelly'); + ... + - pattern-inside: | + require('eta'); + ... + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $HB.compile(..., <... $REQ.$FOO ...>, ...) + - pattern: | + $HB.compile(..., <... $REQ.$FOO.$BAR ...>, ...) + - pattern: | + $X = <... $REQ.$FOO ...>; + ... + $HB.compile(..., <... $X ...>, ...); + - pattern: | + $X = <... $REQ.$FOO.$BAR ...>; + ... + $HB.compile(..., <... $X ...>, ...); + - pattern: | + $X = $SOURCE.replace('...', <... $REQ.$FOO ...>, ...); + ... + $HB.compile(..., <... $X ...>, ...); + - pattern: | + $X = $SOURCE.replace('...', <... $REQ.$FOO.$BAR ...>, ...); + ... + $HB.compile(..., <... $X ...>, ...); + - pattern: | + $HB.Compile(..., <... $REQ.$FOO ...>, ...) + - pattern: | + $HB.Compile(..., <... $REQ.$FOO.$BAR ...>, ...) + - pattern: | + $X = <... $REQ.$FOO ...>; + ... + $HB.Compile(..., <... $X ...>, ...); + - pattern: | + $X = <... $REQ.$FOO.$BAR ...>; + ... + $HB.Compile(..., <... $X ...>, ...); + - pattern: | + $X = $SOURCE.replace('...', <... $REQ.$FOO ...>, ...); + ... + $HB.Compile(..., <... $X ...>, ...); + - pattern: | + $X = $SOURCE.replace('...', <... $REQ.$FOO.$BAR ...>, ...); + ... + $HB.Compile(..., <... $X ...>, ...); + message: >- + Untrusted user input in templating engine's compile() function can result + in Remote Code Execution via server side template injection. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-94: Improper Control of Generation of Code (Code Injection)' \ No newline at end of file diff --git a/rules/exec/exec_os_command.js b/rules/exec/exec_os_command.js new file mode 100644 index 0000000..5dc7b36 --- /dev/null +++ b/rules/exec/exec_os_command.js @@ -0,0 +1,118 @@ + +const { exec, spawn } = require('child_process'); + + +router.post('/ping', (req, res) => { + // ruleid:generic_os_command_exec2 + exec(`${req.body.url}`, (error) => { + if (error) { + return res.send('error'); + } + res.send('pong') + }) + +}) + +router.post('/gzip', (req, res) => { + // ruleid:generic_os_command_exec2 + exec( + 'gzip ' + req.query.file_path, + function (err, data) { + console.log('err: ', err) + console.log('data: ', data); + res.send('done'); + }); +}) + +var child_process = require('child_process'); +var x = 1; +app.get('/', function (req, res) { + // ruleid:generic_os_command_exec + child_process.exec( + req.query.file_path, + function (err, data) { + console.log('err: ', err) + console.log('data: ', data); + }); + + // ruleid:generic_os_command_exec + child_process.exec('gzip' + + req.query.file_path, + function (err, data) { + console.log('err: ', err) + console.log('data: ', data); + }); + + // ruleid:generic_os_command_exec + child_process.exec('foobar' + + req.query.file_path + "asdD", + function (err, data) { + console.log('err: ', err) + console.log('data: ', data); + }); + + // ruleid:generic_os_command_exec + child_process.exec( + req.query.file_path + "asdD", + function (err, data) { + console.log('err: ', err) + console.log('data: ', data); + }); + + //Do not detect this + child_process.exec( + foo + "asdD", + function (err, data) { + console.log('err: ', err) + console.log('data: ', data); + }); + + // ruleid:generic_os_command_exec + child_process.execSync( + req.query.file_path + 'rsync -avAXz --info=progress2 "/src" "/dest"', + { stdio: 'inherit' }); + + res.send('Hello World!') + + + // ruleid:generic_os_command_exec + var foo = req.query.ping; + var x; + child_process.exec('ping -c 2 ' + foo, function (err, data) { + response.end(); + }); +}) + +var foo = '1'; +require('child_process').exec(foo + 'info=progress2 "/src" "/dest"'); + + +const router = require('express').Router(); +const exe = require('child_process'); + +router.post('/', function (req, res) { + // ruleid:generic_os_command_exec + exe.exec('ls ' + req.body.dir, function (err, data) { + if (!err) { + res.json({ message: data }); + } else { + res.status(500).json({ message: err }); + } + }); +}); + +module.exports = router; + + +var http = require("http"); +var url = require("url"); +var exe = require('child_process'); +http.createServer(function (request, response) { + // ruleid:generic_os_command_exec + var parsedUrl = url.parse(request.url, true); + exe.exec('ping -c 2 ' + parsedUrl.query.ping, function (err, data) { + response.end(); + }); + +}).listen(8888); + diff --git a/rules/exec/exec_os_command.yaml b/rules/exec/exec_os_command.yaml new file mode 100644 index 0000000..abe42f0 --- /dev/null +++ b/rules/exec/exec_os_command.yaml @@ -0,0 +1,91 @@ +rules: + - id: generic_os_command_exec + patterns: + - pattern-inside: | + require('child_process'); + ... + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $EXEC.exec(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + $EXEC.exec(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + $EXEC.execSync(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + $EXEC.execSync(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + $EXEC.exec(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + $EXEC.exec(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + $EXEC.execSync(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + $EXEC.execSync(..., <... $INP ...>, ...); + message: >- + User controlled data in 'child_process.exec()' can result in Remote OS + Command Execution. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-78: Improper Neutralization of Special Elements used in an OS + Command ('OS Command Injection') + - id: generic_os_command_exec2 + patterns: + - pattern-inside: | + var {$EXEC} = require('child_process'); + ... + - pattern-inside: | + $APP.$METHOD(..., function $FUNC($REQ, $RES, ...){ ... }); + - pattern-either: + - pattern: | + exec(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + exec(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + execSync(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + execSync(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + exec(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + exec(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + execSync(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + execSync(..., <... $INP ...>, ...); + message: >- + User controlled data in 'child_process.exec()' can result in Remote OS + Command Execution. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-78: Improper Neutralization of Special Elements used in an OS + Command ('OS Command Injection') \ No newline at end of file diff --git a/rules/exec/exec_shelljs.js b/rules/exec/exec_shelljs.js new file mode 100644 index 0000000..a3a3bfc --- /dev/null +++ b/rules/exec/exec_shelljs.js @@ -0,0 +1,14 @@ +const shell = require('shelljs'); +const express = require('express') +const router = express.Router() + +router.get('/greeting', (req, res) => { + // ruleid:shelljs_os_command_exec + return shell.exec(req.query, { silent: true }) +}) + +router.get('/foo', (req, res) => { + // ruleid:shelljs_os_command_exec + const input = `ls ${req.query}` + return shell.exec(input, { silent: true }) +}) \ No newline at end of file diff --git a/rules/exec/exec_shelljs.yaml b/rules/exec/exec_shelljs.yaml new file mode 100644 index 0000000..e765bbb --- /dev/null +++ b/rules/exec/exec_shelljs.yaml @@ -0,0 +1,36 @@ +rules: + - id: shelljs_os_command_exec + patterns: + - pattern-inside: | + require('shelljs'); + ... + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $EXEC.exec(<... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + $EXEC.exec( <... $REQ.$QUERY ...>, ...) + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + $EXEC.exec(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + $EXEC.exec(<... $INP ...>, ...); + message: >- + User controlled data in 'shelljs.exec()' can result in Remote OS + Command Execution. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-78: Improper Neutralization of Special Elements used in an OS + Command ('OS Command Injection') \ No newline at end of file diff --git a/rules/generic/error_disclosure.yaml b/rules/generic/error_disclosure.yaml new file mode 100644 index 0000000..4a72faa --- /dev/null +++ b/rules/generic/error_disclosure.yaml @@ -0,0 +1,51 @@ +rules: + - id: node_error_disclosure + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $ERR = $ERROR.stack; + ... + $RES.end($ERR); + - pattern: | + $ERR = $ERROR.stack; + ... + $RES.send($ERR); + - pattern: | + $RES.end($ERR.stack) + - pattern: | + $RES.send($ERR.stack) + message: >- + Error messages with stack traces can expose sensitive information about + the application. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A3: Sensitive Data Exposure' + cwe: 'CWE-209: Generation of Error Message Containing Sensitive Information' + - id: generic_error_disclosure + patterns: + - pattern-either: + - pattern: | + console.trace(...) + - pattern: | + try { + ... + } catch($ERR){ + console.error(<... $ERR ...>, ...); + } + message: >- + Error messages with stack traces may expose sensitive information about + the application. + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A3: Sensitive Data Exposure' + cwe: 'CWE-209: Generation of Error Message Containing Sensitive Information' \ No newline at end of file diff --git a/rules/generic/error_info_disclosure.js b/rules/generic/error_info_disclosure.js new file mode 100644 index 0000000..f02579c --- /dev/null +++ b/rules/generic/error_info_disclosure.js @@ -0,0 +1,23 @@ +app.get('/', function (req, res) { + try { + foo; + } + catch (err) { + res.statusCode = 500; + res.setHeader("Content-Type", "text/plain"); + // ruleid:node_error_disclosure + res.end(err.stack); + return; + } +}); + + +// ruleid:generic_error_disclosure +try { + throw new Error("Something unexpected has occurred."); +} catch (e) { + console.error(e); +} + +// ruleid:generic_error_disclosure +console.trace("baad") \ No newline at end of file diff --git a/rules/generic/hardcoded_passport.js b/rules/generic/hardcoded_passport.js new file mode 100644 index 0000000..5885cbd --- /dev/null +++ b/rules/generic/hardcoded_passport.js @@ -0,0 +1,238 @@ +// ruleid: hardcoded_passport_secret +const Strat = require("passport-jwt").Strategy; +console.log("words"); +var x = new Strat({ secretOrKey: "secret" }); + + +var passport = require('passport') + +module.exports = class Auth { + constructor(config) { + this.passport = passport + // ruleid: hardcoded_passport_secret + var JwtStrategy = require('passport-jwt').Strategy + this.jwt_secret = 'HARDCODED-SECRET' + + passport.use(new JwtStrategy({ + secretOrKey: this.jwt_secret + }, function (payload, done) { + // auth callback + })) + } + + something(req, res, next) { + // do something + } + +} + +var FACEBOOK_APP_ID = require('../../../config').fbAppID; +var URLcallback = require('../../../config').URL; +var passport = require('passport'); +// ruleid: hardcoded_passport_secret +var FacebookStrategy = require('passport-facebook').Strategy; +var FACEBOOK_APP_SECRET = "HARDCODED-SECRET"; + + +module.exports = function (req, res) { + + passport.use(new FacebookStrategy({ + clientID: FACEBOOK_APP_ID, + clientSecret: FACEBOOK_APP_SECRET, + callbackURL: URLcallback + '/api/auth/facebook/callback' + }, + function (accessToken, refreshToken, profile, done) { + // do something + } + )); +}; + + +let passport = require('passport'); +// ruleid: hardcoded_passport_secret +let JwtStrategy = require('passport-jwt').Strategy; +let ExtractJwt = require('passport-jwt').ExtractJwt; + +/** + * + * @param {object} options + * @param {object} options.logger + * @param {object} options.jwtConfig + * @returns {object} + */ +module.exports = (options) => { + let jwtConfig = options.jwtConfig; + let logger = options.logger; + let secretz = 'HARDCODED-SECRET'; + + this.passportOptions = { + jwtFromRequest: ExtractJwt.fromHeader(jwtConfig.headerKey), + secretOrKey: secretz, + issuer: jwtConfig.issuer, + audience: jwtConfig.audience + }; + passport.use(new JwtStrategy(this.passportOptions, (jwt_payload, done) => { + // do something + })); + return passport.authenticate('jwt', { session: false }) +}; + +'use strict'; +// ruleid: hardcoded_passport_secret +const FacebookStrategy = require('passport-facebook').Strategy; + +exports.init = function (passport, router, config) { + + passport.use( + new FacebookStrategy( + { + clientID: config.appId, + clientSecret: 'HARDCODED-SECRET', + callbackURL: config.publicAddress + config.callbackURL, + enableProof: false, + passReqToCallback: true, + }, + function (req, accessToken, refreshToken, profile, done) { + // do something + }, + ), + ); +}; + +var passport = require('passport'); + +// ruleid: hardcoded_passport_secret +var JwtStrategy = require('passport-jwt').Strategy, + ExtractJwt = require('passport-jwt').ExtractJwt; + +var opts = {} +opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); +opts.secretOrKey = 'hardcoded-secret'; +opts.issuer = 'accounts.examplesoft.com'; +opts.audience = 'yoursite.net'; +passport.use(new JwtStrategy(opts, function (jwt_payload, done) { + User.findOnez({ id: jwt_payload.sub }, function (err, user) { + if (err) { + return done(err, false); + } + if (user) { + return done(null, user); + } else { + return done(null, false); + // or you could create a new account + } + }); +})); + +// ruleid: hardcoded_passport_secret +var FacebookStrategy = require('passport-facebook').Strategy + +passport.use(new FacebookStrategy({ + clientID: FACEBOOK_APP_ID, + clientSecret: "hardcoded-secret", + callbackURL: "http://localhost:3000/auth/facebook/callback" +}, + function (accessToken, refreshToken, profile, cb) { + User.findOrCreate({ facebookId: profile.id }, function (err, user) { + return cb(err, user); + }); + } +)); + +// ruleid: hardcoded_passport_secret +var GoogleStrategy = require('passport-google-oauth2').Strategy; + +passport.use(new GoogleStrategy({ + clientID: GOOGLE_CLIENT_ID, + clientSecret: 'hardcoded-secret', + callbackURL: "http://yourdormain:3000/auth/google/callback", + passReqToCallback: true +}, + function (request, accessToken, refreshToken, profile, done) { + User.findOrCreate({ googleId: profile.id }, function (err, user) { + return done(err, user); + }); + } +)); + +// ruleid: hardcoded_passport_secret +var TwitterStrategy = require('passport-twitter').Strategy; + +passport.use(new TwitterStrategy({ + consumerKey: TWITTER_CONSUMER_KEY, + consumerSecret: "hardcoded-secret", + callbackURL: "http://127.0.0.1:3000/auth/twitter/callback" +}, + function (token, tokenSecret, profile, cb) { + User.findOrCreate({ twitterId: profile.id }, function (err, user) { + return cb(err, user); + }); + } +)); + +// ruleid: hardcoded_passport_secret +var GoogleStrategy = require('passport-google-oauth1').Strategy; + +passport.use(new GoogleStrategy({ + consumerKey: 'www.example.com', + consumerSecret: 'hardcoded-secret', + callbackURL: "http://127.0.0.1:3000/auth/google/callback" +}, + function (token, tokenSecret, profile, cb) { + User.findOrCreate({ googleId: profile.id }, function (err, user) { + return cb(err, user); + }); + } +)); + +// ruleid: hardcoded_passport_secret +var Auth0Strategy = require('passport-auth0').Strategy; + +var strategy = new Auth0Strategy({ + domain: 'your-domain.auth0.com', + clientID: 'your-client-id', + clientSecret: 'hardcoded-secret', + callbackURL: '/callback' +}, + function (accessToken, refreshToken, extraParams, profile, done) { + return done(null, profile); + } +); + +passport.use(strategy); + +// ruleid: hardcoded_passport_secret +var OAuth1Strategy = require('passport-oauth1').Strategy; + +passport.use(new OAuth1Strategy({ + requestTokenURL: 'https://www.example.com/oauth/request_token', + accessTokenURL: 'https://www.example.com/oauth/access_token', + userAuthorizationURL: 'https://www.example.com/oauth/authorize', + consumerKey: EXAMPLE_CONSUMER_KEY, + consumerSecret: "hardcoded-secret", + callbackURL: "http://127.0.0.1:3000/auth/example/callback", + signatureMethod: "RSA-SHA1" +}, + function (token, tokenSecret, profile, cb) { + User.findOrCreate({ exampleId: profile.id }, function (err, user) { + return cb(err, user); + }); + } +)); + +// ruleid: hardcoded_passport_secret +var OAuth2Strategy = require('passport-oauth2').Strategy; + +passport.use(new OAuth2Strategy({ + authorizationURL: 'https://www.example.com/oauth2/authorize', + tokenURL: 'https://www.example.com/oauth2/token', + clientID: EXAMPLE_CLIENT_ID, + clientSecret: "hardcoded-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); + }); + } +)); \ No newline at end of file diff --git a/rules/generic/hardcoded_passport.yaml b/rules/generic/hardcoded_passport.yaml new file mode 100644 index 0000000..ccaca31 --- /dev/null +++ b/rules/generic/hardcoded_passport.yaml @@ -0,0 +1,716 @@ +rules: + - id: hardcoded_passport_secret + pattern-either: + - pattern: | + $F = require("passport-auth0").Strategy; + ... + new $F({clientSecret: "..."}, ...); + - pattern: | + $F = require("passport-auth0").Strategy; + ... + $P.clientSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-auth0").Strategy; + ... + var $P = {clientSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-auth0").Strategy; + ... + var $S = "..."; + ... + new $F({clientSecret: $S}, ...); + - pattern: | + $F = require("passport-auth0").Strategy; + ... + var $S = "..."; + ... + $P.clientSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-auth0").Strategy; + ... + var $S = "..."; + ... + var $P = {clientSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-auth0").Strategy; + ... + $P.clientSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-auth0").Strategy; + ... + $P = {clientSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-auth0").Strategy; + ... + $S = "..."; + ... + new $F({clientSecret: $S}, ...); + - pattern: | + $F = require("passport-auth0").Strategy; + ... + $S = "..."; + ... + $P.clientSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-auth0").Strategy; + ... + $S = "..."; + ... + var $P = {clientSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-auth0").Strategy; + ... + $S = "..."; + ... + $P.clientSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-auth0").Strategy; + ... + $S = "..."; + ... + $P = {clientSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + new $F({clientSecret: "..."}, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + $P.clientSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + var $P = {clientSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + var $S = "..."; + ... + new $F({clientSecret: $S}, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + var $S = "..."; + ... + $P.clientSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + var $S = "..."; + ... + var $P = {clientSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + $P.clientSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + $P = {clientSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + $S = "..."; + ... + new $F({clientSecret: $S}, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + $S = "..."; + ... + $P.clientSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + $S = "..."; + ... + var $P = {clientSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + $S = "..."; + ... + $P.clientSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth2").Strategy; + ... + $S = "..."; + ... + $P = {clientSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + new $F({secretOrKey: "..."}, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + $P.secretOrKey = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + var $P = {secretOrKey: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + var $S = "..."; + ... + new $F({secretOrKey: $S}, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + var $S = "..."; + ... + $P.secretOrKey = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + var $S = "..."; + ... + var $P = {secretOrKey: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + $P.secretOrKey = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + $P = {secretOrKey: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + $S = "..."; + ... + new $F({secretOrKey: $S}, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + $S = "..."; + ... + $P.secretOrKey = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + $S = "..."; + ... + var $P = {secretOrKey: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + $S = "..."; + ... + $P.secretOrKey = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-jwt").Strategy; + ... + $S = "..."; + ... + $P = {secretOrKey: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + new $F({consumerSecret: "..."}, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + $P.consumerSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + var $P = {consumerSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + var $S = "..."; + ... + new $F({consumerSecret: $S}, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + var $S = "..."; + ... + $P.consumerSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + var $S = "..."; + ... + var $P = {consumerSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + $P.consumerSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + $P = {consumerSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + $S = "..."; + ... + new $F({consumerSecret: $S}, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + $S = "..."; + ... + $P.consumerSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + $S = "..."; + ... + var $P = {consumerSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + $S = "..."; + ... + $P.consumerSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-google-oauth1").Strategy; + ... + $S = "..."; + ... + $P = {consumerSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + new $F({clientSecret: "..."}, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + $P.clientSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + var $P = {clientSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + var $S = "..."; + ... + new $F({clientSecret: $S}, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + var $S = "..."; + ... + $P.clientSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + var $S = "..."; + ... + var $P = {clientSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + $P.clientSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + $P = {clientSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + $S = "..."; + ... + new $F({clientSecret: $S}, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + $S = "..."; + ... + $P.clientSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + $S = "..."; + ... + var $P = {clientSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + $S = "..."; + ... + $P.clientSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth2").Strategy; + ... + $S = "..."; + ... + $P = {clientSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + new $F({clientSecret: "..."}, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + $P.clientSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + var $P = {clientSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + var $S = "..."; + ... + new $F({clientSecret: $S}, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + var $S = "..."; + ... + $P.clientSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + var $S = "..."; + ... + var $P = {clientSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + $P.clientSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + $P = {clientSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + $S = "..."; + ... + new $F({clientSecret: $S}, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + $S = "..."; + ... + $P.clientSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + $S = "..."; + ... + var $P = {clientSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + $S = "..."; + ... + $P.clientSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-facebook").Strategy; + ... + $S = "..."; + ... + $P = {clientSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + new $F({consumerSecret: "..."}, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + $P.consumerSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + var $P = {consumerSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + var $S = "..."; + ... + new $F({consumerSecret: $S}, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + var $S = "..."; + ... + $P.consumerSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + var $S = "..."; + ... + var $P = {consumerSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + $P.consumerSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + $P = {consumerSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + $S = "..."; + ... + new $F({consumerSecret: $S}, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + $S = "..."; + ... + $P.consumerSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + $S = "..."; + ... + var $P = {consumerSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + $S = "..."; + ... + $P.consumerSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-twitter").Strategy; + ... + $S = "..."; + ... + $P = {consumerSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth1").Strategy; + ... + new $F({consumerSecret: "..."}, ...); + - pattern: | + $F = require("passport-oauth1").Strategy; + ... + $P.consumerSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth1").Strategy; + ... + var $P = {consumerSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth1").Strategy; + ... + var $S = "..."; + ... + new $F({consumerSecret: $S}, ...); + - pattern: | + $F = require("passport-oauth1").Strategy; + ... + var $S = "..."; + ... + $P.consumerSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth1").Strategy; + ... + var $S = "..."; + ... + var $P = {consumerSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth1").Strategy; + ... + $P.consumerSecret = "..."; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth1").Strategy; + ... + $P = {consumerSecret: "..."}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth1").Strategy; + ... + $S = "..."; + ... + new $F({consumerSecret: $S}, ...); + - pattern: | + $F = require("passport-oauth1").Strategy; + ... + $S = "..."; + ... + $P.consumerSecret = $S; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth1").Strategy; + ... + $S = "..."; + ... + var $P = {consumerSecret: $S}; + ... + new $F($P, ...); + - pattern: | + $F = require("passport-oauth1").Strategy; + ... + $S = "..."; + ... + $P.consumerSecret = $S; + ... + new $F($P, ...); + - pattern: |- + $F = require("passport-oauth1").Strategy; + ... + $S = "..."; + ... + $P = {consumerSecret: $S}; + ... + new $F($P, ...); + message: >- + Hardcoded plain text secret used for Passport Strategy. Store it properly + in an environment variable. + metadata: + cwe: 'CWE-522: Insufficiently Protected Credentials' + owasp: 'A2: Broken Authentication' + languages: + - javascript + severity: ERROR diff --git a/rules/generic/hardcoded_secrets.js b/rules/generic/hardcoded_secrets.js new file mode 100644 index 0000000..6d7a3d9 --- /dev/null +++ b/rules/generic/hardcoded_secrets.js @@ -0,0 +1,51 @@ +// ruleid:node_password +password = '1212'; +foo = "adasd"; +x = 1; +password = x; +password = ''; +// ruleid:node_username +username = 'ajin-test-user' +x.password = 123 +x["password"] = 1; +pass = 123; +// ruleid:node_password +PASSWORD = '12211'; + +// ruleid:node_password +obj['password'] = '121233'; +// ruleid:node_password +obj2.password = '1234'; +// ruleid:node_password +obj2.pass = '1234'; +// ruleid:node_password +obj2["pass"] = '1234'; + +// ruleid:node_password +const password = '1212'; +// ruleid:node_password +let password = '1212'; +// ruleid:node_password +var password = '1212'; + +// ruleid:node_api_key +angular.module('starter.services', []).constant('api_key', '6e906986c3b199c51fff3154cfb76979') +this.apiUrl = api_url; +// ruleid:node_secret +aws('secret', 'asdasdadasddd') +// ruleid:node_api_key +x.config('APIKEY', 'asdsdadsada') +// ruleid:node_api_key +api_key = "213123123123"; +// ruleid:node_api_key +api["apikey"] = "asddadasddad" +// ruleid:node_api_key +obj('APIKEY') = 'asdasdsadasdad' +// ruleid:node_api_key +obj('api_key') = 'asdasdsadasdad' +// ruleid:node_api_key +obj('Api_Key') = 'asdasdsadasdad' +// ruleid:node_secret +secret = "Asdadasdasdda" +// ruleid:node_secret +obj.secret = "Asdadasdasddsa" \ No newline at end of file diff --git a/rules/generic/hardcoded_secrets.yaml b/rules/generic/hardcoded_secrets.yaml new file mode 100644 index 0000000..787cdc3 --- /dev/null +++ b/rules/generic/hardcoded_secrets.yaml @@ -0,0 +1,87 @@ +rules: + - id: node_password + patterns: + - pattern-not: $X = '' + - pattern-not: $OBJ[$X] = '' + - pattern-not: $OBJ.$X = '' + - pattern-either: + - pattern: | + $X = '...' + - metavariable-regex: + metavariable: '$X' + regex: '(?i:.*pass.*)' + message: >- + A hardcoded password in plain text is identified. Store it properly in an + environment variable. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A3: Sensitive Data Exposure' + cwe: 'CWE-798: Use of Hard-coded Credentials' + - id: node_username + patterns: + - pattern-not: $X = '' + - pattern-not: $OBJ[$X] = '' + - pattern-not: $OBJ.$X = '' + - pattern-either: + - pattern: | + $X = '...' + - metavariable-regex: + metavariable: '$X' + regex: '(?i:.*user.*)' + message: >- + A hardcoded username in plain text is identified. Store it properly in an + environment variable. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A3: Sensitive Data Exposure' + cwe: 'CWE-798: Use of Hard-coded Credentials' + - id: node_api_key + patterns: + - pattern-not: $X = '' + - pattern-not: $OBJ[$X] = '' + - pattern-not: $OBJ.$X = '' + - pattern-not: $OBJ($X, '') + - pattern-either: + - pattern: | + $X = '...' + - pattern: | + $Y($X, '...') + - metavariable-regex: + metavariable: '$X' + regex: '(?i).*(api_key|apikey)' + message: >- + A hardcoded API Key is identified. Store it properly in an + environment variable. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A3: Sensitive Data Exposure' + cwe: 'CWE-798: Use of Hard-coded Credentials' + - id: node_secret + patterns: + - pattern-not: $X = '' + - pattern-not: $OBJ[$X] = '' + - pattern-not: $OBJ.$X = '' + - pattern-not: $OBJ($X, '') + - pattern-either: + - pattern: | + $X = '...' + - pattern: | + $Y($X, '...') + - metavariable-regex: + metavariable: '$X' + regex: '(?i:.*secret)' + message: >- + A hardcoded secret is identified. Store it properly in an + environment variable. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A3: Sensitive Data Exposure' + cwe: 'CWE-798: Use of Hard-coded Credentials' \ No newline at end of file diff --git a/rules/generic/logic_bypass.yaml b/rules/generic/logic_bypass.yaml new file mode 100644 index 0000000..108cf39 --- /dev/null +++ b/rules/generic/logic_bypass.yaml @@ -0,0 +1,55 @@ +rules: + - id: node_logic_bypass + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $REQ.$FOO.$BAR !== $REQ.$ZOO.$ZAR + - pattern: | + $REQ.$FOO.$BAR === $REQ.$ZOO.$ZAR + - pattern: | + $REQ.$FOO.$BAR >= $REQ.$ZOO.$ZAR + - pattern: | + $REQ.$FOO.$BAR <= $REQ.$ZOO.$ZAR + - pattern: | + $REQ.$FOO.$BAR < $REQ.$ZOO.$ZAR + - pattern: | + $REQ.$FOO.$BAR > $REQ.$ZOO.$ZAR + - pattern: | + $REQ.$FOO['...'] !== $REQ.$ZOO['...'] + - pattern: | + $REQ.$FOO['...'] === $REQ.$ZOO['...'] + - pattern: | + $REQ.$FOO['...'] >= $REQ.$ZOO['...'] + - pattern: | + $REQ.$FOO['...'] <= $REQ.$ZOO['...'] + - pattern: | + $REQ.$FOO['...'] < $REQ.$ZOO['...'] + - pattern: | + $REQ.$FOO['...'] > $REQ.$ZOO['...'] + - pattern: | + $REQ.$FOO('...') !== $REQ.$ZOO('...') + - pattern: | + $REQ.$FOO('...') === $REQ.$ZOO('...') + - pattern: | + $REQ.$FOO('...') >= $REQ.$ZOO('...') + - pattern: | + $REQ.$FOO('...') <= $REQ.$ZOO('...') + - pattern: | + $REQ.$FOO('...') < $REQ.$ZOO('...') + - pattern: | + $REQ.$FOO('...') > $REQ.$ZOO('...') + message: >- + User controlled data is used for application business logic decision + making. This expose protected data or functionality. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A5: Broken Access Control' + cwe: 'CWE-807: Reliance on Untrusted Inputs in a Security Decision' \ No newline at end of file diff --git a/rules/generic/logic_user_controlled_checks.js b/rules/generic/logic_user_controlled_checks.js new file mode 100644 index 0000000..40b4b70 --- /dev/null +++ b/rules/generic/logic_user_controlled_checks.js @@ -0,0 +1,10 @@ +var express = require('express'); +var app = express(); +app.get('/view/:id', function (req, res) { + + // ruleid:node_logic_bypass + if (req.cookies["user"] === req.params["id"]) { + showProfile(); + } + +}); diff --git a/rules/good/good_anti_csrf.yaml b/rules/good/good_anti_csrf.yaml new file mode 100644 index 0000000..23d57c3 --- /dev/null +++ b/rules/good/good_anti_csrf.yaml @@ -0,0 +1,19 @@ +rules: + - id: anti_csrf_control + patterns: + - pattern-inside: | + $CSRUF = require('csurf'); + ... + - pattern-either: + - pattern: + $X = csrf(...); + - pattern: + $X = csurf(...); + - pattern: + $APP.use(csrf(...)); + - pattern: + $APP.use(csurf(...)); + message: 'This application has anti CSRF protection which prevents cross site request forgery attacks.' + languages: + - javascript + severity: WARNING \ No newline at end of file diff --git a/rules/good/good_helmet_checks.yaml b/rules/good/good_helmet_checks.yaml new file mode 100644 index 0000000..bbd9a4e --- /dev/null +++ b/rules/good/good_helmet_checks.yaml @@ -0,0 +1,236 @@ +# Convert this to INFO when semgrep supports that +rules: + - id: helmet_header_check_csp + message: >- + Content Security Policy header is present. More Information: + https://helmetjs.github.io/docs/csp/ + languages: + - javascript + severity: WARNING + patterns: + - pattern-not: | + $HELMET(..., {contentSecurityPolicy: false}, ...) + - pattern-either: + - pattern: | + helmet({contentSecurityPolicy: {directives: ...}}) + - pattern: | + helmet.contentSecurityPolicy({directives: ...}) + - pattern: | + csp({directives: ...}) + - id: helmet_header_check_crossdomain + message: >- + X-Permitted-Cross-Domain-Policies header set to off. More information: + https://helmetjs.github.io/docs/crossdomain/ + languages: + - javascript + severity: WARNING + patterns: + - pattern-not: | + $HELMET(..., {permittedCrossDomainPolicies: false}, ...) + - pattern-either: + - pattern: | + permittedCrossDomainPolicies() + - pattern: | + permittedCrossDomainPolicies({ permittedPolicies: ... }) + - pattern: | + helmet.permittedCrossDomainPolicies({ permittedPolicies: ... }) + - pattern: | + helmet({permittedCrossDomainPolicies: { permittedPolicies: ... }}) + - pattern: | + helmet.permittedCrossDomainPolicies() + - id: helmet_header_check_expect_ct + message: >- + Expect-CT header is present. More information: + https://helmetjs.github.io/docs/expect-ct/ + languages: + - javascript + severity: WARNING + patterns: + - pattern-not: | + $HELMET(..., {expectCt: false}, ...) + - pattern-either: + - pattern: | + expectCt({maxAge: ...,}) + - pattern: | + helmet.expectCt({maxAge: ...,}) + - pattern: | + expectCt({enforce: ...,}) + - pattern: | + hemlet.expectCt({enforce: ...,}) + - pattern: | + helmet({expectCt: { enforce: ... }}) + - id: helmet_header_feature_policy + message: >- + Feature-Policy header is present. More information: + https://helmetjs.github.io/docs/feature-policy/ + languages: + - javascript + severity: WARNING + patterns: + - pattern-not: | + $HELMET(..., {featurePolicy: false}, ...) + - pattern-either: + - pattern: | + featurePolicy(..., {features: ...}, ...) + - pattern: | + helmet.featurePolicy(..., {features: ...}, ...) + - pattern: | + helmet({featurePolicy: {features: ...}}) + - id: helmet_header_frame_guard + message: >- + X-Frame-Options header is present. More information: + https://helmetjs.github.io/docs/frameguard/ + languages: + - javascript + severity: WARNING + patterns: + - pattern-not: | + $HELMET(..., {frameguard: false}, ...) + - pattern-either: + - pattern: | + $APP.use(hemlet()) + - pattern: | + helmet.frameguard(...) + - pattern: | + frameguard(...) + - pattern: | + helmet({frameguard: ...}) + - id: helmet_header_dns_prefetch + message: >- + X-DNS-Prefetch-Control header is present and DNS Prefetch Control is + enabled. More information: + https://helmetjs.github.io/docs/dns-prefetch-control/ + languages: + - javascript + severity: WARNING + patterns: + - pattern-not: | + $HELMET(..., {dnsPrefetchControl: false}, ...) + - pattern-either: + - pattern: | + $APP.use(helmet()) + - pattern: | + helmet.dnsPrefetchControl() + - pattern: | + dnsPrefetchControl() + - pattern: | + helmet.dnsPrefetchControl({ allow: false }) + - pattern: | + helmet({dnsPrefetchControl: {allow: false}}) + - pattern: | + dnsPrefetchControl({ allow: false }) + - id: helmet_header_x_powered_by + message: >- + Default X-Powered-By is removed or modified. More information: + https://helmetjs.github.io/docs/hide-powered-by/ + languages: + - javascript + severity: WARNING + patterns: + - pattern-not: | + $HELMET(..., {hidePoweredBy: false}, ...) + - pattern-either: + - pattern: | + $APP.use(helmet()) + - pattern: | + app.disable('x-powered-by') + - pattern: | + helmet.hidePoweredBy(...) + - pattern: | + hidePoweredBy(...) + - pattern: | + helmet({hidePoweredBy: ...}) + - id: helmet_header_hsts + message: >- + HSTS header is present. More information: + https://helmetjs.github.io/docs/hsts/ + languages: + - javascript + severity: WARNING + patterns: + - pattern-not: | + $HELMET(..., {hsts: false}, ...) + - pattern-either: + - pattern: | + $APP.use(helmet()) + - pattern: | + helmet.hsts(...) + - pattern: | + hsts({ maxAge: ...}) + - pattern: | + helmet({hsts: ...}) + - id: helmet_header_ienoopen + message: >- + X-Download-Options header is present. More information: + https://helmetjs.github.io/docs/ienoopen/ + languages: + - javascript + severity: WARNING + patterns: + - pattern-not: | + $HELMET(..., {ieNoOpen: false}, ...) + - pattern-either: + - pattern: | + $APP.use(helmet()) + - pattern: | + helmet.ieNoOpen() + - pattern: | + ieNoOpen() + - pattern: | + helmet({ieNoOpen: ...}) + - id: helmet_header_nosniff + message: >- + Content-Type-Options header is present. More information: + https://helmetjs.github.io/docs/dont-sniff-mimetype/ + languages: + - javascript + severity: WARNING + patterns: + - pattern-not: | + $HELMET(..., {noSniff: false}, ...) + - pattern-either: + - pattern: | + $APP.use(helmet()) + - pattern: | + helmet.noSniff() + - pattern: | + noSniff() + - pattern: | + helmet({noSniff: ...}) + - id: helmet_header_referrer_policy + message: >- + Referrer-Policy header is present. More information: + https://helmetjs.github.io/docs/referrer-policy/ + languages: + - javascript + severity: WARNING + patterns: + - pattern-not: | + $HELMET(..., {referrerPolicy: false}, ...) + - pattern-either: + - pattern: | + helmet.referrerPolicy(...) + - pattern: | + referrerPolicy(...) + - pattern: | + helmet({referrerPolicy: ...}) + - id: helmet_header_xss_filter + message: >- + X-XSS-Protection header is present. More information: + https://helmetjs.github.io/docs/xss-filter/ + languages: + - javascript + severity: WARNING + patterns: + - pattern-not: | + $HELMET(..., {xssFilter: false}, ...) + - pattern-either: + - pattern: | + $APP.use(helmet()) + - pattern: | + helmet.xssFilter(...) + - pattern: | + xssFilter(...) + - pattern: | + helmet({xssFilter: ...}) + diff --git a/rules/good/good_ratelimiting.yaml b/rules/good/good_ratelimiting.yaml new file mode 100644 index 0000000..aba6c0f --- /dev/null +++ b/rules/good/good_ratelimiting.yaml @@ -0,0 +1,14 @@ +rules: + - id: rate_limit_control + patterns: + - pattern-either: + - pattern: + require("express-rate-limit"); + - pattern: + require("express-limiter"); + - pattern: + require("@authentication/rate-limit"); + message: 'This application has API rate limiting controls.' + languages: + - javascript + severity: WARNING diff --git a/rules/good/header_helmet_disabled.js b/rules/good/header_helmet_disabled.js new file mode 100644 index 0000000..8ca4c9a --- /dev/null +++ b/rules/good/header_helmet_disabled.js @@ -0,0 +1,10 @@ +// ruleid:helmet_feature_disabled +app.use(helmet({ + frameguard: false, +})) + + +// ruleid:helmet_feature_disabled +app.use(helmet({ + "xssFilter": false +})) \ No newline at end of file diff --git a/rules/headers/header_cookie.js b/rules/headers/header_cookie.js new file mode 100644 index 0000000..8d83ee6 --- /dev/null +++ b/rules/headers/header_cookie.js @@ -0,0 +1,131 @@ +var session = require('express-session') +var express = require('express') +var app = express() + +function test1() { + var expiryDate = new Date(Date.now() + 60 * 60 * 1000) // 1 hour + var opts = { + keys: ['key1', 'key2'], + cookie: { + secure: true, + sameSite: 'strict', + httpOnly: true, + domain: 'example.com', + path: 'foo/bar', + expires: expiryDate + } + } + // ruleid:cookie_session_default + app.use(session(opts)) +} + +function test2() { + // ruleid:cookie_session_no_secure + app.use(session(Object.assign({ + keys: ['key1', 'key2'], + name: 'foo' + }, { + cookie: { + httpOnly: true, + sameSite: true, + domain: 'example.com', + path: 'foo/bar', + expires: new Date(Date.now() + 60 * 60 * 1000) + } + }))) +} + +function test3() { + // ruleid:cookie_session_no_httponly + app.use(session({ + keys: ['key1', 'key2'], + name: 'foo', + cookie: { + httpOnly: false, + secure: true, + sameSite: 'lax', + domain: 'example.com', + path: 'foo/bar', + expires: new Date(Date.now() + 60 * 60 * 1000) + } + })) +} + +function test4() { + var opts = { + keys: ['key1', 'key2'], + name: 'foo', + } + + if (app.get('env') === 'production') { + app.set('trust proxy', 1) // trust first proxy + opts.cookie = { + secure: true, + sameSite: 'strict', + httpOnly: true, + path: 'foo/bar', + expires: new Date(Date.now() + 60 * 60 * 1000) + } + } + // ruleid:cookie_session_no_domain + app.use(session(opts)) +} + +function test5() { + var expiryDate = new Date(Date.now() + 60 * 60 * 1000) // 1 hour + var opts = { + keys: ['key1', 'key2'], + name: 'foo', + cookie: { + secure: true, + sameSite: 'strict', + httpOnly: true + } + } + + if (app.get('env') === 'production') { + app.set('trust proxy', 1) // trust first proxy + opts.cookie.domain = 'example.com' + opts.cookie.expires = expiryDate + } + + // ruleid:cookie_session_no_path + app.use(session(opts)) +} + +function test6() { + var opts = { + keys: ['key1', 'key2'], + name: 'foo', + cookie: { + secure: true, + sameSite: 'strict', + httpOnly: true, + domain: 'example.com', + path: 'foo/bar' + } + } + + // ruleid:cookie_session_no_expires + app.use(session(opts)) +} + +function samestite() { + var expiryDate = new Date(Date.now() + 60 * 60 * 1000) // 1 hour + var opts = { + keys: ['key1', 'key2'], + name: 'foo', + cookie: { + secure: true, + httpOnly: true, + sameSite: 'none', + domain: 'example.com', + path: 'foo/bar', + expires: expiryDate + } + } + + // ruleid:cookie_session_no_samesite + app.use(session(opts)) +} + diff --git a/rules/headers/header_cookie.yaml b/rules/headers/header_cookie.yaml new file mode 100644 index 0000000..f8654e4 --- /dev/null +++ b/rules/headers/header_cookie.yaml @@ -0,0 +1,382 @@ +rules: + - id: cookie_session_default + patterns: + - pattern-either: + - pattern-inside: | + $SESSION = require('cookie-session'); + ... + - pattern-inside: | + $SESSION = require('express-session'); + ... + - pattern: $SESSION(...) + - pattern-not-inside: '$SESSION(<... {name:...} ...>,...)' + - pattern-not-inside: | + $OPTS = <... {name:...} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.name = ...; + ... + $SESSION($OPTS,...); + message: >- + Consider changing the default session cookie name. An attacker can use it + to fingerprint the server and target attacks accordingly. + severity: INFO + languages: + - javascript + metadata: + cwe: 'CWE-522: Insufficiently Protected Credentials' + owasp: 'A2: Broken Authentication' + - id: cookie_session_no_secure + patterns: + - pattern-either: + - pattern-inside: | + $SESSION = require('cookie-session'); + ... + - pattern-inside: | + $SESSION = require('express-session'); + ... + - pattern: $SESSION(...) + - pattern-not-inside: '$SESSION(<... {cookie:{secure:true}} ...>,...)' + - pattern-not-inside: | + $OPTS = <... {cookie:{secure:true}} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE = <... {secure:true} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie = <... {secure:true} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE.secure = true; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie.secure = true; + ... + $SESSION($OPTS,...); + message: >- + Default session middleware settings: `secure` not set. It ensures the + browser only sends the cookie over HTTPS. + severity: WARNING + languages: + - javascript + metadata: + cwe: 'CWE-614: Sensitive Cookie in HTTPS Session Without ''Secure'' Attribute' + owasp: 'A2: Broken Authentication' + - id: cookie_session_no_samesite + patterns: + - pattern-either: + - pattern-inside: | + $SESSION = require('cookie-session'); + ... + - pattern-inside: | + $SESSION = require('express-session'); + ... + - pattern: $SESSION(...) + - pattern-not-inside: '$SESSION(<... {cookie:{sameSite:true}} ...>,...)' + - pattern-not-inside: '$SESSION(<... {cookie:{sameSite:''lax''}} ...>,...)' + - pattern-not-inside: '$SESSION(<... {cookie:{sameSite:''strict''}} ...>,...)' + - pattern-not-inside: | + $OPTS = <... {cookie:{sameSite:true}} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE = <... {sameSite:true} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie = <... {sameSite:true} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE.sameSite = true; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie.sameSite = true; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = <... {cookie:{sameSite:'strict'}} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE = <... {sameSite:'strict'} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie = <... {sameSite:'strict'} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE.sameSite = 'strict'; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie.sameSite = 'strict'; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = <... {cookie:{sameSite:'strict'}} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE = <... {sameSite:'strict'} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie = <... {sameSite:'strict'} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE.sameSite = 'strict'; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie.sameSite = 'strict'; + ... + $SESSION($OPTS,...); + message: >- + Default session middleware settings: `sameSite` attribute is not + configured to strict or lax. These configurations provides protection + against Cross Site Request Forgery attacks. + severity: WARNING + languages: + - javascript + metadata: + cwe: 'CWE-1275: Sensitive Cookie with Improper SameSite Attribute' + owasp: 'A2: Broken Authentication' + - id: cookie_session_no_httponly + patterns: + - pattern-either: + - pattern-inside: | + $SESSION = require('cookie-session'); + ... + - pattern-inside: | + $SESSION = require('express-session'); + ... + - pattern-either: + - pattern-inside: '$SESSION(<... {cookie:{httpOnly:false}} ...>,...)' + - pattern-inside: | + $OPTS = <... {cookie:{httpOnly:false}} ...>; + ... + $SESSION($OPTS,...); + - pattern-inside: | + $OPTS = ...; + ... + $COOKIE = <... {httpOnly:false} ...>; + ... + $SESSION($OPTS,...); + - pattern-inside: | + $OPTS = ...; + ... + $OPTS.cookie = <... {httpOnly:false} ...>; + ... + $SESSION($OPTS,...); + - pattern-inside: | + $OPTS = ...; + ... + $COOKIE.httpOnly = false; + ... + $SESSION($OPTS,...); + - pattern-inside: | + $OPTS = ...; + ... + $OPTS.cookie.httpOnly = false; + ... + $SESSION($OPTS,...); + message: >- + Session middleware settings: `httpOnly` is explicitly set to false. + It ensures that sensitive cookies cannot be accessed by client side + JavaScript and helps to protect against cross-site scripting attacks. + severity: WARNING + languages: + - javascript + metadata: + cwe: 'CWE-1004: Sensitive Cookie Without ''HttpOnly'' Flag' + owasp: 'A2: Broken Authentication' + - id: cookie_session_no_domain + patterns: + - pattern-either: + - pattern-inside: | + $SESSION = require('cookie-session'); + ... + - pattern-inside: | + $SESSION = require('express-session'); + ... + - pattern: $SESSION(...) + - pattern-not-inside: '$SESSION(<... {cookie:{domain:...}} ...>,...)' + - pattern-not-inside: | + $OPTS = <... {cookie:{domain:...}} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE = <... {domain:...} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie = <... {domain:...} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE.domain = ...; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie.domain = ...; + ... + $SESSION($OPTS,...); + message: >- + Default session middleware settings: `domain` not set. It indicates the + domain of the cookie; use it to compare against the domain of the server + in which the URL is being requested. If they match, then check the path + attribute next. + severity: INFO + languages: + - javascript + metadata: + cwe: 'CWE-522: Insufficiently Protected Credentials' + owasp: 'A2: Broken Authentication' + - id: cookie_session_no_path + patterns: + - pattern-either: + - pattern-inside: | + $SESSION = require('cookie-session'); + ... + - pattern-inside: | + $SESSION = require('express-session'); + ... + - pattern: $SESSION(...) + - pattern-not-inside: '$SESSION(<... {cookie:{path:...}} ...>,...)' + - pattern-not-inside: | + $OPTS = <... {cookie:{path:...}} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE = <... {path:...} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie = <... {path:...} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE.path = ...; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie.path = ...; + ... + $SESSION($OPTS,...); + message: >- + Default session middleware settings: `path` not set. It indicates the path + of the cookie; use it to compare against the request path. If this and + domain match, then send the cookie in the request. + severity: INFO + languages: + - javascript + metadata: + cwe: 'CWE-522: Insufficiently Protected Credentials' + owasp: 'A2: Broken Authentication' + - id: cookie_session_no_expires + patterns: + - pattern-either: + - pattern-inside: | + $SESSION = require('cookie-session'); + ... + - pattern-inside: | + $SESSION = require('express-session'); + ... + - pattern: $SESSION(...) + - pattern-not-inside: '$SESSION(<... {cookie:{expires:...}} ...>,...)' + - pattern-not-inside: | + $OPTS = <... {cookie:{expires:...}} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE = <... {expires:...} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $OPTS.cookie = <... {expires:...} ...>; + ... + $SESSION($OPTS,...); + - pattern-not-inside: | + $OPTS = ...; + ... + $COOKIE.expires = ...; + ... + $SESSION($OPTS,...); + - pattern-not-inside: |- + $OPTS = ...; + ... + $OPTS.cookie.expires = ...; + ... + $SESSION($OPTS,...); + message: >- + Default session middleware settings: `expires` not set. Use it to set + expiration date for persistent cookies. + severity: INFO + languages: + - javascript + metadata: + cwe: 'CWE-613: Insufficient Session Expiration' + owasp: 'A2: Broken Authentication' \ No newline at end of file diff --git a/rules/headers/header_cors_star.js b/rules/headers/header_cors_star.js new file mode 100644 index 0000000..b4d81bd --- /dev/null +++ b/rules/headers/header_cors_star.js @@ -0,0 +1,41 @@ +const express = require('express'); + +const app = express(); + +// ruleid:generic_cors +app.options('*', cors()) +app.get('/', function (req, res) { + + res.set(ffff) +}); + +app.get('/', function (req, res) { + var y = 1; + // ruleid:express_cors + var x = '*'; + //sgrep bug - https://github.com/returntocorp/sgrep/issues/512 + // ruleid:express_cors + res.writeHead(200, { 'Access-Control-Allow-Origin': '*' }); + // ruleid:express_cors + res.set('access-control-allow-origin', '*'); + //do not match - sgrep bug -rewrite-rule + res.set('Access-Control-Allow-Origin', 'google.com'); + // ruleid:express_cors + res.set('Access-Control-Allow-Origin', '*'); + // ruleid:express_cors + res.set({ + 'Content-Length': 123, + 'access-control-allow-origin': '*', + 'ETag': '12345' + }) + // ruleid:express_cors + res.writeHead(200, { 'Access-Control-Allow-Origin': '*' }) + + res.set('access-control-allow-origin', x); + + // do not detect - sgrep bug + res.set('access-control-allow-origin', 'xyz.com'); + //do not detect - sgrep bug + res.set('access-control-allow-origin', null); + +}); \ No newline at end of file diff --git a/rules/headers/header_cors_star.yaml b/rules/headers/header_cors_star.yaml new file mode 100644 index 0000000..6c9eb97 --- /dev/null +++ b/rules/headers/header_cors_star.yaml @@ -0,0 +1,70 @@ +rules: + - id: generic_cors + patterns: + - pattern: | + $APP.options('*', cors(...)) + message: >- + Access-Control-Allow-Origin response header is set to "*". This will + disable CORS Same Origin Policy restrictions. + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-346: Origin Validation Error' + - id: express_cors + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $APP.options('*', cors(...)) + - pattern: > + $RES.set("=~/access-control-allow-origin/i", + '*', ...) + - pattern: > + $RES.set(..., { + "=~/access-control-allow-origin/i" : + '*' }, ...) + - pattern: > + $RES.header("=~/access-control-allow-origin/i", + '*', ...) + - pattern: > + $RES.writeHead(..., + {"=~/access-control-allow-origin/i": + '*' }, ...); + - pattern: > + $VAL = '*'; + ... + $RES.set("=~/access-control-allow-origin/i", + $VAL, ...); + - pattern: > + $VAL = '*'; + ... + $RES.set(..., { + "=~/access-control-allow-origin/i" : + $VAL }, ...); + - pattern: > + $VAL = '*'; + ... + $RES.header("=~/access-control-allow-origin/i", + $VAL, ...); + - pattern: > + $VAL = '*'; + ... + $RES.writeHead(..., + {"=~/access-control-allow-origin/i": + $VAL }, ...); + message: >- + Access-Control-Allow-Origin response header is set to "*". This will + disable CORS Same Origin Policy restrictions. + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-346: Origin Validation Error' \ No newline at end of file diff --git a/rules/headers/header_helmet_disabled.js b/rules/headers/header_helmet_disabled.js new file mode 100644 index 0000000..8ca4c9a --- /dev/null +++ b/rules/headers/header_helmet_disabled.js @@ -0,0 +1,10 @@ +// ruleid:helmet_feature_disabled +app.use(helmet({ + frameguard: false, +})) + + +// ruleid:helmet_feature_disabled +app.use(helmet({ + "xssFilter": false +})) \ No newline at end of file diff --git a/rules/headers/header_helmet_disabled.yaml b/rules/headers/header_helmet_disabled.yaml new file mode 100644 index 0000000..0f2b67a --- /dev/null +++ b/rules/headers/header_helmet_disabled.yaml @@ -0,0 +1,36 @@ +rules: + - id: helmet_feature_disabled + patterns: + - pattern-either: + - pattern: | + $HELMET(..., {frameguard: false}, ...) + - pattern: | + $HELMET(..., {contentSecurityPolicy: false}, ...) + - pattern: | + $HELMET(..., {permittedCrossDomainPolicies: false}, ...) + - pattern: | + $HELMET(..., {dnsPrefetchControl: false}, ...) + - pattern: | + $HELMET(..., {expectCt: false}, ...) + - pattern: | + $HELMET(..., {featurePolicy: false}, ...) + - pattern: | + $HELMET(..., {hsts: false}, ...) + - pattern: | + $HELMET(..., {ieNoOpen: false}, ...) + - pattern: | + $HELMET(..., {noSniff: false}, ...) + - pattern: | + $HELMET(..., {hidePoweredBy: false}, ...) + - pattern: | + $HELMET(..., {referrerPolicy: false}, ...) + - pattern: | + $HELMET(..., {xssFilter: false}, ...) + message: >- + One or more Security Response header is explicitly disabled in Helmet. + languages: + - javascript + severity: WARNING + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-693: Protection Mechanism Failure' \ No newline at end of file diff --git a/rules/headers/header_injection.js b/rules/headers/header_injection.js new file mode 100644 index 0000000..401398e --- /dev/null +++ b/rules/headers/header_injection.js @@ -0,0 +1,63 @@ +var server = http.createServer(function (req, res) { + var bla = 'dsdsd'; + switch (testIndex++) { + case 0: + // ruleid:generic_header_injection + res.writeHead(200, { test: 'foo \r\ninvalid: bar' + req.foo }); + break; + case 1: + // ruleid:generic_header_injection + res.writeHead(200, { test: req.foo + 'foo \ninvalid: bar' }); + break; + case 2: + // ruleid:generic_header_injection + res.writeHead(200, { test: 'foo \rinvalid: bar' + req.foo + 'asdadasd', foo: bar }); + break; + case 3: + // ruleid:generic_header_injection + res.writeHead(200, { test: bla + 'foo \n\n\ninvalid: bar' + req.foo }); + break; + case 5: + // ruleid:generic_header_injection + res.writeHead(200, { test: bla + 'foo \n\n\ninvalid: bar' + req.foo('asd') }); + break; + case 4: + // ruleid:generic_header_injection + res.writeHead(200, { test: req.foo }); + server.close(); + break; + default: + assert(false); + } + res.end('Hi mars!'); +}); +server.listen(common.PORT); + +var express = require('express'); +var app = express(); +app.get('/', function (req, res) { + // ruleid:generic_header_injection + res.writeHead(200, { test: 'foo \r\ninvalid: bar' + req.foo }); + + // ruleid:generic_header_injection + res.set('Content-Type', req.query.foo); + // ruleid:generic_header_injection + res.set('foo', 'asdad' + req.query.foo); + // ruleid:generic_header_injection + res.set(req.query.foo, 'asdadad'); + // ruleid:generic_header_injection + res.set('asda' + req.query.foo, 'asdadad'); + // ruleid:generic_header_injection + res.set('asda' + req.query["foo"], 'asdadad'); + // ruleid:generic_header_injection + res.set('asda' + req.query("foo"), 'asdadad'); + // ruleid:generic_header_injection + res.set({ + 'Content-Type': 'text/plain', + 'Content-Length': req.query.foo, + 'ETag': '12345' + }) + //do not detect + res.writeHead(200, { tast: ddd }) + res.set(ffff) +}); diff --git a/rules/headers/header_injection.yaml b/rules/headers/header_injection.yaml new file mode 100644 index 0000000..0267cf4 --- /dev/null +++ b/rules/headers/header_injection.yaml @@ -0,0 +1,55 @@ +rules: + - id: generic_header_injection + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $INP = $REQ.$QUERY; + ... + $RES.set(..., <... $INP ...>, ...); + - pattern: | + $INP = $REQ.$QUERY.$VAR; + ... + $RES.set(..., <... $INP ...>, ...); + - pattern: | + $INP = $REQ.$VAR; + ... + $RES.set(..., { $X: <... $INP ...>}, ...); + - pattern: | + $INP = $REQ.$QUERY.$FOO; + ... + $RES.set(..., { $X: <... $INP ...>}, ...); + - pattern: | + $INP = $REQ.$VAR; + ... + $RES.writeHead(..., { $X: <... $INP ...> }, ...); + - pattern: | + $INP = $REQ.$QUERY.$FOO; + ... + $RES.writeHead(..., { $X: <... $INP ...> }, ...); + - pattern: | + $RES.set(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + $RES.set(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + $RES.set(..., { $X: <... $REQ.$VAR ...>}, ...) + - pattern: | + $RES.set(..., { $X: <... $REQ.$QUERY.$FOO ...>}, ...); + - pattern: | + $RES.writeHead(..., { $X: <... $REQ.$VAR ...> }, ...); + - pattern: | + $RES.writeHead(..., { $X: <... $REQ.$QUERY.$FOO ...> }, ...); + message: >- + Untrusted user input in response header will result in HTTP Header + Injection or Response Splitting Attacks. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-644: Improper Neutralization of HTTP Headers for Scripting Syntax' \ No newline at end of file diff --git a/rules/headers/header_xss_protection.js b/rules/headers/header_xss_protection.js new file mode 100644 index 0000000..eb187f9 --- /dev/null +++ b/rules/headers/header_xss_protection.js @@ -0,0 +1,52 @@ +const express = require('express'); +const lusca = require('lusca'); + +const app = express(); + +// ruleid:header_xss_lusca +app.use(lusca({ + csrf: true, + csp: { policy: "referrer no-referrer" }, + xframe: 'SAMEORIGIN', + p3p: 'ABCDEF', + hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }, + xssProtection: false, + nosniff: true, + referrerPolicy: 'same-origin' +})); + +app.use(lusca.csrf()); +app.use(lusca.csp({ policy: [{ "img-src": "'self' http:" }, "block-all-mixed-content"], reportOnly: false })); +app.use(lusca.xframe('SAMEORIGIN')); +app.use(lusca.p3p('ABCDEF')); +app.use(lusca.hsts({ maxAge: 31536000 })); +// ruleid:header_xss_lusca +app.use(lusca.xssProtection(false)); +app.use(lusca.nosniff()); +app.use(lusca.referrerPolicy('same-origin')); + +app.get('/', function (req, res) { + // ruleid:header_xss_generic + var x = 0; + // ruleid:header_xss_generic + res.writeHead(200, { 'x-xss-protection': 0 }); + + // ruleid:header_xss_generic + res.set('x-xss-protection', 0); + //do not match + res.set('x-xss-protection', 1); + // ruleid:header_xss_generic + res.set('X-XSS-Protection', 0); + // ruleid:header_xss_generic + res.set({ + 'Content-Length': req.query.foo, + 'x-xss-protection': 0, + 'ETag': '12345' + }) + // ruleid:header_xss_generic + res.writeHead(200, { 'x-xss-protection': 0 }) + res.set('X-XSS-Protection', x); + + // do not detect + res.set(ffff) +}); diff --git a/rules/headers/header_xss_protection.yaml b/rules/headers/header_xss_protection.yaml new file mode 100644 index 0000000..529e965 --- /dev/null +++ b/rules/headers/header_xss_protection.yaml @@ -0,0 +1,68 @@ +rules: + - id: header_xss_lusca + patterns: + - pattern-inside: | + $X = require('lusca'); + ... + - pattern-not: | + $X.use(helmet()) + - pattern-either: + - pattern: | + $X.xssProtection(false) + - pattern: | + $X({ xssProtection: false}) + message: >- + X-XSS-Protection header is set to 0. This will disable the browser's XSS + Filter. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-693: Protection Mechanism Failure' + - id: header_xss_generic + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $RES.header("=~/x-xss-protection/i", 0, ...) + - pattern: | + $RES.set("=~/x-xss-protection/i", 0, ...) + - pattern: > + $RES.set(..., { "=~/x-xss-protection/i" : 0 }, + ...) + - pattern: > + $RES.writeHead(..., {"=~/x-xss-protection/i": 0 + }, ...); + - pattern: | + $VAL = 0; + ... + $RES.header("=~/x-xss-protection/i", $VAL, ...); + - pattern: | + $VAL = 0; + ... + $RES.set("=~/x-xss-protection/i", $VAL, ...); + - pattern: > + $VAL = 0; + ... + $RES.set(..., { "=~/x-xss-protection/i" : $VAL + }, ...); + - pattern: > + $VAL = 0; + ... + $RES.writeHead(..., {"=~/x-xss-protection/i": + $VAL }, ...); + message: >- + X-XSS-Protection header is set to 0. This will disable the browser's XSS + Filter. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: 'CWE-693: Protection Mechanism Failure' \ No newline at end of file diff --git a/rules/headers/host_header_injection.js b/rules/headers/host_header_injection.js new file mode 100644 index 0000000..52192c8 --- /dev/null +++ b/rules/headers/host_header_injection.js @@ -0,0 +1,54 @@ +// https://www.acunetix.com/blog/articles/automated-detection-of-host-header-attacks/ +app.get('/', function (req, res) { + + //semgrep string lateral support is pending + var foo = { + text: `reset url: https://${req.host}/password_reset/${token}` + }; + + //do not match + var x = 'https://' + foo + // do not match + var x = "https://" + req.foo + "/reset" + foo; + // do not match + var x = "https://" + z + "/reset"; + + + + // ruleid:host_header_injection + var url = 'http://' + req.host; + // ruleid:host_header_injection + var reset = 'https://' + req.host + '/password_reset'; + // ruleid:host_header_injection + var pass = "https://" + req.host + "/reset"; + + // ruleid:host_header_injection + var z = req.host; + var pass = "https://" + z + "/reset"; + + // ruleid:host_header_injection + var reset_url = "Reset password: Reset"; + // ruleid:host_header_injection + var foo = { + text: 'password: https://' + req.host + '/token/', + token: 'f2131ASDSADASoo', + }; + + // ruleid:host_header_injection + var foo = { + text: 'reset password: https://' + req['host'] + '/token/', + token: 'f2131ASDSADASoo', + }; + + // ruleid:host_header_injection + let x = "https://" + req['host'] + "/reset" + foo; + // ruleid:host_header_injection + x = "https://" + req("host") + "/reset" + foo + 'barr' + foo2; + + // ruleid:host_header_injection + var foo = { + text: 'reset password: https://' + req.host + '/resettoken/' + foo, + token: 'f2131ASDSADASoo', + }; + +}); diff --git a/rules/headers/host_header_injection.yaml b/rules/headers/host_header_injection.yaml new file mode 100644 index 0000000..3230f70 --- /dev/null +++ b/rules/headers/host_header_injection.yaml @@ -0,0 +1,55 @@ +rules: + - id: host_header_injection + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $X = <... "=~/.*http[s]*:///" + $REQ.host ...>; + - pattern: | + $X = <... "=~/.*http[s]*:///" + $REQ["host"] ...>; + - pattern: | + $X = <... "=~/.*http[s]*:///" + $REQ("host") ...>; + - pattern: | + $X = { $Y: <... "=~/.*http[s]*:///" + $REQ.host ...>}; + - pattern: | + $X = { $Y: <... "=~/.*http[s]*:///" + $REQ["host"] ...>}; + - pattern: | + $X = { $Y: <... "=~/.*http[s]*:///" + $REQ("host") ...>}; + - pattern: | + $Z = $REQ.host; + ... + $X = <... "=~/.*http[s]*:///" + $Z ...>; + - pattern: | + $Z = $REQ["host"]; + ... + $X = <... "=~/.*http[s]*:///" + $Z ...>; + - pattern: | + $Z = $REQ("host"); + ... + $X = <... "=~/.*http[s]*:///" + $Z ...>; + - pattern: | + $Z = $REQ.host; + ... + $X = { $Y: <... "=~/.*http[s]*:///" + $REQ.host ...>}; + - pattern: | + $Z = $REQ["host"]; + ... + $X = { $Y: <... "=~/.*http[s]*:///" + $Z ...>}; + - pattern: | + $Z = $REQ("host"); + ... + $X = { $Y: <... "=~/.*http[s]*:///" + $REQ("host") ...>}; + message: >- + Using untrusted Host header for generating dynamic URLs can result in web + cache and or password reset poisoning. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-20: Improper Input Validation' \ No newline at end of file diff --git a/rules/jwt/hardcoded_jwt.js b/rules/jwt/hardcoded_jwt.js new file mode 100644 index 0000000..2a987c9 --- /dev/null +++ b/rules/jwt/hardcoded_jwt.js @@ -0,0 +1,119 @@ +// ruleid:hardcoded_jwt_secret +const jsonwt = require('jsonwebtoken') +const jose = require('jose') +const { JWK, JWT } = jose +const config = require('./config') + +const payload = { foo: 'bar' } +const secret = 'shhhhh' + +const secret2 = config.secret +const secret3 = process.env.SECRET || 'fallback-secret' + +//jsonwebtoken +//true +const token1 = jsonwt.sign(payload, secret) +//true +const token2 = jsonwt.sign(payload, 'some-secret') + +//?? +const token5 = jsonwt.sign(payload, secret3) + +//jose +//true +const token6 = JWT.sign(payload, JWK.asKey(secret)) +//true +const token7 = JWT.sign(payload, JWK.asKey('raz-dva-tri')) +//true +const token8 = JWT.sign(payload, secret) +//true +const token9 = JWT.sign(payload, 'secret-again') + + + +// ruleid:hardcoded_jwt_secret +const $jwt = require('jsonwebtoken'); + +const cert = 'hardcoded-secret'; + +module.exports = (app) => { + app.post('/api/login', (req, res) => { + app.login(req.body.username, req.body.password).then((out) => { + out.token = $jwt.sign(out, cert, { expiresIn: '1d' }); + res.send(out); + }, (err) => { + console.error(err); + res.status(400).send(err); + }); + }); +}; + +// ruleid:hardcoded_jwt_secret +const jwt = require('jsonwebtoken') + +const jwtSign = (payload = { id: 1 }) => + jwt.sign(payload, 'hardcoded-secret') + +const jwtVerify = req => () => new Promise((resolve, reject) => { + const token = req.headers['x-access-token'] + if (!token) { + resolve(false) + } + jwt.verify(token, 'hardcoded-secret', (err, decoded) => { + if (err) { + resolve(false) + } + resolve(decoded) + }) +}) + +export default { jwtSign, jwtVerify } + (() => { + + 'use strict'; + + // ruleid:hardcoded_jwt_secret + let User = require('./user'), + jwt = require('jsonwebtoken'); + + const express = require('express'); + let router = express.Router(); + + router.post('/signup', (req, res) => { + let user = new User({ + name: req.body.name, + password: req.body.password + }); + var token = jwt.sign(user, "hardcoded-secret", { expiresIn: 60 * 60 * 10 }); + res.send({ success: true, token: token }); + }); + + module.exports = router; + })(); + +'use strict'; +const config = require('./app.config'); +const privateMethods = { + initialize(USER) { + // ruleid:hardcoded_jwt_secret + const router = require('express').Router(), + jwt = require('jsonwebtoken'); + if (config) { + router.route('/register').post((req, res) => { + USER.findOne({}).exec((error, user) => { + if (error) + return res.status(400).send({ error: error }); + user.save((error, user) => { + if (error) { + return res.status(400).send({ error: error }); + } else { + const token = jwt.sign({ id: user._id }, 'hardcoded-secret'); + return res.status(201).json({ token: token }); + } + }); + }); + }); + } + } +}; +module.exports = privateMethods; \ No newline at end of file diff --git a/rules/jwt/hardcoded_jwt_express.js b/rules/jwt/hardcoded_jwt_express.js new file mode 100644 index 0000000..a3bdf06 --- /dev/null +++ b/rules/jwt/hardcoded_jwt_express.js @@ -0,0 +1,24 @@ +var jwt = require('express-jwt'); + +// ruleid: jwt_express_hardcoded +app.get('/protected', jwt({ secret: 'shhhhhhared-secret' }), function (req, res) { + if (!req.user.admin) return res.sendStatus(401); + res.sendStatus(200); +}); + +// ruleid: jwt_express_hardcoded +let hardcodedSecret = 'shhhhhhared-secret' + +app.get('/protected2', jwt({ secret: hardcodedSecret }), function (req, res) { + if (!req.user.admin) return res.sendStatus(401); + res.sendStatus(200); +}); + +// ruleid: jwt_express_hardcoded +let secret = "hardcode" +const opts = Object.assign({ issuer: 'http://issuer' }, { secret }) + +app.get('/protected3', jwt(opts), function (req, res) { + if (!req.user.admin) return res.sendStatus(401); + res.sendStatus(200); +}); \ No newline at end of file diff --git a/rules/jwt/jwt_exposed_credentials.js b/rules/jwt/jwt_exposed_credentials.js new file mode 100644 index 0000000..2419db1 --- /dev/null +++ b/rules/jwt/jwt_exposed_credentials.js @@ -0,0 +1,114 @@ +//jose +function example30() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + const token1 = JWT.sign({ password: 123 }, 'secret', { some: 'params' }) +} + +function example31() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + const payload = { one: 1, two: 2, password: 123 } + const token1 = JWT.sign(payload, 'secret', { some: 'params' }) +} + +function example32() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + let payload; + payload = { one: 1, two: 2, password: 123 } + const token1 = JWT.sign(payload, 'secret', { some: 'params' }) +} + +function example33() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + const payload = {} + payload.password = 123 + const token1 = JWT.sign(payload, 'secret', { some: 'params' }) +} + +function example34() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + const payload = Object.assign({ password: 'bar' }, { bar: 123 }, { one: 1, two: 2 }) + const token1 = JWT.sign(payload, 'secret', { some: 'params' }) +} + +function example35() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + let payload; + payload = Object.assign({ password: 'bar' }, { bar: 123 }, { one: 1, two: 2 }) + const token1 = JWT.sign(payload, 'secret', { some: 'params' }) +} + +function example36() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + const token1 = JWT.sign(Object.assign({ password: 'bar' }, { bar: 123 }, { one: 1, two: 2 }), 'secret', { some: 'params' }) +} + +function example37() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + const token1 = JWT.sign({ user: { password: 123 } }, 'secret', { some: 'params' }) +} + +function example38() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + const payload = { one: 1, two: 2, user: { password: 123 } } + const token1 = JWT.sign(payload, 'secret', { some: 'params' }) +} + +function example39() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + let payload; + payload = { one: 1, two: 2, user: { password: 123 } } + const token1 = JWT.sign(payload, 'secret', { some: 'params' }) +} + +function example40() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + const payload = { user: {} } + payload.user.password = 123 + const token1 = JWT.sign(payload, 'secret', { some: 'params' }) +} + +function example41() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + const payload = Object.assign({ user: { password: 123 } }, { bar: 123 }, { one: 1, two: 2 }) + const token1 = JWT.sign(payload, 'secret', { some: 'params' }) +} + +function example42() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + let payload; + payload = Object.assign({ user: { password: 123 } }, { bar: 123 }, { one: 1, two: 2 }) + const token1 = JWT.sign(payload, 'secret', { some: 'params' }) +} + +function example43() { + // ruleid: jwt_exposed_credentials + const jose = require('jose') + const { JWK, JWT } = jose + const token1 = JWT.sign(Object.assign({ user: { password: 123 } }, { bar: 123 }, { one: 1, two: 2 }), 'secret', { some: 'params' }) +} \ No newline at end of file diff --git a/rules/jwt/jwt_exposed_credentials.yaml b/rules/jwt/jwt_exposed_credentials.yaml new file mode 100644 index 0000000..944c631 --- /dev/null +++ b/rules/jwt/jwt_exposed_credentials.yaml @@ -0,0 +1,221 @@ +rules: + - id: jwt_exposed_credentials + patterns: + - pattern-either: + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $T = JWT.sign({password:...},...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $T = JWT.sign({password:...},...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + var $P = {password:...}; + ... + var $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + var $P = {password:...}; + ... + $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $P = {password:...}; + ... + var $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $P = {password:...}; + ... + $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $P.password = ...; + ... + var $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $P.password = ...; + ... + $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + var $P = Object.assign(...,{password:...},...); + ... + var $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + var $P = Object.assign(...,{password:...},...); + ... + $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $P = Object.assign(...,{password:...},...); + ... + var $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $P = Object.assign(...,{password:...},...); + ... + $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + var $T = JWT.sign(Object.assign(...,{password:...},...),...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $T = JWT.sign(Object.assign(...,{password:...},...),...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + var $T = JWT.sign({$U:{password:...}},...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $T = JWT.sign({$U:{password:...}},...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + var $P = {$U:{password:...}}; + ... + var $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + var $P = {$U:{password:...}}; + ... + $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $P = {$U:{password:...}}; + ... + var $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $P = {$U:{password:...}}; + ... + $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $P.$U.password = ...; + ... + var $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $P.$U.password = ...; + ... + $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + var $P = Object.assign(...,{$U:{password:...}},...); + ... + var $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + var $P = Object.assign(...,{$U:{password:...}},...); + ... + $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $P = Object.assign(...,{$U:{password:...}},...); + ... + var $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $P = Object.assign(...,{$U:{password:...}},...); + ... + $T = JWT.sign($P,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + var $T = JWT.sign(Object.assign(...,{$U:{password:...}},...),...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWT } = $JOSE; + ... + $T = JWT.sign(Object.assign(...,{$U:{password:...}},...),...); + severity: ERROR + languages: + - javascript + metadata: + cwe: 'CWE-522: Insufficiently Protected Credentials' + owasp: 'A2: Broken Authentication' + message: >- + Password is exposed through JWT token payload. This is not encrypted and + the password could be compromised. Do not store passwords in JWT tokens. diff --git a/rules/jwt/jwt_exposed_data.js b/rules/jwt/jwt_exposed_data.js new file mode 100644 index 0000000..38c25b1 --- /dev/null +++ b/rules/jwt/jwt_exposed_data.js @@ -0,0 +1,8 @@ +const config = require('./config') +const { JWT } = require('jose') + +function example(user) { + // ruleid: jwt_exposed_data + const token = JWT.sign(user, secret) + return token; +} \ No newline at end of file diff --git a/rules/jwt/jwt_exposed_data.yaml b/rules/jwt/jwt_exposed_data.yaml new file mode 100644 index 0000000..7dd6f07 --- /dev/null +++ b/rules/jwt/jwt_exposed_data.yaml @@ -0,0 +1,27 @@ +rules: + - id: jwt_exposed_data + patterns: + - pattern-inside: | + ... + require('jose'); + ... + - pattern-either: + - patterns: + - pattern-inside: 'function (...,$INPUT,...) {...}' + - pattern-either: + - pattern: '$JOSE.JWT.sign($INPUT,...)' + - pattern: '$JWT.sign($INPUT,...)' + - patterns: + - pattern-inside: 'function $F(...,$INPUT,...) {...}' + - pattern-either: + - pattern: '$JOSE.JWT.sign($INPUT,...)' + - pattern: '$JWT.sign($INPUT,...)' + message: >- + The object is passed strictly to jose.JWT.sign(...). Make sure + that sensitive information is not exposed through JWT token payload. + severity: WARNING + metadata: + owasp: 'A3:2017-Sensitive Data Exposure' + cwe: 'CWE-522: Insufficiently Protected Credentials' + languages: + - javascript diff --git a/rules/jwt/jwt_express_hardcoded.yaml b/rules/jwt/jwt_express_hardcoded.yaml new file mode 100644 index 0000000..972a66b --- /dev/null +++ b/rules/jwt/jwt_express_hardcoded.yaml @@ -0,0 +1,32 @@ +rules: + - id: jwt_express_hardcoded + patterns: + - pattern-inside: | + $JWT = require('express-jwt'); + ... + - pattern-either: + - pattern: | + $JWT(<... {secret: "..."} ...>,...); + - pattern: | + $SECRET = "..."; + ... + $JWT(<... {secret: $SECRET} ...>,...); + - pattern: | + $OPTS = <... {secret: "..."} ...>; + ... + $JWT($OPTS,...); + - pattern: |- + $SECRET = "..."; + ... + $OPTS = <... {secret: $SECRET} ...>; + ... + $JWT($OPTS,...); + message: >- + Hardcoded JWT secret or private key was found. Store it properly in + an environment variable. + severity: ERROR + languages: + - javascript + metadata: + cwe: 'CWE-522: Insufficiently Protected Credentials' + owasp: 'A2: Broken Authentication' diff --git a/rules/jwt/jwt_hardcoded.yaml b/rules/jwt/jwt_hardcoded.yaml new file mode 100644 index 0000000..4324fc3 --- /dev/null +++ b/rules/jwt/jwt_hardcoded.yaml @@ -0,0 +1,74 @@ +# Rule is from https://github.com/returntocorp/semgrep-rules/blob/develop/javascript/jwt-hardcode/jwt-hardcode.yaml +# module imports used as described in https://github.com/returntocorp/semgrep/issues/285 +rules: + - id: hardcoded_jwt_secret + patterns: + - pattern-either: + - pattern: | + $JWT = require("jsonwebtoken"); + ... + $JWT.sign($P, "...", ...); + - pattern: | + $JWT = require("jsonwebtoken"); + ... + $JWT.verify($P, "...", ...); + - pattern: | + $JWT = require("jsonwebtoken"); + ... + $SECRET = "..."; + ... + $JWT.sign($P, $SECRET, ...); + - pattern: | + $JWT = require("jsonwebtoken"); + ... + $SECRET = "..."; + ... + $JWT.verify($P, $SECRET, ...); + - pattern: | + $JOSE = require("jose"); + ... + $JOSE.JWT.sign($P, "...", ...); + - pattern: | + $JOSE = require("jose"); + ... + $JOSE.JWT.verify($P, "...", ...); + - pattern: | + $JOSE = require("jose"); + ... + $JOSE.JWT.sign($P, $JOSE.JWK.asKey("..."), ...); + - pattern: | + $JOSE = require("jose"); + ... + $JOSE.JWT.verify($P, $JOSE.JWK.asKey("..."), ...); + - pattern: | + $JOSE = require("jose"); + ... + $SECRET = "..."; + ... + $JOSE.JWT.sign($P, $SECRET, ...); + - pattern: | + $JOSE = require("jose"); + ... + $SECRET = "..."; + ... + $JOSE.JWT.verify($P, $SECRET, ...); + - pattern: | + $JOSE = require("jose"); + ... + $SECRET = "..."; + ... + $JOSE.JWT.sign($P, $JOSE.JWK.asKey($SECRET), ...); + - pattern: | + $JOSE = require("jose"); + ... + $SECRET = "..."; + ... + $JOSE.JWT.verify($P, $JOSE.JWK.asKey($SECRET), ...); + message: Hardcoded JWT secret was found. Store it properly in + an environment variable. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A3: Sensitive Data Exposure' + cwe: 'CWE-798: Use of Hard-coded Credentials' diff --git a/rules/jwt/jwt_none_algorithm.js b/rules/jwt/jwt_none_algorithm.js new file mode 100644 index 0000000..c304f09 --- /dev/null +++ b/rules/jwt/jwt_none_algorithm.js @@ -0,0 +1,14 @@ +// ruleid:node_jwt_none_algorithm +const jose = require("jose"); +const { JWK, JWT } = jose; +const token = JWT.verify('token-here', JWK.None); + +function verifyJwt() { + // ruleid:node_jwt_none_algorithm + let jwt = require("jsonwebtoken"); + let secret = 'some-secret'; + jwt.verify('token-here', secret, { algorithms: ['RS256', 'none'] }, function (err, payload) { + console.log(payload); + }); +} + diff --git a/rules/jwt/jwt_none_algorithm.yaml b/rules/jwt/jwt_none_algorithm.yaml new file mode 100644 index 0000000..4b0b7db --- /dev/null +++ b/rules/jwt/jwt_none_algorithm.yaml @@ -0,0 +1,45 @@ +# Rule from : https://github.com/returntocorp/semgrep-rules/blob/develop/javascript/jwt-none-alg/jwt-none-alg.yaml +# https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/#Meet-the--None--Algorithm +rules: + - id: node_jwt_none_algorithm + patterns: + - pattern-either: + - pattern: | + $JWT = require("jsonwebtoken"); + ... + $T = $JWT.verify($P, $X, {algorithms:[...,'none',...]},...); + - pattern: | + $JWT = require("jsonwebtoken"); + ... + $T = $JWT.verify($P, $X, {algorithms:[...,'none',...]},...); + - pattern: | + $JWT = require("jsonwebtoken"); + ... + $JWT.verify($P, $X, {algorithms:[...,'none',...]},...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWK, JWT } = $JOSE; + ... + $T = JWT.verify($P, JWK.None,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWK, JWT } = $JOSE; + ... + $T = JWT.verify($P, JWK.None,...); + - pattern: | + $JOSE = require("jose"); + ... + var { JWK, JWT } = $JOSE; + ... + JWT.verify($P, JWK.None,...); + message: >- + Algorithm is set to none for JWT token. This can nullify the integrity of + JWT signature. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A9: Using Components with Known Vulnerabilities' + cwe: 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' diff --git a/rules/jwt/jwt_not_revoked.js b/rules/jwt/jwt_not_revoked.js new file mode 100644 index 0000000..113f20e --- /dev/null +++ b/rules/jwt/jwt_not_revoked.js @@ -0,0 +1,16 @@ +var jwt = require('express-jwt'); +var blacklist = require('express-jwt-blacklist'); + +// ruleid: jwt_not_revoked +app.get('/ok-protected', jwt({ secret: process.env.SECRET }), function (req, res) { + if (!req.user.admin) return res.sendStatus(401); + res.sendStatus(200); +}); + +let configSecret = config.get('secret') +const opts = Object.assign({ issuer: 'http://issuer' }, { secret: configSecret }) +// ruleid: jwt_not_revoked +app.get('/ok-protected', jwt(opts), function (req, res) { + if (!req.user.admin) return res.sendStatus(401); + res.sendStatus(200); +}); \ No newline at end of file diff --git a/rules/jwt/jwt_not_revoked.yaml b/rules/jwt/jwt_not_revoked.yaml new file mode 100644 index 0000000..f068883 --- /dev/null +++ b/rules/jwt/jwt_not_revoked.yaml @@ -0,0 +1,21 @@ +rules: + - id: jwt_not_revoked + patterns: + - pattern-inside: | + $JWT = require('express-jwt'); + ... + - pattern: $JWT(...) + - pattern-not-inside: '$JWT(<... {isRevoked:...} ...>,...)' + - pattern-not-inside: |- + $OPTS = <... {isRevoked:...} ...>; + ... + $JWT($OPTS,...); + message: >- + No token revoking configured for `express-jwt`. A leaked token could still + be used and unable to be revoked. Consider using function as the `isRevoked` option. + severity: WARNING + languages: + - javascript + metadata: + cwe: 'CWE-522: Insufficiently Protected Credentials' + owasp: 'A2: Broken Authentication' diff --git a/rules/memory/buffer_noassert.js b/rules/memory/buffer_noassert.js new file mode 100644 index 0000000..ee55791 --- /dev/null +++ b/rules/memory/buffer_noassert.js @@ -0,0 +1,2 @@ +// ruleid:buffer_noassert +a.readUInt8(0, true) diff --git a/rules/memory/buffer_noassert.yaml b/rules/memory/buffer_noassert.yaml new file mode 100644 index 0000000..d57b5ce --- /dev/null +++ b/rules/memory/buffer_noassert.yaml @@ -0,0 +1,43 @@ +rules: + - id: buffer_noassert + pattern-either: + - pattern: '$OBJ.readUInt8(..., true)' + - pattern: '$OBJ.readUInt16LE(..., true)' + - pattern: '$OBJ.readUInt16BE(..., true)' + - pattern: '$OBJ.readUInt32LE(..., true)' + - pattern: '$OBJ.readUInt32BE(..., true)' + - pattern: '$OBJ.readInt8(..., true)' + - pattern: '$OBJ.readInt16LE(..., true)' + - pattern: '$OBJ.readInt16BE(..., true)' + - pattern: '$OBJ.readInt32LE(..., true)' + - pattern: '$OBJ.readInt32BE(..., true)' + - pattern: '$OBJ.readFloatLE(..., true)' + - pattern: '$OBJ.readFloatBE(..., true)' + - pattern: '$OBJ.readDoubleLE(..., true)' + - pattern: '$OBJ.readDoubleBE(..., true)' + - pattern: '$OBJ.writeUInt8(..., true)' + - pattern: '$OBJ.writeUInt16LE(..., true)' + - pattern: '$OBJ.writeUInt16BE(..., true)' + - pattern: '$OBJ.writeUInt32LE(..., true)' + - pattern: '$OBJ.writeUInt32BE(..., true)' + - pattern: '$OBJ.writeInt8(..., true)' + - pattern: '$OBJ.writeInt16LE(..., true)' + - pattern: '$OBJ.writeInt16BE(..., true)' + - pattern: '$OBJ.writeInt32LE(..., true)' + - pattern: '$OBJ.writeInt32BE(..., true)' + - pattern: '$OBJ.writeFloatLE(..., true)' + - pattern: '$OBJ.writeFloatBE(..., true)' + - pattern: '$OBJ.writeDoubleLE(..., true)' + - pattern: '$OBJ.writeDoubleBE(..., true)' + severity: WARNING + languages: + - javascript + metadata: + owasp: 'A6: Security Misconfiguration' + cwe: >- + CWE-119: Improper Restriction of Operations within the Bounds of a + Memory Buffer + message: >- + Detected usage of noassert in Buffer API, which allows the offset the be + beyond the end of the buffer. This could result in writing or reading + beyond the end of the buffer. diff --git a/rules/redirect/open_redirect.js b/rules/redirect/open_redirect.js new file mode 100644 index 0000000..cdc6da0 --- /dev/null +++ b/rules/redirect/open_redirect.js @@ -0,0 +1,130 @@ +const express = require('express'); +const router = express.Router() + +router.use((req, res, next) => { + if (req.method === 'POST') { + console.log(JSON.stringify(req.session.data, null, 2)) + } + next() +}) + +router.post('/sprint18b/frequency', (req, res) => { + res.redirect('/sprint18b/payment') //GOOD +}); + +var express = require('express'); + +var app = express(); + +app.get('/some/path', function (req, res) { + // BAD: a request parameter is incorporated without validation into a URL redirect + // ruleid:express_open_redirect + res.redirect(302, req.param("target")); +}); + +app.get('/some/path1', function (req, res) { + // BAD: a request parameter is incorporated without validation into a URL redirect + // ruleid:express_open_redirect + res.redirect(300, req.param); +}); + +app.get('/some/path2', function (req, res) { + // BAD: a request parameter is incorporated without validation into a URL redirect + // ruleid:express_open_redirect + res.redirect(req.param["target"]); +}); + +app.get('/some/path3', function (req, res) { + // BAD: a request parameter is incorporated without validation into a URL redirect + // ruleid:express_open_redirect + res.redirect(req.body.url); +}); +app.get('/some/path4', function (req, res) { + // BAD subdomain control + // ruleid:express_open_redirect + res.redirect("sdcssf" + req.param("target")); +}); +app.get('/some/path5', function (req, res) { + // BAD: a request parameter is incorporated without validation into a URL redirect + // ruleid:express_open_redirect + res.redirect(req.param("target") + "/asdad"); +}); +app.all(function (req, res) { + // BAD: a request parameter is incorporated without validation into a URL redirect + // ruleid:express_open_redirect2 + res.header("Location", req.param["target"]); +}); + +app.all(function (req, res) { + // BAD: a request parameter is incorporated without validation into a URL redirect + // ruleid:express_open_redirect2 + res.header('Location', req.param("foo")); +}); + + +app.all(function (req, res) { + // ruleid:express_open_redirect2 + res.writeHead(200, { location: 'foo \rinvalid: bar' + req.foo + 'asdadasd', foo: bar }); +}); + + +app.all(function (req, res) { + // ruleid:express_open_redirect2 + res.writeHead(200, { 'location': req.foo }); +}); + +app.all(function (req, res) { + // BAD: a request parameter is incorporated without validation into a URL redirect + // ruleid:express_open_redirect2 + res.header('location', req.param("bar")); +}); + +app.get('/some/path', function (req, res) { + // ruleid:express_open_redirect + var target = req.param("target"); + // BAD: sanitization doesn't apply here + res.redirect(target); +}); + +app.get('/foo', function (req, res) { + // BAD: may be a global redirection + // ruleid:express_open_redirect + res.redirect((req.param('action') && req.param('action') != "") ? req.param('action') : "/google_contacts") +}); + +app.get('/yet/another/path', function (req, res) { + // BAD: a request parameter is incorporated without validation into a URL redirect + // ruleid:express_open_redirect + res.redirect(`${req.param("target")}/foo`); +}); + +app.get('/array/join', function (req, res) { + // BAD: request input becomes before query string + // ruleid:express_open_redirect + res.redirect([req.query.page, '?section=', req.query.section].join('')); +}); + +app.get('/call', function (req, res) { + sendUserToUrl(res, req.query.nextUrl); +}); + +function sendUserToUrl(res, nextUrl) { + // BAD: value comes from query parameter + res.redrect(nextUrl); +} + +app.get('/redirect/:user', function (req, res) { + + // ruleid:express_open_redirect + res.redirect('/' + req.params.user); // BAD - could go to //evil.com + // ruleid:express_open_redirect + res.redirect('//' + req.params.user); // BAD - could go to //evil.com + // ruleid:express_open_redirect + res.redirect('u' + req.params.user); // BAD - could go to u.evil.com + // ruleid:express_open_redirect + res.redirect('Fan999' + req.params.user); // BAD - could go to Fan999.evil.com + // ruleid:express_open_redirect + res.redirect('/' + ('/u' + req.params.user)); // BAD - could go to //u.evil.com, + //do not trigger + res.redirect('/' + foo) +}); \ No newline at end of file diff --git a/rules/redirect/open_redirect.yaml b/rules/redirect/open_redirect.yaml new file mode 100644 index 0000000..0780523 --- /dev/null +++ b/rules/redirect/open_redirect.yaml @@ -0,0 +1,83 @@ +rules: + - id: express_open_redirect + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $X.redirect(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + $X.redirect(..., <... $REQ.$QUERY.$FOO ...>, ...) + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + $X.redirect(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY.$FOO ...>; + ... + $X.redirect(..., <... $INP ...>, ...); + message: >- + Untrusted user input in redirect() can result in Open Redirect + vulnerability. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-601: URL Redirection to Untrusted Site ('Open Redirect') + - id: express_open_redirect2 + patterns: + - pattern-inside: | + $APP.$METHOD(..., function $FUNC($REQ, $RES) { ... }) + - pattern-either: + - pattern: | + $RES.header(..., "=~/location/i", <... $REQ.$VAR ...>, ...) + - pattern: | + $RES.header(..., "=~/location/i", <... $REQ.$VAR.$VARR ...>, ...) + - pattern: | + $RES.writeHead(..., "=~/location/i", <... $REQ.$VAR ...>, ...) + - pattern: | + $RES.writeHead(..., "=~/location/i", <... $REQ.$VAR.$VARR ...>, ...) + - pattern: | + $RES.writeHead(..., {"=~/location/i": <... $REQ.$VAR ...> }, ...) + - pattern: | + $RES.writeHead(..., {"=~/location/i": <... $REQ.$VAR.$VARR ...> }, ...) + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + $RES.header(..., "=~/location/i", <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR.$VARR ...>; + ... + $RES.header(..., "=~/location/i", <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + $RES.writeHead(..., "=~/location/i", <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR.$VARR ...>; + ... + $RES.writeHead(..., "=~/location/i", <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + $RES.writeHead(..., {"=~/location/i": <... $INP ...> }, ...); + - pattern: | + $INP = <... $REQ.$VAR.$VARR ...>; + ... + $RES.writeHead(..., {"=~/location/i": <... $INP ...> }, ...); + message: >- + Untrusted user input in response header('Location') can result in Open + Redirect vulnerability. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-601: URL Redirection to Untrusted Site ('Open Redirect') \ No newline at end of file diff --git a/rules/ssrf/ssrf_node.js b/rules/ssrf/ssrf_node.js new file mode 100644 index 0000000..3805f5a --- /dev/null +++ b/rules/ssrf/ssrf_node.js @@ -0,0 +1,143 @@ +var express = require('express'); +const request = require('request'); +var needle = require('needle'); +var app = express(); +const bent = require('bent') +const getJSON = bent('json') +const getBuffer = bent('buffer') +var urllib = require('urllib'); +const http = require('http'); +let axios = require('axios'); +var http = require('https'); + +app.get('/', function (req, res) { + // ruleid:node_ssrf + request(req.query.name, function (error, response, body) { + console.error('error:', error); // Print the error if one occurred + console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received + console.log('body:', body); // Print the HTML for the Google homepage. + }); + + // ruleid:node_ssrf + needle('get', req.vbody.foo).then(res => { + console.log(res.body); + }) + .catch(err => { + console.error(err); + }); + + + // ruleid:node_ssrf + urllib.request(req.foo, function (err, data, res) { + if (err) { + throw err; // you need to handle error + } + console.log(res.statusCode); + console.log(res.headers); + // data is Buffer instance + console.log(data.toString()); + }); + +}); + +app.get('/', function (req, res) { + + // ruleid:node_ssrf + needle.get(req.foo, function (error, response) { + if (!error && response.statusCode == 200) + console.log(response.body); + }); + + + // ruleid:node_ssrf + needle.post(req.foo) + .pipe(out) + .on('finish', () => { + console.log('pipe done'); + }); + + //Do not match this + needle.get('http://google.com') + .pipe(out) + .on('finish', () => { + console.log('pipe done'); + }); + + //Do not match this to reduce false positives + needle.get(somvar) + .pipe(out) + .on('finish', () => { + console.log('pipe done'); + }); + + + // ruleid:node_ssrf + axios.get(req.foo.bar) + .then(function (response) { + // handle success + console.log(response); + }) + .catch(function (error) { + // handle error + console.log(error); + }) + .finally(function () { + // always executed + }); + + + // ruleid:node_ssrf + let obj = await getJSON(req.foo); + // ruleid:node_ssrf + let buffer = await getBuffer(req.foo); + + // ruleid:node_ssrf + fetch(req.post.doo, { method: 'POST', body: 'a=1' }) + .then(res => res.json()) // expecting a json response + .then(json => console.log(json)); + +}); + +app.listen(8000); + +// do not match +needle.get(foo, function (error, response) { + if (!error && response.statusCode == 200) + console.log(response.body); +}); + + +var net = require('net'); + + +app.get('/', function (req, res) { + var client = new net.Socket(); + // ruleid:node_ssrf + client.connect(1337, req.body.host, function () { + console.log('Connected'); + client.write('Hello, server! Love, Client.'); + }); + + client.on('data', function (data) { + console.log('Received: ' + data); + client.destroy(); // kill client after server's response + }); + + client.on('close', function () { + console.log('Connection closed'); + }); + + + + + // ruleid:node_ssrf + const fk = http.get({ host: req.foo }); + req.end(); + req.once('response', (res) => { + const ip = req.socket.localAddress; + const port = req.socket.localPort; + console.log(`Your IP address is ${ip} and your source port is ${port}.`); + // Consume response object + }); + +}); \ No newline at end of file diff --git a/rules/ssrf/ssrf_node.yaml b/rules/ssrf/ssrf_node.yaml new file mode 100644 index 0000000..55be1dd --- /dev/null +++ b/rules/ssrf/ssrf_node.yaml @@ -0,0 +1,191 @@ +rules: + - id: node_ssrf + patterns: + - pattern-either: + - pattern-inside: | + require('request'); + ... + - pattern-inside: | + require('axios'); + ... + - pattern-inside: | + require('needle'); + ... + - pattern-inside: | + require('bent'); + ... + - pattern-inside: | + require('urllib'); + ... + - pattern-inside: | + require('net'); + ... + - pattern-inside: | + require('https'); + ... + - pattern-inside: | + require('superagent'); + ... + - pattern-inside: | + require('got'); + ... + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $PKG.get(<... $REQ.$VAR ...>, ...) + - pattern: | + $PKG.get(<... $REQ.$VAR.$FOO ...>, ...) + - pattern: | + $PKG.post(<... $REQ.$VAR ...>, ...) + - pattern: | + $PKG.post(<... $REQ.$VAR.$FOO ...>, ...) + - pattern: | + $PKG.put(<... $REQ.$VAR ...>, ...) + - pattern: | + $PKG.put(<... $REQ.$VAR.$FOO ...>, ...) + - pattern: | + needle("=~/^[get|post|put]+$/i", <... $REQ.$VAR.$FOO ...>, ...) + - pattern: | + needle("=~/^[get|post|put]+$/i", <... $REQ.$VAR ...>, ...) + - pattern: | + request(<... $REQ.$VAR ...>, ...) + - pattern: | + request(<... $REQ.$VAR.$FOO ...>, ...) + - pattern: | + $PKG.request(<... $REQ.$VAR ...>, ...) + - pattern: | + $PKG.request(<... $REQ.$VAR.$FOO ...>, ...) + - pattern: | + getJSON(<... $REQ.$VAR ...>, ...) + - pattern: | + getJSON(<... $REQ.$VAR.$FOO ...>, ...) + - pattern: | + getBuffer(<... $REQ.$VAR ...>, ...) + - pattern: | + getBuffer(<... $REQ.$VAR.$FOO ...>, ...) + - pattern: | + fetch(<... $REQ.$VAR ...>, ...) + - pattern: | + fetch(<... $REQ.$VAR.$FOO ...>, ...) + - pattern: | + $SOCKET.connect($PORT, <... $REQ.$VAR ...>, ...) + - pattern: | + $SOCKET.connect($PORT, <... $REQ.$VAR.$FOO ...>, ...) + - pattern: | + $PKG.get(..., {host: <... $REQ.$VAR ...>}, ...) + - pattern: | + $PKG.get(..., {host: <... $REQ.$VAR.$FOO ...>}, ...) + - pattern: | + $PKG.get(..., {hostname: <... $REQ.$VAR ...>}, ...) + - pattern: | + $PKG.get(..., {hostname: <... $REQ.$VAR.$FOO ...>}, ...) + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + $PKG.get(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR.$FOO ...>; + ... + $PKG.get(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + $PKG.post(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR.$FOO ...>; + ... + $PKG.post(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + $PKG.put(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR.$FOO ...>; + ... + $PKG.put(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR.$FOO ...>; + ... + needle("=~/^[get|post|put]+$/i", <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + needle("=~/^[get|post|put]+$/i", <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + request(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR.$FOO ...>; + ... + request(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + $PKG.request(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR.$FOO ...>; + ... + $PKG.request(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + getJSON(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR.$FOO ...>; + ... + getJSON(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + getBuffer(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR.$FOO ...>; + ... + getBuffer(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + fetch(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR.$FOO ...>; + ... + fetch(<... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + $SOCKET.connect($PORT, <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR.$FOO ...>; + ... + $SOCKET.connect($PORT, <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + $PKG.get(..., {host: <... $INP ...>}, ...); + - pattern: | + $INP = <... $REQ.$VAR.$FOO ...>; + ... + $PKG.get(..., {host: <... $INP ...>}, ...); + - pattern: | + $INP = <... $REQ.$VAR ...>; + ... + $PKG.get(..., {hostname: <... $INP ...>}, ...); + - pattern: | + $INP = <... $REQ.$VAR.$FOO ...>; + ... + $PKG.get(..., {hostname: <... $INP ...>}, ...); + message: >- + User controlled URL in http client libraries can result in Server Side + Request Forgery (SSRF). + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-918: Server-Side Request Forgery (SSRF)' \ No newline at end of file diff --git a/rules/ssrf/ssrf_phantomjs.js b/rules/ssrf/ssrf_phantomjs.js new file mode 100644 index 0000000..017d83a --- /dev/null +++ b/rules/ssrf/ssrf_phantomjs.js @@ -0,0 +1,122 @@ +const express = require('express') +const app = express() +const port = 3000 +const phantom = require('phantom'); + +app.get('/test', async (req, res) => { + const instance = await phantom.create(); + const page = await instance.createPage(); + await page.on('onResourceRequested', function (requestData) { + console.info('Requesting', requestData.url); + }); + + // ruleid: phantom_ssrf + const status = await page.property('content', req.get('name')); + + // ruleid: phantom_ssrf + await page.setContent(req.query.q); + + res.send('Hello World!') +}) + +app.post('/test2', async (req, res) => { + const instance = await phantom.create(); + const page = await instance.createPage(); + await page.on('onResourceRequested', function (requestData) { + console.info('Requesting', requestData.url); + }); + + // ruleid: phantom_ssrf + const status = await page.property('content', req.query.q); + + // ruleid: phantom_ssrf + await page.setContent(req.body); + + const express = require('express') + const app = express() + const port = 3000 + const phantom = require('phantom'); + + app.get('/test', async (req, res) => { + const instance = await phantom.create(); + const page = await instance.createPage(); + await page.on('onResourceRequested', function (requestData) { + console.info('Requesting', requestData.url); + }); + + // ruleid: phantom_ssrf + const status = await page.property('content', req.get('name')); + + // ruleid: phantom_ssrf + await page.setContent(req.query.q); + + res.send('Hello World!') + }) + + app.post('/test2', async (req, res) => { + const instance = await phantom.create(); + const page = await instance.createPage(); + await page.on('onResourceRequested', function (requestData) { + console.info('Requesting', requestData.url); + }); + + // ruleid: phantom_ssrf + const status = await page.property('content', req.query.q); + + // ruleid: phantom_ssrf + await page.setContent(req.body); + + + await instance.exit(); + + res.send('Hello World!') + }) + + app.post('/test3', async (req, res) => { + const instance = await phantom.create(); + const page = await instance.createPage(); + await page.on('onResourceRequested', function (requestData) { + console.info('Requesting', requestData.url); + }); + + // ruleid: phantom_ssrf + const status = await page.openUrl(req.params.url, {}, {}); + + // ruleid: phantom_ssrf + await page.evaluateJavaScript(req.body.script); + + + await instance.exit(); + + res.send('Hello World!') + }) + + + app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`)) + await instance.exit(); + + res.send('Hello World!') +}) + +app.post('/test3', async (req, res) => { + const instance = await phantom.create(); + const page = await instance.createPage(); + await page.on('onResourceRequested', function (requestData) { + console.info('Requesting', requestData.url); + }); + + // ruleid: phantom_ssrf + const status = await page.openUrl(req.params.url, {}, {}); + + // ruleid: phantom_ssrf + await page.evaluateJavaScript(req.body.script); + + + + await instance.exit(); + + res.send('Hello World!') +}) + + +app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`)) \ No newline at end of file diff --git a/rules/ssrf/ssrf_phantomjs.yaml b/rules/ssrf/ssrf_phantomjs.yaml new file mode 100644 index 0000000..d00bb03 --- /dev/null +++ b/rules/ssrf/ssrf_phantomjs.yaml @@ -0,0 +1,71 @@ +rules: + - id: phantom_ssrf + patterns: + - pattern-inside: | + require('phantom'); + ... + - pattern-either: + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: '$PAGE.open(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.setContent(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.open(<... $REQ.$BODY ...>,...)' + - pattern: '$PAGE.setContent(<... $REQ.$BODY ...>,...)' + - pattern: '$PAGE.openUrl(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.openUrl(<... $REQ.$BODY ...>,...)' + - pattern: '$PAGE.evaluateJavaScript(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.evaluateJavaScript(<... $REQ.$BODY ...>,...)' + - pattern: '$PAGE.property("content",<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.property("content",<... $REQ.$BODY ...>,...)' + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.open(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.open(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.setContent(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.setContent(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.openUrl(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.openUrl(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.evaluateJavaScript(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.evaluateJavaScript(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.property("content",<... $INPUT ...>,...); + - pattern: |- + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.property("content",<... $INPUT ...>,...); + message: > + If unverified user data can reach the `phantom` methods it can result in + Server-Side Request Forgery vulnerabilities. + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-918: Server-Side Request Forgery (SSRF)' + severity: ERROR + languages: + - javascript diff --git a/rules/ssrf/ssrf_playwright.js b/rules/ssrf/ssrf_playwright.js new file mode 100644 index 0000000..3a317bc --- /dev/null +++ b/rules/ssrf/ssrf_playwright.js @@ -0,0 +1,58 @@ +const { chromium } = require('playwright'); +const express = require('express') +const app = express() +const port = 3000 + +app.post('/goto', async (req, res) => { + const browser = await chromium.launch(); + const page = await browser.newPage(); + let url = 'https://hardcoded.url.com' + + // ruleid:playwright_ssrf + await page.goto(req.foo); + + // ruleid:playwright_ssrf + const newUrl = req.foo.bar; + await page.goto(newUrl); + + await page.screenshot({ path: 'example.png' }); + await browser.close(); +}) + +app.post('/setContent', async (req, res) => { + const browser = await chromium.launch(); + const page = await browser.newPage(); + + + // ruleid:playwright_ssrf + await page.setContent(req.foo['bar']); + + await page.screenshot({ path: 'example.png' }); + await browser.close(); +}) + +app.post('/evaluate', async (req, res) => { + + const browser = await chromium.launch(); + const page = await browser.newPage(); + + + // ruleid:playwright_ssrf + await page.evaluate(`fetch(${req.foo})`); + + await page.screenshot({ path: 'example.png' }); + await browser.close(); +}) + +app.post('/evaluate', async (req, res) => { + + const browser = await chromium.launch(); + const page = await browser.newPage(); + + + // ruleid:playwright_ssrf + await page.evaluate(x => fetch(x), req.foo.bar); + + await page.screenshot({ path: 'example.png' }); + await browser.close(); +}) \ No newline at end of file diff --git a/rules/ssrf/ssrf_playwright.yaml b/rules/ssrf/ssrf_playwright.yaml new file mode 100644 index 0000000..03fa29d --- /dev/null +++ b/rules/ssrf/ssrf_playwright.yaml @@ -0,0 +1,101 @@ +rules: + - id: playwright_ssrf + patterns: + - pattern-inside: | + require('playwright'); + ... + - pattern-either: + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: '$PAGE.goto(<... $REQ.$QUERY.$FOO ...>, ...)' + - pattern: '$PAGE.goto(<... $REQ.$BODY ...>, ...)' + - pattern: '$PAGE.setContent(<... $REQ.$QUERY.$FOO ...>, ...)' + - pattern: '$PAGE.setContent(<... $REQ.$BODY ...>, ...)' + - pattern: '$PAGE.evaluate(<... $REQ.$QUERY.$FOO ...>, ...)' + - pattern: '$PAGE.evaluate(<... $REQ.$BODY ...>, ...)' + - pattern: '$PAGE.evaluate($CODE,..., <... $REQ.$QUERY.$FOO ...>, ...)' + - pattern: '$PAGE.evaluate($CODE,..., <... $REQ.$BODY ...>, ...)' + - pattern: '$PAGE.evaluateHandle(<... $REQ.$QUERY.$FOO ...>, ...)' + - pattern: '$PAGE.evaluateHandle(<... $REQ.$BODY ...>, ...)' + - pattern: '$PAGE.evaluateHandle($CODE,..., <... $REQ.$QUERY.$FOO ...>, ...)' + - pattern: '$PAGE.evaluateHandle($CODE,..., <... $REQ.$BODY ...>, ...)' + - pattern: '$PAGE.evaluateOnNewDocument(<... $REQ.$BODY ...>, ...)' + - pattern: '$PAGE.evaluateOnNewDocument(<... $REQ.$BODY.$FOO ...>, ...)' + - pattern: '$CONTEXT.addInitScript(<... $REQ.$BODY ...>,...)' + - pattern: '$CONTEXT.addInitScript(<... $REQ.$BODY.$FOO ...>,...)' + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.goto(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.goto(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.setContent(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.setContent(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.evaluate($CODE,..., <... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.evaluate($CODE,..., <... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.evaluate(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.evaluate(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.evaluateHandle(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.evaluateHandle(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.evaluateHandle($CODE,..., <... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.evaluateHandle($CODE,..., <... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.evaluateOnNewDocument(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.evaluateOnNewDocument(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $CONTEXT.addInitScript($INPUT,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $CONTEXT.addInitScript($INPUT,...); + message: >- + If unverified user data can reach the `puppeteer` methods it can result in + Server-Side Request Forgery vulnerabilities. + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-918: Server-Side Request Forgery (SSRF)' + severity: ERROR + languages: + - javascript diff --git a/rules/ssrf/ssrf_puppeteer.js b/rules/ssrf/ssrf_puppeteer.js new file mode 100644 index 0000000..06c9b61 --- /dev/null +++ b/rules/ssrf/ssrf_puppeteer.js @@ -0,0 +1,69 @@ +const express = require('express') +const app = express() +const port = 3000 +const puppeteer = require('puppeteer') + +app.get('/', async (req, res) => { + const browser = await puppeteer.launch() + const page = await browser.newPage() + // ruleid: puppeteer_ssrf + const url = `https://${req.query.name}` + await page.goto(url) + + await page.screenshot({ path: 'example.png' }) + await browser.close() + + res.send('Hello World!') +}) + +app.post('/test', async (req, res) => { + const browser = await puppeteer.launch() + const page = await browser.newPage() + // ruleid: puppeteer_ssrf + await page.setContent(`${req.body.foo}`) + + await page.screenshot({ path: 'example.png' }) + await browser.close() + + res.send('Hello World!') +}) + +const controller = async (req, res) => { + const browser = await puppeteer.launch(); + const page = await browser.newPage(); + // ruleid: puppeteer_ssrf + const body = req.body.foo; + await page.setContent('' + body + ''); + + await page.screenshot({ path: 'example.png' }); + await browser.close(); + + res.send('Hello World!'); +} + +app.post('/test2', async (req, res) => { + const browser = await puppeteer.launch() + const page = await browser.newPage() + // ruleid: puppeteer_ssrf + await page.evaluateOnNewDocument(`${req.body.foo}`) + + await page.screenshot({ path: 'example.png' }) + await browser.close() + + res.send('Hello World!') +}) + +const controller2 = async (req, res) => { + const browser = await puppeteer.launch(); + const page = await browser.newPage(); + // ruleid: puppeteer_ssrf + const body = req.body.foo; + await page.evaluate('alert(' + body + ')'); + + await page.screenshot({ path: 'example.png' }); + await browser.close(); + + res.send('Hello World!'); +} + +app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`)) \ No newline at end of file diff --git a/rules/ssrf/ssrf_puppeteer.yaml b/rules/ssrf/ssrf_puppeteer.yaml new file mode 100644 index 0000000..8d3e7b5 --- /dev/null +++ b/rules/ssrf/ssrf_puppeteer.yaml @@ -0,0 +1,101 @@ +rules: + - id: puppeteer_ssrf + patterns: + - pattern-inside: | + require('puppeteer'); + ... + - pattern-either: + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: '$PAGE.goto(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.goto(<... $REQ.$BODY ...>,...)' + - pattern: '$PAGE.setContent(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.setContent(<... $REQ.$BODY ...>,...)' + - pattern: '$PAGE.evaluate(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.evaluate(<... $REQ.$BODY ...>,...)' + - pattern: '$PAGE.evaluateHandle(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.evaluateHandle(<... $REQ.$BODY ...>,...)' + - pattern: '$PAGE.evaluateOnNewDocument(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.evaluateOnNewDocument(<... $REQ.$BODY ...>,...)' + - pattern: '$PAGE.evaluate($CODE,<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.evaluate($CODE,<... $REQ.$BODY ...>,...)' + - pattern: '$PAGE.evaluateHandle($CODE,<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.evaluateHandle($CODE,<... $REQ.$BODY ...>,...)' + - pattern: '$PAGE.evaluateOnNewDocument($CODE,<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PAGE.evaluateOnNewDocument($CODE,<... $REQ.$BODY ...>,...)' + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.goto(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.goto(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.setContent(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.setContent(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.evaluate(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.evaluate(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.evaluateHandle(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.evaluateHandle(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.evaluateOnNewDocument(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.evaluateOnNewDocument(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.evaluate($CODE,<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.evaluate($CODE,<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.evaluateHandle($CODE,<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.evaluateHandle($CODE,<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PAGE.evaluateOnNewDocument($CODE,<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PAGE.evaluateOnNewDocument($CODE,<... $INPUT ...>,...); + message: >- + If unverified user data can reach the `puppeteer` methods it can result in + Server-Side Request Forgery vulnerabilities. + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-918: Server-Side Request Forgery (SSRF)' + severity: ERROR + languages: + - javascript diff --git a/rules/ssrf/ssrf_wkhtmltoimage.js b/rules/ssrf/ssrf_wkhtmltoimage.js new file mode 100644 index 0000000..127cb28 --- /dev/null +++ b/rules/ssrf/ssrf_wkhtmltoimage.js @@ -0,0 +1,18 @@ +var wkhtmltoimage = require('wkhtmltoimage') + +// ruleid: wkhtmltopdf_ssrf_warning +wkhtmltoimage.generate(input(), { output: 'vuln.jpg' }) + +function test(userInput) { + // ruleid: wkhtmltopdf_ssrf_warning + wkhtmltoimage.generate(userInput, { output: 'vuln.jpg' }) +} + + +app.get('/', function (req, res) { + + // ruleid:wkhtmltopdf_ssrf + wkhtmltoimage.generate(req.foo, { output: 'vuln.jpg' }) + + +}); diff --git a/rules/ssrf/ssrf_wkhtmltoimage.yaml b/rules/ssrf/ssrf_wkhtmltoimage.yaml new file mode 100644 index 0000000..754850a --- /dev/null +++ b/rules/ssrf/ssrf_wkhtmltoimage.yaml @@ -0,0 +1,34 @@ +rules: + - id: wkhtmltoimage_ssrf + patterns: + - pattern-inside: | + require('wkhtmltoimage'); + ... + - pattern-either: + - pattern-inside: 'function ($REQ, $RES, ...) {...}' + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: | + $INP = <...$REQ.$VAR...>; + ... + $PKG.generate(<... $INP ...>, ...); + - pattern: | + $INP = <...$REQ.$VAR.$FOO...>; + ... + $PKG.generate(<... $INP ...>, ...); + - pattern: | + $PKG.generate(<... $REQ.$VAR ...>, ...) + - pattern: | + $PKG.generate(<... $REQ.$VAR.$FOO ...>, ...) + message: >- + User controlled URL reached to `wkhtmltoimage` can result in Server Side + Request Forgery (SSRF). + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-918: Server-Side Request Forgery (SSRF)' diff --git a/rules/ssrf/ssrf_wkhtmltopdf.js b/rules/ssrf/ssrf_wkhtmltopdf.js new file mode 100644 index 0000000..e289409 --- /dev/null +++ b/rules/ssrf/ssrf_wkhtmltopdf.js @@ -0,0 +1,17 @@ +const wkhtmltopdf = require('wkhtmltopdf') + +// ruleid:wkhtmltopdf_ssrf_warning +wkhtmltopdf(input(), { output: 'vuln.pdf' }) + +function test(userInput) { + // ruleid:wkhtmltopdf_ssrf_warning + return wkhtmltopdf(userInput, { output: 'vuln.pdf' }) +} + + +app.get('/', function (req, res) { + wkhtmltopdf('', { output: 'vuln.pdf' }) + // ruleid:wkhtmltopdf_ssrf + wkhtmltopdf(req.foo, { output: 'vuln.pdf' }) + +}); \ No newline at end of file diff --git a/rules/ssrf/ssrf_wkhtmltopdf.yaml b/rules/ssrf/ssrf_wkhtmltopdf.yaml new file mode 100644 index 0000000..bb307b2 --- /dev/null +++ b/rules/ssrf/ssrf_wkhtmltopdf.yaml @@ -0,0 +1,34 @@ +rules: + - id: wkhtmltopdf_ssrf + patterns: + - pattern-inside: | + require('wkhtmltopdf'); + ... + - pattern-either: + - pattern-inside: 'function ($REQ, $RES, ...) {...}' + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: | + $INP = <...$REQ.$VAR...>; + ... + wkhtmltopdf(<... $INP ...>, ...); + - pattern: | + $INP = <...$REQ.$VAR.$FOO...>; + ... + wkhtmltopdf(<... $INP ...>, ...); + - pattern: | + wkhtmltopdf(<... $REQ.$VAR ...>, ...) + - pattern: | + wkhtmltopdf(<... $REQ.$VAR.$FOO ...>, ...) + message: >- + User controlled URL reached to `wkhtmltopdf` can result in Server Side + Request Forgery (SSRF). + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: 'CWE-918: Server-Side Request Forgery (SSRF)' diff --git a/rules/traversal/archive_path_overwrite.js b/rules/traversal/archive_path_overwrite.js new file mode 100644 index 0000000..75d413f --- /dev/null +++ b/rules/traversal/archive_path_overwrite.js @@ -0,0 +1,82 @@ +//Ref: https://snyk.io/research/zip-slip-vulnerability +const fs = require('fs'); +const unzip = require('unzip'); + +fs.createReadStream('archive.zip') + .pipe(unzip.Parse()) + .on('entry', entry => { + const fileName = entry.path; + // Arbitrary file overwrite + // ruleid:zip_path_overwrite + entry.pipe(fs.createWriteStream(fileName)); + }); + +fs.createReadStream('archive.zip') + .pipe(unzip.Parse()) + .on('entry', entry => { + const fileName = entry.path; + // Arbitrary file overwrite + // ruleid:zip_path_overwrite + entry.pipe(fs.writeFileSync(fileName)); + }); + +fs.readFile('path/to/archive.zip', function (err, zipContents) { + unzip.Parse(zipContents).on('entry', function (entry) { + var fileName = 'output/path/' + entry.path; + // Arbitrary file overwrite + // ruleid:zip_path_overwrite2 + fs.writeFileSync(fileName, entry.contents); + }); +}); + +//admzip +const fs = require('fs'); +var AdmZip = require('adm-zip'); +var zip = new AdmZip("archive.zip"); +var zipEntries = zip.getEntries(); +// ruleid:admzip_path_overwrite +zipEntries.forEach(function (zipEntry) { + fs.createWriteStream(zipEntry.entryName); +}); + +// ruleid:admzip_path_overwrite +zip.getEntries().forEach(function (zipEntry) { + fs.writeFileSync(zipEntry.entryName); +}); + +// tar-stream overwrite +const tar = require('tar-stream'); +const extract = tar.extract(); + +extract.on('entry', (header, stream, next) => { + // ruleid:tar_path_overwrite + const out = fs.createWriteStream(header.name); + stream.pipe(out); + stream.on('end', () => { + next(); + }) + stream.resume(); +}) + +tar.extract().on('entry', (header, stream, next) => { + // ruleid:tar_path_overwrite + const out = fs.writeFileSync(header.name); + stream.pipe(out); + stream.on('end', () => { + next(); + }) + stream.resume(); +}) + +///unzipper lib +fs.createReadStream('./bad.tar').pipe(extract); +const fs = require('fs'); +const unzipper = require('unzipper'); + +fs.createReadStream('path/to/archive.zip') + .pipe(unzipper.Parse()) + .on('entry', function (entry) { + var fileName = entry.path; + // ruleid:zip_path_overwrite + entry.pipe(fs.createWriteStream(fileName)); + }); diff --git a/rules/traversal/archive_path_overwrite.yaml b/rules/traversal/archive_path_overwrite.yaml new file mode 100644 index 0000000..a8aa978 --- /dev/null +++ b/rules/traversal/archive_path_overwrite.yaml @@ -0,0 +1,158 @@ +rules: + - id: zip_path_overwrite + patterns: + - pattern-either: + - pattern-inside: | + $X = require('unzip'); + ... + - pattern-inside: | + $X = require('unzipper'); + ... + - pattern-inside: | + $Y.pipe($UNZIP.Parse(...)).on('entry', function $FUNC(...) { + ... + }, ...); + - pattern-not: | + $X = $FILENAME.indexOf(...); + - pattern-not: > + $FUNC.pipe($FS.createWriteStream($PATH.join(..., + $PATH.basename($FILENAME, ...)))); + - pattern-not: > + $FUNC.pipe($FS.writeFile($PATH.join(..., $PATH.basename($FILENAME, + ...)))); + - pattern-not: > + $FUNC.pipe($FS.writeFileSync($PATH.join(..., $PATH.basename($FILENAME, + ...)))); + - pattern-either: + - pattern: | + $FUNC.pipe($FS.createWriteStream($FIL, ...)); + - pattern: | + $FUNC.pipe($FS.writeFile($FIL, ...)); + - pattern: | + $FUNC.pipe($FS.writeFileSync($FIL, ...)); + message: >- + Insecure ZIP archive extraction can result in arbitrary path over write + and can result in code injection. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A5: Broken Access Control' + cwe: >- + CWE-22: Improper Limitation of a Pathname to a Restricted Directory + ('Path Traversal') + - id: zip_path_overwrite2 + patterns: + - pattern-either: + - pattern-inside: | + $X = require('unzip'); + ... + - pattern-inside: | + $X = require('unzipper'); + ... + - pattern-inside: | + $UNZIP.Parse(...).on('entry', function $FUNC($ENTRY) { + ... + }, ...); + - pattern-not: | + if ($FILENAME.indexOf('..')); + - pattern-not: > + $FS.createWriteStream($PATH.join(..., $PATH.basename($FILENAME, + ...))); + - pattern-not: | + $FS.writeFile($PATH.join(..., $PATH.basename($FILENAME, ...))); + - pattern-not: | + $FS.writeFileSync($PATH.join(..., $PATH.basename($FILENAME, ...))); + - pattern-either: + - pattern: | + $FS.createWriteStream($FIL, ...); + - pattern: | + $FS.writeFile($FIL, ...); + - pattern: | + $FS.writeFileSync($FIL, ...); + message: >- + Insecure ZIP archive extraction can result in arbitrary path over write + and can result in code injection. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A5: Broken Access Control' + cwe: >- + CWE-22: Improper Limitation of a Pathname to a Restricted Directory + ('Path Traversal') + - id: admzip_path_overwrite + patterns: + - pattern-inside: | + $X = require('adm-zip'); + ... + - pattern-not: | + if ($FILENAME.indexOf('..')); + - pattern-not: > + $FS.createWriteStream($PATH.join(..., $PATH.basename($FILENAME, + ...))); + - pattern-not: | + $FS.writeFile($PATH.join(..., $PATH.basename($FILENAME, ...))); + - pattern-not: | + $FS.writeFileSync($PATH.join(..., $PATH.basename($FILENAME, ...))); + - pattern-either: + - pattern: >- + $ZIPENTZ.forEach(function $FUNC($ENTRY, ...) { + $FS.createWriteStream(...); }, ...); + - pattern: >- + $ZIPENTZ.forEach(function $FUNC($ENTRY, ...) { $FS.writeFile(...); + }, ...); + - pattern: >- + $ZIPENTZ.forEach(function $FUNC($ENTRY, ...) { + $FS.writeFileSync(...); }, ...); + message: >- + Insecure ZIP archive extraction using adm-zip can result in arbitrary path + over write and can result in code injection. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A5: Broken Access Control' + cwe: >- + CWE-22: Improper Limitation of a Pathname to a Restricted Directory + ('Path Traversal') + - id: tar_path_overwrite + patterns: + - pattern-inside: | + $X = require('tar-stream'); + ... + - pattern-not-inside: | + $Y.pipe($UNZIP.Parse(...)).on('entry', function $FUNC(...) { + ... + }, ...); + - pattern-inside: | + $EXTRACT.on('entry', function $FUNC(...) { + ... + }, ...); + - pattern-not: | + if ($FILENAME.indexOf('..')); + - pattern-not: > + $FS.createWriteStream($PATH.join(..., $PATH.basename($FILENAME, + ...))); + - pattern-not: | + $FS.writeFile($PATH.join(..., $PATH.basename($FILENAME, ...))); + - pattern-not: | + $FS.writeFileSync($PATH.join(..., $PATH.basename($FILENAME, ...))); + - pattern-either: + - pattern: | + $FS.createWriteStream($FIL, ...); + - pattern: | + $FS.writeFile($FIL, ...); + - pattern: | + $FS.writeFileSync($FIL, ...); + message: >- + Insecure TAR archive extraction can result in arbitrary path over write + and can result in code injection. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A5: Broken Access Control' + cwe: >- + CWE-22: Improper Limitation of a Pathname to a Restricted Directory + ('Path Traversal') diff --git a/rules/traversal/path_traversal.js b/rules/traversal/path_traversal.js new file mode 100644 index 0000000..d5a085f --- /dev/null +++ b/rules/traversal/path_traversal.js @@ -0,0 +1,40 @@ +var http = require('http'), + fileSystem = require('fs'), + path = require('path'); + +var config = require('../config'); +var Promise = require('bluebird'); +Promise.promisifyAll(fileSystem); + +var express = require('express'); +var app = express(); +app.get('/', function (req, res) { + // ruleid:generic_path_traversal + var filePath = path.join(__dirname, '/' + req.query.load); + var readStream = fileSystem.createReadStream(filePath); + // ruleid:generic_path_traversal + fileSystem.readFile(req.query.foo); + // ruleid:generic_path_traversal + console.log(fileSystem.readFileSync(req.query.nar, 'utf8')); + // ruleid:generic_path_traversal + var foo = req.query.y; + fileSystem.readFile(foo); + fileSystem.readFile(foo + "bar"); + readStream.pipe(res); +}); + +app.get('/foo', function (req, res) { + // ruleid:generic_path_traversal + var date = req.query.date; + var fileName = config.dirName + '/' + date; + var downloadFileName = 'log_' + fileName + '.txt'; + + fs.readFileAsync(fileName) + .then(function (data) { + res.download(fileName, downloadFileName); + }) +}) + +app.listen(8888); +// do not match +fileSystem.readFile(ddd); \ No newline at end of file diff --git a/rules/traversal/path_traversal.yaml b/rules/traversal/path_traversal.yaml new file mode 100644 index 0000000..5a0a8cf --- /dev/null +++ b/rules/traversal/path_traversal.yaml @@ -0,0 +1,128 @@ +rules: + - id: generic_path_traversal + patterns: + - pattern-either: + - pattern-inside: | + require('http'); + ... + - pattern-inside: | + require('express'); + ... + - pattern-inside: | + require('koa'); + ... + - pattern-inside: | + require('electron'); + ... + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $X.createReadStream(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + $X.createReadStream(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + $X.readFile(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + $X.readFile(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + $X.readFileSync(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + $X.readFileSync(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + $X.readFileAsync(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + $X.readFileAsync(..., <... $REQ.$QUERY ...>, ...) + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + $X.createReadStream(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + $X.createReadStream(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + $X.readFile(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + $X.readFile(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + $X.readFileSync(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + $X.readFileSync(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + $X.readFileAsync(..., <... $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$QUERY ...>; + ... + $X.readFileAsync(..., <... $INP ...>, ...); + - pattern: | + $Y = $REQ.$QUERY.$VAR; + ... + $INP = <... $Y ...>; + ... + $X.createReadStream(..., <... $INP ...>, ...); + - pattern: | + $Y = $REQ.$QUERY; + ... + $INP = <... $Y ...>; + ... + $X.createReadStream(..., <... $INP ...>, ...); + - pattern: | + $Y = $REQ.$QUERY.$VAR; + ... + $INP = <... $Y ...>; + ... + $X.readFile(..., <... $INP ...>, ...); + - pattern: | + $Y = $REQ.$QUERY; + ... + $INP = <... $Y ...>; + ... + $X.readFile(..., <... $INP ...>, ...); + - pattern: | + $Y = $REQ.$QUERY.$VAR; + ... + $INP = <... $Y ...>; + ... + $X.readFileSync(..., <... $INP ...>, ...); + - pattern: | + $Y = $REQ.$QUERY; + ... + $INP = <... $Y ...>; + ... + $X.readFileSync(..., <... $INP ...>, ...); + - pattern: | + $Y = $REQ.$QUERY.$VAR; + ... + $INP = <... $Y ...>; + ... + $X.readFileAsync(..., <... $INP ...>, ...); + - pattern: | + $Y = $REQ.$QUERY; + ... + $INP = <... $Y ...>; + ... + $X.readFileAsync(..., <... $INP ...>, ...); + message: >- + Untrusted user input in readFile()/readFileSync() can endup in Directory + Traversal Attacks. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A5: Broken Access Control' + cwe: 'CWE-23: Relative Path Traversal' \ No newline at end of file diff --git a/rules/traversal/path_traversal_join_resolve.js b/rules/traversal/path_traversal_join_resolve.js new file mode 100644 index 0000000..b7d5f58 --- /dev/null +++ b/rules/traversal/path_traversal_join_resolve.js @@ -0,0 +1,27 @@ +const path = require('path') +const express = require('express') +const app = express() +const port = 3000 + +app.get('/test1', (req, res) => { + // ruleid:join_resolve_path_traversal + var extractPath = path.join(opts.path, req.query.path); + extractFile(extractPath); + res.send('Hello World!'); +}) + +app.post('/test2', function test2(req, res) { + // ruleid:join_resolve_path_traversal + createFile({ filePath: path.resolve(opts.path, req.body) }) + res.send('Hello World!') +}) + +function testCtrl3(req, res) { + // ruleid:join_resolve_path_traversal + let somePath = req.body.path; + const pth = path.join(opts.path, somePath); + extractFile(pth); + res.send('Hello World!'); +} + +app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`)) \ No newline at end of file diff --git a/rules/traversal/resolve_path_traversal.yaml b/rules/traversal/resolve_path_traversal.yaml new file mode 100644 index 0000000..ea7ad80 --- /dev/null +++ b/rules/traversal/resolve_path_traversal.yaml @@ -0,0 +1,44 @@ +rules: + - id: join_resolve_path_traversal + patterns: + - pattern-inside: | + require('path'); + ... + - pattern-either: + - pattern-inside: 'function ($REQ, $RES, ...) {...}' + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: '$PATH.join(...,<... $REQ.$BODY ...>,...)' + - pattern: '$PATH.join(...,<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: | + $VAR = <... $REQ.$BODY ...>; + ... + $PATH.join(...,<... $VAR ...>,...); + - pattern: | + $VAR = <... $REQ.$QUERY.$FOO ...>; + ... + $PATH.join(...,<... $VAR ...>,...); + - pattern: '$PATH.resolve(...,<... $REQ.$BODY ...>,...)' + - pattern: '$PATH.resolve(...,<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: | + $VAR = <... $REQ.$BODY ...>; + ... + $PATH.resolve(...,<... $VAR ...>,...); + - pattern: |- + $VAR = <... $REQ.$QUERY.$FOO ...>; + ... + $PATH.resolve(...,<... $VAR ...>,...); + message: >- + Path constructed with user input can result in Path Traversal. Ensure that + user input does not reach `join()` or `resolve()`. + languages: + - javascript + metadata: + owasp: 'A5: Broken Access Control' + cwe: >- + CWE-22: Improper Limitation of a Pathname to a Restricted Directory + (Path Traversal) + severity: WARNING diff --git a/rules/xml/xml_entity_expansion.js b/rules/xml/xml_entity_expansion.js new file mode 100644 index 0000000..fd35d01 --- /dev/null +++ b/rules/xml/xml_entity_expansion.js @@ -0,0 +1,5 @@ +app.get('/expat', function (req, res) { + // ruleid:node_entity_expansion + var parser = new expat.Parser(); + parser.write(req.param("xml")); +}) \ No newline at end of file diff --git a/rules/xml/xml_entity_expansion_dos.yaml b/rules/xml/xml_entity_expansion_dos.yaml new file mode 100644 index 0000000..7c853de --- /dev/null +++ b/rules/xml/xml_entity_expansion_dos.yaml @@ -0,0 +1,31 @@ +rules: + - id: node_entity_expansion + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $PARSER = new expat.Parser(); + ... + $PARSER.write(..., <... $REQ.$QUERY ...>, ...); + - pattern: | + $PARSER = new expat.Parser(); + ... + $PARSER.write(..., <... $REQ.$QUERY.$FOO ...>, ...); + - pattern: | + $PARSER = new expat.Parser(); + ... + $PARSER.write(..., <... $REQ.$QUERY.$FOO.$FILE ...>, ...); + message: >- + User controlled data in XML Parsers can result in + XML Internal Entity Processing vulnerabilities like in DoS. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A4: XML External Entities (XXE)' + cwe: "CWE-776: Improper Restriction of Recursive Entity References in DTDs ('XML Entity Expansion')" \ No newline at end of file diff --git a/rules/xml/xpathi_node.js b/rules/xml/xpathi_node.js new file mode 100644 index 0000000..6560958 --- /dev/null +++ b/rules/xml/xpathi_node.js @@ -0,0 +1,19 @@ +var xpath = require('xpath'); +var express = require('express'); + +var app = express(); + +app.get('/xpath', function (req, res) { + // ruleid:node_xpath_injection + var expr = xpath.parse("//persons/user[name/text()='" + req.param("name") + "']/details/text()"); + // ruleid:node_xpath_injection + expr = xpath.parse("//persons/user[name/text()='" + req.param.name + "']/details/text()"); + // ruleid:node_xpath_injection + expr = xpath.parse("//persons/user[name/text()='" + req["name"] + "']/details/text()"); + // ruleid:node_xpath_injection + var foo = req.param; + expr = xpath.parse("//persons/user[name/text()='" + foo + "']/details/text()"); + //do not match + expr = JSON.parse("{'foo':" + req.param + "}"); + res.redirect('/home') +}); diff --git a/rules/xml/xpathi_node.yaml b/rules/xml/xpathi_node.yaml new file mode 100644 index 0000000..6991b5a --- /dev/null +++ b/rules/xml/xpathi_node.yaml @@ -0,0 +1,57 @@ +rules: + - id: node_xpath_injection + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $XPATH.parse(<... "=~/^[\/\/].+/" + $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + $XPATH.parse(<... "=~/^[\/\/].+/" + $REQ.$PARAM ...>, ...) + - pattern: | + $XPATH.parse(<... "=~/^[\/\/].+/" + $REQ.$PARAM["..."] ...>, ...) + - pattern: | + $XPATH.parse(<... "=~/^[\/\/].+/" + $REQ.$PARAM("...") ...>, ...) + - pattern: | + $XPATH.parse(<... "=~/^[\/\/].+/" + $REQ["..."] ...>, ...) + - pattern: | + $XPATH.parse(<... "=~/^[\/\/].+/" + $REQ("...") ...>, ...) + - pattern: | + $INP = <... $REQ.$QUERY.$VAR ...>; + ... + $XPATH.parse(<... "=~/^[\/\/].+/" + $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$PARAM...>; + ... + $XPATH.parse(<... "=~/^[\/\/].+/" + $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$PARAM["..."] ...>; + ... + $XPATH.parse(<... "=~/^[\/\/].+/" + $INP ...>, ...); + - pattern: | + $INP = <... $REQ.$PARAM("...") ...>; + ... + $XPATH.parse(<... "=~/^[\/\/].+/" + $INP ...>, ...); + - pattern: | + $INP = <... $REQ["..."] ...>; + ... + $XPATH.parse(<... "=~/^[\/\/].+/" + $INP ...>, ...); + - pattern: | + $INP = <... $REQ("...") ...>; + ... + $XPATH.parse(<... "=~/^[\/\/].+/" + $INP ...>, ...); + message: >- + User controlled data in xpath.parse() can result in XPATH injection + vulnerability. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-643: Improper Neutralization of Data within XPath Expressions + ('XPath Injection') \ No newline at end of file diff --git a/rules/xml/xxe_expat.js b/rules/xml/xxe_expat.js new file mode 100644 index 0000000..3034bf6 --- /dev/null +++ b/rules/xml/xxe_expat.js @@ -0,0 +1,28 @@ +const express = require('express') +const app = express() +const port = 3000 +const expat = require('node-expat'); + +app.get('/test', async (req, res) => { + var parser = new expat.Parser('UTF-8') + // ruleid: xxe_expat + parser.parse(req.body) + res.send('Hello World!') +}) + +app.get('/test1', async (req, res) => { + var parser = new expat.Parser('UTF-8') + // ruleid: xxe_expat + parser.write(req.query.value) + res.send('Hello World!') +}) + +app.get('/test2', async (req, res) => { + var parser = new expat.Parser('UTF-8') + // ruleid: xxe_expat + var data = req.body.foo + parser.write(data) + res.send('Hello World!') +}) + +app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`)) \ No newline at end of file diff --git a/rules/xml/xxe_expat.yaml b/rules/xml/xxe_expat.yaml new file mode 100644 index 0000000..1f31635 --- /dev/null +++ b/rules/xml/xxe_expat.yaml @@ -0,0 +1,49 @@ +rules: + - id: xxe_expat + patterns: + - pattern-inside: | + require('node-expat'); + ... + - pattern-either: + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern-inside: | + $PARSER = new $EXPAT.Parser(...); + ... + - pattern-inside: | + $PARSER = new Parser(...); + ... + - pattern-either: + - pattern: '$PARSER.parse(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PARSER.parse(<... $REQ.$BODY ...>,...)' + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PARSER.parse(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $PARSER.parse(<... $INPUT ...>,...); + - pattern: '$PARSER.write(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$PARSER.write(<... $REQ.$BODY ...>,...)' + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $PARSER.write(<... $INPUT ...>,...); + - pattern: |- + $INPUT = <... $REQ.$BODY ...>; + ... + $PARSER.write(<... $INPUT ...>,...); + message: >- + Make sure that unverified user data can not reach the XML Parser, as it + can result in XML External or Internal Entity (XXE) Processing + vulnerabilities. + metadata: + owasp: 'A4: XML External Entities (XXE)' + cwe: 'CWE-611: Improper Restriction of XML External Entity Reference' + severity: ERROR + languages: + - javascript diff --git a/rules/xml/xxe_node.js b/rules/xml/xxe_node.js new file mode 100644 index 0000000..f440c82 --- /dev/null +++ b/rules/xml/xxe_node.js @@ -0,0 +1,57 @@ + +const libxmljs = require('libxmljs'); + +app.get('/noent', function (req, res) { + // entity expansion + // ruleid:node_xxe + libxmljs.parseXml(req.param("xml"), { noent: true }); +}); + + +app.get('/sax', function (req, res) { + // SAX parser expands external entities + // ruleid:node_xxe + const parser = new libxmljs.SaxParser(); + const x = 1 + parser.parseString(req.param("xml")); +}); + + +app.get('/saxpush/parser', function (req, res) { + // SAX parser expands external entities + // ruleid:node_xxe + const parser = new libxmljs.SaxPushParser(); + const x = 1 + parser.push(req.param("some-xml")); +}); + + +app.get('/sax', function (req, res) { + // SAX parser expands external entities + const parser = new libxmljs.SaxParser(); + const x = 1 + // ruleid:node_xxe + var products = parser.parseXmlString(req.files.products.data, { noent: true, noblanks: true }) +}) + +const express = require('express') +const libxmljs = require('libxml') +const db = require('db'); +const router = express.Router() + +router.post('/upload-products', (req, res) => { + // ruleid:node_xxe + const XMLfile = req.files.products.data; + const products = libxmljs.parseXmlString(XMLfile, { noent: true, noblanks: true }) + + products.root().childNodes().forEach(product => { + let newProduct = new db.Product() + newProduct.name = product.childNodes()[0].text() + newProduct.description = product.childNodes()[3].text() + newProduct.save() + }); + + res.send('Thanks') +}) + +module.exports = router \ No newline at end of file diff --git a/rules/xml/xxe_node.yaml b/rules/xml/xxe_node.yaml new file mode 100644 index 0000000..be7c3fa --- /dev/null +++ b/rules/xml/xxe_node.yaml @@ -0,0 +1,110 @@ +rules: + - id: node_xxe + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $LIBXML.parseXmlString(..., <... $REQ.$QUERY.$VAR.$FILE ...>, ...) + - pattern: | + $LIBXML.parseXmlString(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + $LIBXML.parseXmlString(..., <... $REQ.$QUERY ...>, ...) + - pattern: > + $FOO = <... $REQ.$QUERY.$VAR.$FILE ...>; ... + $LIBXML.parseXmlString(..., <... $FOO ...>, ...); + - pattern: > + $FOO = <... $REQ.$QUERY.$VAR ...>; ... $LIBXML.parseXmlString(..., + <... $FOO ...>, ...); + - pattern: > + $FOO = <... $REQ.$QUERY ...>; ... $LIBXML.parseXmlString(..., <... + $FOO ...>, ...); + - pattern: | + $LIBXML.parseXml(..., <... $REQ.$QUERY.$VAR.$FILE ...>, ...) + - pattern: | + $LIBXML.parseXml(..., <... $REQ.$QUERY.$VAR ...>, ...) + - pattern: | + $LIBXML.parseXml(..., <... $REQ.$QUERY ...>, ...) + - pattern: > + $FOO = <... $REQ.$QUERY.$VAR.$FILE ...>; ... $LIBXML.parseXml(..., + <... $FOO ...>, ...); + - pattern: > + $FOO = <... $REQ.$QUERY.$VAR ...>; ... $LIBXML.parseXml(..., <... + $FOO ...>, ...); + - pattern: | + $FOO = <... $REQ.$QUERY ...>; + ... + $LIBXML.parseXml(..., <... $FOO ...>, ...); + - pattern: | + $PARSER = new libxmljs.SaxParser(); + ... + $PARSER.parseString(..., <... $REQ.$QUERY ...>, ...); + - pattern: | + $PARSER = new libxmljs.SaxParser(); + ... + $PARSER.parseString(..., <... $REQ.$QUERY.$BAR ...>, ...); + - pattern: | + $PARSER = new libxmljs.SaxParser(); + ... + $PARSER.parseString(..., <... $REQ.$QUERY.$BAR.$FILE ...>, ...); + - pattern: | + $PARSER = new libxmljs.SaxPushParser(); + ... + $PARSER.push(..., <... $REQ.$QUERY ...>, ...); + - pattern: | + $PARSER = new libxmljs.SaxPushParser(); + ... + $PARSER.push(..., <... $REQ.$QUERY.$FOO ...> , ...); + - pattern: | + $PARSER = new libxmljs.SaxPushParser(); + ... + $PARSER.push(..., <... $REQ.$QUERY.$FOO.$FILE ...> , ...); + - pattern: | + $PARSER = new libxmljs.SaxParser(); + ... + $FOO = <... $REQ.$QUERY ...>; + ... + $PARSER.parseString(..., <... $FOO ...>, ...); + - pattern: | + $PARSER = new libxmljs.SaxParser(); + ... + $FOO = <... $REQ.$QUERY.$BAR ...>; + ... + $PARSER.parseString(..., <... $FOO ...>, ...); + - pattern: | + $PARSER = new libxmljs.SaxParser(); + ... + $FOO = <... $REQ.$QUERY.$BAR.$FILE ...>; + ... + $PARSER.parseString(..., <... $FOO ...>, ...); + - pattern: | + $PARSER = new libxmljs.SaxPushParser(); + ... + $FOO = <... $REQ.$QUERY ...>; + ... + $PARSER.push(..., <... $FOO ...>, ...); + - pattern: | + $PARSER = new libxmljs.SaxPushParser(); + ... + $FOO = <... $REQ.$QUERY.$BAR ...>; + ... + $PARSER.push(..., <... $FOO ...> , ...); + - pattern: | + $PARSER = new libxmljs.SaxPushParser(); + ... + $FOO = <... $REQ.$QUERY.$BAR.$FILE ...>; + ... + $PARSER.push(..., <... $FOO ...> , ...); + message: >- + User controlled data in XML parsers can result in XML External or Internal + Entity (XXE) Processing vulnerabilities + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A4: XML External Entities (XXE)' + cwe: 'CWE-611: Improper Restriction of XML External Entity Reference' \ No newline at end of file diff --git a/rules/xml/xxe_sax.js b/rules/xml/xxe_sax.js new file mode 100644 index 0000000..5b36fca --- /dev/null +++ b/rules/xml/xxe_sax.js @@ -0,0 +1,38 @@ +function test1() { + // ruleid: xxe_sax + var sax = require("sax"), + strict = false, + parser = sax.parser(strict); + + parser.onattribute = function (attr) { + doSmth(attr) + }; + + parser.ondoctype = function (dt) { + processDocType(dt) + } + + const xml = ` + ]> + &xxe;`; + + parser.write(xml).close(); +} + +function test2() { + // ruleid: xxe_sax + var saxStream = require("sax").createStream(strict, options) + + saxStream.on("opentag", function (node) { + // same object as above + }) + + saxStream.on("doctype", function (node) { + processType(node) + }) + + fs.createReadStream("file.xml") + .pipe(saxStream) + .pipe(fs.createWriteStream("file-copy.xml")) +} \ No newline at end of file diff --git a/rules/xml/xxe_sax.yaml b/rules/xml/xxe_sax.yaml new file mode 100644 index 0000000..90e07ca --- /dev/null +++ b/rules/xml/xxe_sax.yaml @@ -0,0 +1,23 @@ +rules: + - id: xxe_sax + pattern-either: + - pattern: | + require('sax'); + ... + $PARSER.ondoctype = ...; + - pattern: |- + require('sax'); + ... + $PARSER.on('doctype',...); + severity: WARNING + languages: + - javascript + message: >- + Use of 'ondoctype' in 'sax' library detected. By default, 'sax' + won't do anything with custom DTD entity definitions. If you're + implementing a custom DTD entity definition, be sure not to introduce + XML External Entity (XXE) vulnerabilities, or be absolutely sure that + external entities received from a trusted source while processing XML. + metadata: + owasp: 'A4: XML External Entities (XXE)' + cwe: 'CWE-611: Improper Restriction of XML External Entity Reference' diff --git a/rules/xml/xxe_xml2json.js b/rules/xml/xxe_xml2json.js new file mode 100644 index 0000000..f3ede9f --- /dev/null +++ b/rules/xml/xxe_xml2json.js @@ -0,0 +1,28 @@ +function test1() { + const express = require('express') + const xml2json = require('xml2json') + const app = express() + const port = 3000 + + app.get('/', (req, res) => { + // ruleid: xxe_xml2json + const xml = req.query.xml + const content = xml2json.toJson(xml, { coerce: true, object: true }); + }) + + app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`)) +} + +function test2() { + const express = require('express') + const xml2json = require('xml2json') + const app = express() + const port = 3000 + + app.get('/', (req, res) => { + // ruleid: xxe_xml2json + const content = xml2json.toJson(req.body, { coerce: true, object: true }); + }) + + app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`)) +} diff --git a/rules/xml/xxe_xml2json.yaml b/rules/xml/xxe_xml2json.yaml new file mode 100644 index 0000000..8a016a5 --- /dev/null +++ b/rules/xml/xxe_xml2json.yaml @@ -0,0 +1,32 @@ +rules: + - id: xxe_xml2json + patterns: + - pattern-inside: | + require('xml2json'); + ... + - pattern-either: + - pattern-inside: 'function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: '$X = function $FUNC($REQ, $RES, ...) {...}' + - pattern-inside: 'var $X = function $FUNC($REQ, $RES, ...) {...};' + - pattern-inside: '$APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})' + - pattern-either: + - pattern: '$EXPAT.toJson(<... $REQ.$QUERY.$FOO ...>,...)' + - pattern: '$EXPAT.toJson(<... $REQ.$BODY ...>,...)' + - pattern: | + $INPUT = <... $REQ.$QUERY.$FOO ...>; + ... + $EXPAT.toJson(<... $INPUT ...>,...); + - pattern: | + $INPUT = <... $REQ.$BODY ...>; + ... + $EXPAT.toJson(<... $INPUT ...>,...); + message: >- + Make sure that unverified user data can not reach the XML Parser, as it + can result in XML External or Internal Entity (XXE) Processing + vulnerabilities. + metadata: + owasp: 'A4: XML External Entities (XXE)' + cwe: 'CWE-611: Improper Restriction of XML External Entity Reference' + severity: ERROR + languages: + - javascript diff --git a/rules/xss/xss_mustache_escape.yaml b/rules/xss/xss_mustache_escape.yaml new file mode 100644 index 0000000..0d659e4 --- /dev/null +++ b/rules/xss/xss_mustache_escape.yaml @@ -0,0 +1,12 @@ +rules: + - id: xss_disable_mustache_escape + pattern: $OBJ.escapeMarkup = false + severity: WARNING + languages: + - javascript + metadata: + cwe: 'CWE-116: Improper Encoding or Escaping of Output' + owasp: 'A7: Cross-Site Scripting XSS' + message: >- + Markup escaping disabled. This can be used with some template engines to + escape disabling of HTML entities, which can lead to XSS attacks. diff --git a/rules/xss/xss_mustache_escape_disabled.js b/rules/xss/xss_mustache_escape_disabled.js new file mode 100644 index 0000000..2a09177 --- /dev/null +++ b/rules/xss/xss_mustache_escape_disabled.js @@ -0,0 +1,2 @@ +// ruleid:xss_disable_mustache_escape +a.escapeMarkup = false; \ No newline at end of file diff --git a/rules/xss/xss_node.js b/rules/xss/xss_node.js new file mode 100644 index 0000000..336848e --- /dev/null +++ b/rules/xss/xss_node.js @@ -0,0 +1,115 @@ + +const express = require('express') +const router = express.Router() + +router.get('/greeting', (req, res) => { + // ruleid:express_xss + const { name } = req.query; + res.send('

Hello :' + name + "

") +}) + +//template handle escaping +router.get('/greet-template', (req, res) => { + name = req.query.name + res.render('index', { user_name: name }); +}) + +module.exports = router + + +app.get('/', function (req, res) { + // ruleid:express_xss + var user = req.query.name; + + msg = "Hi " + user + res.send('Response
' + msg); +}); + + +var msg = ''; +app.get('/3', function (req, res) { + // ruleid:express_xss + var user = req.query.name; + + msg = "Hi " + user + res.send('Response
' + msg); +}); + +app.get('/2', function (req, res) { + // ruleid:express_xss + var user = { user: req.query.name }; + res.send('Response
' + user.name); +}); + +app.get('/1', function (req, res) { + // ruleid:express_xss + var user = req.query.name; + var msg = []; + msg.push(user); + res.send('Response
' + msg[0]); +}); + +app.get('/4', function (req, res) { + var user = req.query.name; + var header = ""; + var msg = 'Hi ' + user; + var footer = ""; + var output = header + msg + footer; + res.send(output); +}); + + + + + +var express = require('express'); +var app = express(); +app.get('/', function (req, res) { + // ruleid:express_xss + var resp = req.query.name; + res.send('Response
' + resp); +}); +app.get('/3', function (req, res) { + // ruleid:express_xss + var resp = req.query.name; + res.write('Response
' + resp); +}); + +app.get('/3', function (req, res) { + // ruleid:express_xss + var resp = req.foo; + var x = 1; + res.write('Response
' + resp); +}); + +app.get('/xss', function (req, res) { + // ruleid:express_xss + var html = "ASadad" + req.query.name + "Asdadads" + res.write('Response
' + html); +}); +app.get('/xss', function (req, res) { + // ruleid:express_xss + res.write('Response
' + req.query('doo')); +}); +app.get('/xss', function (req, res) { + // ruleid:express_xss + res.write('Response
' + req.query.name); +}); + +app.get('/noxss', function (req, res) { + var resp = req.query.name; + res.write('Response
'); +}); + +app.get('/noxs2s', function (req, res) { + var resp = req.query.name; + res.write('Response
' + foo); +}); + +app.get('/xss', function (req, res) { + // ruleid:express_xss + var resp = req.query.name; + var html = "ASadad" + resp + "Asdadads" + res.write('Response
' + html); +}); +app.listen(8000); \ No newline at end of file diff --git a/rules/xss/xss_node.yaml b/rules/xss/xss_node.yaml new file mode 100644 index 0000000..5470613 --- /dev/null +++ b/rules/xss/xss_node.yaml @@ -0,0 +1,137 @@ +rules: + - id: express_xss + patterns: + - pattern-either: + - pattern-inside: function ($REQ, $RES, ...) {...} + - pattern-inside: function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...} + - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...}; + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) + - pattern-either: + - pattern: | + $RES.write(..., <... $REQ.$QUERY ...>, ...); + - pattern: | + $RES.write(..., <... $REQ.$QUERY.$FOO ...>, ...); + - pattern: | + $RES.send(..., <... $REQ.$QUERY ...>, ...); + - pattern: | + $RES.send(..., <... $REQ.$QUERY.$FOO ...>, ...); + - pattern: | + $LOCALVAR = <... $REQ.$QUERY ...>; + ... + $RES.write(..., <... $LOCALVAR ...>, ...); + - pattern: | + $LOCALVAR = <... $REQ.$QUERY.$FOO ...>; + ... + $RES.write(..., <... $LOCALVAR ...>, ...); + - pattern: | + $LOCALVAR = <... $REQ.$QUERY.$VAR ...>; + ... + $RES.send(..., <... $LOCALVAR ...>, ...); + - pattern: | + $LOCALVAR = <... $REQ.$QUERY ...>; + ... + $RES.send(..., <... $LOCALVAR ...>, ...); + - pattern: | + var {$LOCALVAR} = <... $REQ.$QUERY.$FOO ...>; + ... + $RES.write(..., <... $LOCALVAR ...>, ...); + - pattern: | + var {$LOCALVAR} = <... $REQ.$QUERY.$VAR ...>; + ... + $RES.send(..., <... $LOCALVAR ...>, ...); + - pattern: | + var {$LOCALVAR} = <... $REQ.$QUERY ...>; + ... + $RES.send(..., <... $LOCALVAR ...>, ...); + - pattern: | + $LOCALVAR = {$KEY: <... $REQ.$QUERY ...>}; + ... + $RES.write(..., <... $LOCALVAR ...>, ...); + - pattern: | + $LOCALVAR = {$KEY: <... $REQ.$QUERY.$FOO ...>}; + ... + $RES.write(..., <... $LOCALVAR ...>, ...); + - pattern: | + $LOCALVAR = {$KEY: <... $REQ.$QUERY.$VAR ...>}; + ... + $RES.send(..., <... $LOCALVAR ...>, ...); + - pattern: | + $LOCALVAR = {$KEY: <... $REQ.$QUERY ...>}; + ... + $RES.send(..., <... $LOCALVAR ...>, ...); + - pattern: | + $LOCALVAR.push(<... $REQ.$QUERY ...>); + ... + $RES.write(..., <... $LOCALVAR ...>, ...); + - pattern: | + $LOCALVAR.push(<... $REQ.$QUERY.$FOO ...>); + ... + $RES.write(..., <... $LOCALVAR ...>, ...); + - pattern: | + $LOCALVAR.push(<... $REQ.$QUERY.$VAR ...>); + ... + $RES.send(..., <... $LOCALVAR ...>, ...); + - pattern: | + $LOCALVAR.push(<... $REQ.$QUERY ...>); + ... + $RES.send(..., <... $LOCALVAR ...>, ...); + - pattern: | + $LOCALVAR = <... $REQ.$QUERY ...>; + ... + $ARR.push(<...$LOCALVAR...>); + ... + $RES.write(..., <... $ARR ...>, ...); + - pattern: | + $LOCALVAR = <... $REQ.$QUERY.$FOO ...>; + ... + $ARR.push(<...$LOCALVAR...>); + ... + $RES.write(..., <... $ARR ...>, ...); + - pattern: | + $LOCALVAR = <... $REQ.$QUERY.$VAR ...>; + ... + $ARR.push(<...$LOCALVAR...>); + ... + $RES.send(..., <... $ARR ...>, ...); + - pattern: | + $LOCALVAR = <... $REQ.$QUERY ...>; + ... + $ARR.push(<...$LOCALVAR...>); + ... + $RES.send(..., <... $ARR ...>, ...); + - pattern: | + $LOCALVAR = <... $REQ.$QUERY ...>; + ... + $OUT = <... $LOCALVAR ...>; + ... + $RES.write(..., <... $OUT ...>, ...); + - pattern: | + $LOCALVAR = <... $REQ.$QUERY.$FOO ...>; + ... + $OUT = <... $LOCALVAR ...>; + ... + $RES.write(..., <... $OUT ...>, ...); + - pattern: | + $LOCALVAR = <... $REQ.$QUERY.$VAR ...>; + ... + $OUT = <... $LOCALVAR ...>; + ... + $RES.send(..., <... $OUT ...>, ...); + - pattern: | + $LOCALVAR = <... $REQ.$QUERY ...>; + ... + $OUT = <... $LOCALVAR ...>; + ... + $RES.send(..., <... $OUT ...>, ...); + message: >- + Untrusted User Input in Response will result in Reflected Cross Site + Scripting Vulnerability. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-79: Improper Neutralization of Input During Web Page Generation + ('Cross-site Scripting') \ No newline at end of file diff --git a/rules/xss/xss_serialize_js.yaml b/rules/xss/xss_serialize_js.yaml new file mode 100644 index 0000000..12f5d31 --- /dev/null +++ b/rules/xss/xss_serialize_js.yaml @@ -0,0 +1,22 @@ +rules: + - id: xss_serialize_javascript + patterns: + - pattern-inside: | + ... + $S = require('serialize-javascript'); + ... + - pattern-not-inside: escape(...) + - pattern-not-inside: encodeURI(...) + - pattern: | + $S(..., {unsafe: true}); + message: >- + Untrusted user input reaching `serialize-javascript` with `unsafe` + attribute can cause Cross Site Scripting (XSS). + severity: WARNING + languages: + - javascript + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-80: Improper Neutralization of Script-Related HTML Tags in a Web + Page diff --git a/rules/xss/xss_serializejs.js b/rules/xss/xss_serializejs.js new file mode 100644 index 0000000..4f6abda --- /dev/null +++ b/rules/xss/xss_serializejs.js @@ -0,0 +1,13 @@ +var serialize = require('serialize-javascript'); + +function test(userInput) { + // ruleid: xss_serialize_javascript + const result = serialize({ foo: userInput }, { unsafe: true, space: 2 }) + return result +} + +function test2() { + // ruleid: xss_serialize_javascript + const result = serialize({ foo: '' }, { unsafe: true, space: 2 }) + return result +} diff --git a/rules/xss/xss_templates.js b/rules/xss/xss_templates.js new file mode 100644 index 0000000..ccc132a --- /dev/null +++ b/rules/xss/xss_templates.js @@ -0,0 +1,25 @@ +function name() { + var x = '

hell0

' + // ruleid:handlebars_safestring + var y = new Handlebars.SafeString(x); + // ruleid:handlebars_safestring + return new Handlebars.SafeString(''); +} + +function test2() { + var x = 'foooo' + var z = new Handlebars; + // ruleid:handlebars_safestring + var xx = z.SafeString(x) + return xx; +} + + +// ruleid:handlebars_noescape +var template = Handlebars.compile(source, { noEscape: true }); +var template = "This is {{target}}"; +var target = "user's pictures"; +// ruleid:handlebars_noescape +var result = Handlerbars.compile(template, { noEscape: true })({ target: target }); +// ruleid:squirrelly_autoescape +Sqrl.autoEscaping(false) \ No newline at end of file diff --git a/rules/xss/xss_templates.yaml b/rules/xss/xss_templates.yaml new file mode 100644 index 0000000..ef6b5d5 --- /dev/null +++ b/rules/xss/xss_templates.yaml @@ -0,0 +1,44 @@ +rules: + - id: handlebars_safestring + pattern-either: + - pattern: $X.SafeString(...) + - pattern: new Handlebars.SafeString(...) + message: >- + Handlebars SafeString will not escape the data passed through it. + Untrusted user input passing through SafeString can cause XSS. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-79: Improper Neutralization of Input During Web Page Generation + ('Cross-site Scripting') + - id: handlebars_noescape + patterns: + - pattern: | + $X.compile(..., {noEscape: true}, ...) + message: >- + Disabling Escaping in Handlebars is not a secure behaviour. This can + introduce XSS vulnerabilties. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-80: Improper Neutralization of Script-Related HTML Tags in a Web + Page (Basic XSS) + - id: squirrelly_autoescape + pattern: $X.autoEscaping(false) + message: >- + Handlebars SafeString will not escape the data passed through it. + Untrusted user input passing through SafeString can cause XSS. + languages: + - javascript + severity: ERROR + metadata: + owasp: 'A1: Injection' + cwe: >- + CWE-79: Improper Neutralization of Input During Web Page Generation + ('Cross-site Scripting') \ No newline at end of file From 45d276eea4cd71394aa3b2a074f27f866f8125e1 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Sat, 26 Sep 2020 22:22:14 -0400 Subject: [PATCH 3/3] rule file rename --- ...{crypto_timing_attacks.js => timing_attack_node.js} | 0 rules/database/{sqli_node.js => sql_injection.js} | 0 .../dos/{regex_injection_dos.js => regex_injection.js} | 0 .../{security_electron.js => security_electronjs.js} | 0 ...{eval_grpc_insecure.js => eval_grpc_deserialize.js} | 0 ...rpc_deserialize.yaml => eval_grpc_deserialize.yaml} | 0 .../{error_info_disclosure.js => error_disclosure.js} | 0 ...logic_user_controlled_checks.js => logic_bypass.js} | 0 rules/good/header_helmet_disabled.js | 10 ---------- ...rdcoded_jwt_express.js => jwt_express_hardcoded.js} | 0 rules/jwt/{hardcoded_jwt.js => jwt_hardcoded.js} | 0 ...ersal_join_resolve.js => resolve_path_traversal.js} | 0 ...entity_expansion.js => xml_entity_expansion_dos.js} | 0 ...tache_escape_disabled.js => xss_mustache_escape.js} | 0 rules/xss/{xss_serializejs.js => xss_serialize_js.js} | 0 15 files changed, 10 deletions(-) rename rules/crypto/{crypto_timing_attacks.js => timing_attack_node.js} (100%) rename rules/database/{sqli_node.js => sql_injection.js} (100%) rename rules/dos/{regex_injection_dos.js => regex_injection.js} (100%) rename rules/electronjs/{security_electron.js => security_electronjs.js} (100%) rename rules/eval/{eval_grpc_insecure.js => eval_grpc_deserialize.js} (100%) rename rules/eval/{eval_drpc_deserialize.yaml => eval_grpc_deserialize.yaml} (100%) rename rules/generic/{error_info_disclosure.js => error_disclosure.js} (100%) rename rules/generic/{logic_user_controlled_checks.js => logic_bypass.js} (100%) delete mode 100644 rules/good/header_helmet_disabled.js rename rules/jwt/{hardcoded_jwt_express.js => jwt_express_hardcoded.js} (100%) rename rules/jwt/{hardcoded_jwt.js => jwt_hardcoded.js} (100%) rename rules/traversal/{path_traversal_join_resolve.js => resolve_path_traversal.js} (100%) rename rules/xml/{xml_entity_expansion.js => xml_entity_expansion_dos.js} (100%) rename rules/xss/{xss_mustache_escape_disabled.js => xss_mustache_escape.js} (100%) rename rules/xss/{xss_serializejs.js => xss_serialize_js.js} (100%) diff --git a/rules/crypto/crypto_timing_attacks.js b/rules/crypto/timing_attack_node.js similarity index 100% rename from rules/crypto/crypto_timing_attacks.js rename to rules/crypto/timing_attack_node.js diff --git a/rules/database/sqli_node.js b/rules/database/sql_injection.js similarity index 100% rename from rules/database/sqli_node.js rename to rules/database/sql_injection.js diff --git a/rules/dos/regex_injection_dos.js b/rules/dos/regex_injection.js similarity index 100% rename from rules/dos/regex_injection_dos.js rename to rules/dos/regex_injection.js diff --git a/rules/electronjs/security_electron.js b/rules/electronjs/security_electronjs.js similarity index 100% rename from rules/electronjs/security_electron.js rename to rules/electronjs/security_electronjs.js diff --git a/rules/eval/eval_grpc_insecure.js b/rules/eval/eval_grpc_deserialize.js similarity index 100% rename from rules/eval/eval_grpc_insecure.js rename to rules/eval/eval_grpc_deserialize.js diff --git a/rules/eval/eval_drpc_deserialize.yaml b/rules/eval/eval_grpc_deserialize.yaml similarity index 100% rename from rules/eval/eval_drpc_deserialize.yaml rename to rules/eval/eval_grpc_deserialize.yaml diff --git a/rules/generic/error_info_disclosure.js b/rules/generic/error_disclosure.js similarity index 100% rename from rules/generic/error_info_disclosure.js rename to rules/generic/error_disclosure.js diff --git a/rules/generic/logic_user_controlled_checks.js b/rules/generic/logic_bypass.js similarity index 100% rename from rules/generic/logic_user_controlled_checks.js rename to rules/generic/logic_bypass.js diff --git a/rules/good/header_helmet_disabled.js b/rules/good/header_helmet_disabled.js deleted file mode 100644 index 8ca4c9a..0000000 --- a/rules/good/header_helmet_disabled.js +++ /dev/null @@ -1,10 +0,0 @@ -// ruleid:helmet_feature_disabled -app.use(helmet({ - frameguard: false, -})) - - -// ruleid:helmet_feature_disabled -app.use(helmet({ - "xssFilter": false -})) \ No newline at end of file diff --git a/rules/jwt/hardcoded_jwt_express.js b/rules/jwt/jwt_express_hardcoded.js similarity index 100% rename from rules/jwt/hardcoded_jwt_express.js rename to rules/jwt/jwt_express_hardcoded.js diff --git a/rules/jwt/hardcoded_jwt.js b/rules/jwt/jwt_hardcoded.js similarity index 100% rename from rules/jwt/hardcoded_jwt.js rename to rules/jwt/jwt_hardcoded.js diff --git a/rules/traversal/path_traversal_join_resolve.js b/rules/traversal/resolve_path_traversal.js similarity index 100% rename from rules/traversal/path_traversal_join_resolve.js rename to rules/traversal/resolve_path_traversal.js diff --git a/rules/xml/xml_entity_expansion.js b/rules/xml/xml_entity_expansion_dos.js similarity index 100% rename from rules/xml/xml_entity_expansion.js rename to rules/xml/xml_entity_expansion_dos.js diff --git a/rules/xss/xss_mustache_escape_disabled.js b/rules/xss/xss_mustache_escape.js similarity index 100% rename from rules/xss/xss_mustache_escape_disabled.js rename to rules/xss/xss_mustache_escape.js diff --git a/rules/xss/xss_serializejs.js b/rules/xss/xss_serialize_js.js similarity index 100% rename from rules/xss/xss_serializejs.js rename to rules/xss/xss_serialize_js.js