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/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/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.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/crypto_node.js b/rules/crypto/crypto_node.js similarity index 100% rename from nodejsscan/crypto_node.js rename to rules/crypto/crypto_node.js diff --git a/nodejsscan/crypto_node.yaml b/rules/crypto/crypto_node.yaml similarity index 94% rename from nodejsscan/crypto_node.yaml rename to rules/crypto/crypto_node.yaml index f595b43..f021b65 100644 --- a/nodejsscan/crypto_node.yaml +++ b/rules/crypto/crypto_node.yaml @@ -29,9 +29,9 @@ rules: patterns: - pattern-either: - pattern: | - $X.createCipheriv("=~/aes-\([0-9]+\)-ecb/", ...) + $X.createCipheriv("=~/^aes-([0-9]+)-ecb$/", ...) - pattern: | - $X.createDecipheriv("=~/aes-\([0-9]+\)-ecb/", ...) + $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. diff --git a/nodejsscan/crypto_timing_attacks.js b/rules/crypto/timing_attack_node.js similarity index 100% rename from nodejsscan/crypto_timing_attacks.js rename to rules/crypto/timing_attack_node.js diff --git a/nodejsscan/timing_attack_node.yaml b/rules/crypto/timing_attack_node.yaml similarity index 100% rename from nodejsscan/timing_attack_node.yaml rename to rules/crypto/timing_attack_node.yaml diff --git a/nodejsscan/tls_node.js b/rules/crypto/tls_node.js similarity index 100% rename from nodejsscan/tls_node.js rename to rules/crypto/tls_node.js diff --git a/nodejsscan/tls_node.yaml b/rules/crypto/tls_node.yaml similarity index 92% rename from nodejsscan/tls_node.yaml rename to rules/crypto/tls_node.yaml index a22881e..dec2290 100644 --- a/nodejsscan/tls_node.yaml +++ b/rules/crypto/tls_node.yaml @@ -8,7 +8,7 @@ rules: $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. + accept self signed certificates and is not a secure behaviour. languages: - javascript severity: ERROR 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/nodejsscan/nosql_injection.js b/rules/database/nosql_injection.js similarity index 69% rename from nodejsscan/nosql_injection.js rename to rules/database/nosql_injection.js index 45c1293..7094656 100644 --- a/nodejsscan/nosql_injection.js +++ b/rules/database/nosql_injection.js @@ -38,7 +38,7 @@ User.find(query, function (err, users) { }); app.post('/foo', function (req, res) { -// ruleid:node_nosqli_js_injection + // ruleid:node_nosqli_js_injection var query = {}; query['$where'] = `this.email == '${req.body.email}'`; User.find(query, function (err, data) { @@ -50,32 +50,4 @@ app.post('/foo', function (req, res) { 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/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/nodejsscan/sqli_node.js b/rules/database/sql_injection.js similarity index 100% rename from nodejsscan/sqli_node.js rename to rules/database/sql_injection.js diff --git a/nodejsscan/sql_injection.yaml b/rules/database/sql_injection.yaml similarity index 100% rename from nodejsscan/sql_injection.yaml rename to rules/database/sql_injection.yaml diff --git a/nodejsscan/express_bodyparser_dos.js b/rules/dos/express_bodyparser_dos.js similarity index 100% rename from nodejsscan/express_bodyparser_dos.js rename to rules/dos/express_bodyparser_dos.js diff --git a/nodejsscan/express_bodyparser_dos.yaml b/rules/dos/express_bodyparser_dos.yaml similarity index 100% rename from nodejsscan/express_bodyparser_dos.yaml rename to rules/dos/express_bodyparser_dos.yaml diff --git a/nodejsscan/layer7_object_dos.js b/rules/dos/layer7_object_dos.js similarity index 100% rename from nodejsscan/layer7_object_dos.js rename to rules/dos/layer7_object_dos.js diff --git a/nodejsscan/layer7_object_dos.yaml b/rules/dos/layer7_object_dos.yaml similarity index 86% rename from nodejsscan/layer7_object_dos.yaml rename to rules/dos/layer7_object_dos.yaml index d077a6e..5d9699f 100644 --- a/nodejsscan/layer7_object_dos.yaml +++ b/rules/dos/layer7_object_dos.yaml @@ -14,11 +14,10 @@ rules: - pattern-inside: | $OBJ = $REQ.body.$FOO; ... - - pattern-either: - - pattern-inside: | - for(...){...}; - - pattern: | - $OBJ.length; + - pattern-inside: | + for(...){...}; + - pattern: | + $OBJ.length; message: Layer7 Denial of Service. Looping over user controlled objects can result in DoS. languages: - javascript diff --git a/nodejsscan/regex_dos.js b/rules/dos/regex_dos.js similarity index 100% rename from nodejsscan/regex_dos.js rename to rules/dos/regex_dos.js diff --git a/nodejsscan/regex_dos.yaml b/rules/dos/regex_dos.yaml similarity index 97% rename from nodejsscan/regex_dos.yaml rename to rules/dos/regex_dos.yaml index c8e6ee1..478fe9b 100644 --- a/nodejsscan/regex_dos.yaml +++ b/rules/dos/regex_dos.yaml @@ -64,4 +64,4 @@ rules: severity: WARNING metadata: owasp: 'A6: Security Misconfiguration' - cwe: 'CWE-185: Incorrect Regular Expression' + cwe: 'CWE-185: Incorrect Regular Expression' \ No newline at end of file diff --git a/nodejsscan/regex_injection_dos.js b/rules/dos/regex_injection.js similarity index 100% rename from nodejsscan/regex_injection_dos.js rename to rules/dos/regex_injection.js diff --git a/nodejsscan/regex_injection.yaml b/rules/dos/regex_injection.yaml similarity index 97% rename from nodejsscan/regex_injection.yaml rename to rules/dos/regex_injection.yaml index f970ab9..71df9f7 100644 --- a/nodejsscan/regex_injection.yaml +++ b/rules/dos/regex_injection.yaml @@ -64,4 +64,4 @@ rules: severity: ERROR metadata: owasp: 'A1: Injection' - cwe: 'CWE-400: Uncontrolled Resource Consumption' + cwe: 'CWE-400: Uncontrolled Resource Consumption' \ No newline at end of file diff --git a/nodejsscan/security_electron.js b/rules/electronjs/security_electronjs.js similarity index 100% rename from nodejsscan/security_electron.js rename to rules/electronjs/security_electronjs.js diff --git a/nodejsscan/security_electronjs.yaml b/rules/electronjs/security_electronjs.yaml similarity index 92% rename from nodejsscan/security_electronjs.yaml rename to rules/electronjs/security_electronjs.yaml index a80b9b8..7c3667f 100644 --- a/nodejsscan/security_electronjs.yaml +++ b/rules/electronjs/security_electronjs.yaml @@ -13,7 +13,7 @@ rules: - javascript severity: ERROR metadata: - owasp: A6 - Security Misconfiguration + owasp: 'A6: Security Misconfiguration' cwe: 'CWE-346: Origin Validation Error' - id: electron_allow_http patterns: @@ -30,7 +30,7 @@ rules: - javascript severity: ERROR metadata: - owasp: A6 - Security Misconfiguration + owasp: 'A6: Security Misconfiguration' cwe: 'CWE-319: Cleartext Transmission of Sensitive Information' - id: electron_blink_integration patterns: @@ -46,7 +46,7 @@ rules: - javascript severity: WARNING metadata: - owasp: A6 - Security Misconfiguration + owasp: 'A6: Security Misconfiguration' cwe: 'CWE-272: Least Privilege Violation' - id: electron_nodejs_integration patterns: @@ -63,7 +63,7 @@ rules: - javascript severity: WARNING metadata: - owasp: A6 - Security Misconfiguration + owasp: 'A6: Security Misconfiguration' cwe: 'CWE-272: Least Privilege Violation' - id: electron_context_isolation patterns: @@ -79,7 +79,7 @@ rules: - javascript severity: WARNING metadata: - owasp: A6 - Security Misconfiguration + owasp: 'A6: Security Misconfiguration' cwe: 'CWE-693: Protection Mechanism Failure' - id: electron_experimental_features patterns: @@ -95,5 +95,5 @@ rules: - javascript severity: WARNING metadata: - owasp: A6 - Security Misconfiguration + owasp: 'A6: Security Misconfiguration' cwe: 'CWE-272: Least Privilege Violation' diff --git a/nodejsscan/eval_deserialize.js b/rules/eval/eval_deserialize.js similarity index 95% rename from nodejsscan/eval_deserialize.js rename to rules/eval/eval_deserialize.js index cc9c476..9783851 100644 --- a/nodejsscan/eval_deserialize.js +++ b/rules/eval/eval_deserialize.js @@ -28,4 +28,4 @@ app.get('/', function (req, res) { }); app.listen(3000); // ruleid:serializetojs_deserialize -require('serialize-to-js').deserialize(str); +require('serialize-to-js').deserialize(str); \ No newline at end of file diff --git a/nodejsscan/eval_deserialize.yaml b/rules/eval/eval_deserialize.yaml similarity index 100% rename from nodejsscan/eval_deserialize.yaml rename to rules/eval/eval_deserialize.yaml diff --git a/rules/eval/eval_grpc_deserialize.js b/rules/eval/eval_grpc_deserialize.js new file mode 100644 index 0000000..185c099 --- /dev/null +++ b/rules/eval/eval_grpc_deserialize.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_grpc_deserialize.yaml b/rules/eval/eval_grpc_deserialize.yaml new file mode 100644 index 0000000..7baae09 --- /dev/null +++ b/rules/eval/eval_grpc_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/nodejsscan/eval_node.js b/rules/eval/eval_node.js similarity index 100% rename from nodejsscan/eval_node.js rename to rules/eval/eval_node.js diff --git a/nodejsscan/eval_node.yaml b/rules/eval/eval_node.yaml similarity index 100% rename from nodejsscan/eval_node.yaml rename to rules/eval/eval_node.yaml 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/nodejsscan/eval_yaml_deserialize.js b/rules/eval/eval_yaml_deserialize.js similarity index 100% rename from nodejsscan/eval_yaml_deserialize.js rename to rules/eval/eval_yaml_deserialize.js diff --git a/nodejsscan/eval_yaml_deserialize.yaml b/rules/eval/eval_yaml_deserialize.yaml similarity index 100% rename from nodejsscan/eval_yaml_deserialize.yaml rename to rules/eval/eval_yaml_deserialize.yaml diff --git a/nodejsscan/server_side_template_injection.js b/rules/eval/server_side_template_injection.js similarity index 100% rename from nodejsscan/server_side_template_injection.js rename to rules/eval/server_side_template_injection.js diff --git a/nodejsscan/server_side_template_injection.yaml b/rules/eval/server_side_template_injection.yaml similarity index 89% rename from nodejsscan/server_side_template_injection.yaml rename to rules/eval/server_side_template_injection.yaml index 193d72f..b65e6e2 100644 --- a/nodejsscan/server_side_template_injection.yaml +++ b/rules/eval/server_side_template_injection.yaml @@ -3,22 +3,22 @@ rules: patterns: - pattern-either: - pattern-inside: | - $HB = require('handlebars'); + require('handlebars'); ... - pattern-inside: | - $HB = require('pug'); + require('pug'); ... - pattern-inside: | - $HB = require('hamljs'); + require('hamljs'); ... - pattern-inside: | - $HB = require('ejs'); + require('ejs'); ... - pattern-inside: | - $HB = require('squirrelly'); + require('squirrelly'); ... - pattern-inside: | - $HB = require('eta'); + require('eta'); ... - pattern-either: - pattern-inside: function ($REQ, $RES, ...) {...} @@ -75,4 +75,4 @@ rules: severity: ERROR metadata: owasp: 'A1: Injection' - cwe: CWE-94 Improper Control of Generation of Code ('Code Injection') + cwe: 'CWE-94: Improper Control of Generation of Code (Code Injection)' \ No newline at end of file diff --git a/nodejsscan/exec_os_command.js b/rules/exec/exec_os_command.js similarity index 100% rename from nodejsscan/exec_os_command.js rename to rules/exec/exec_os_command.js diff --git a/nodejsscan/exec_os_command.yaml b/rules/exec/exec_os_command.yaml similarity index 97% rename from nodejsscan/exec_os_command.yaml rename to rules/exec/exec_os_command.yaml index b97b7b8..abe42f0 100644 --- a/nodejsscan/exec_os_command.yaml +++ b/rules/exec/exec_os_command.yaml @@ -2,7 +2,7 @@ rules: - id: generic_os_command_exec patterns: - pattern-inside: | - $EXEC = require('child_process'); + require('child_process'); ... - pattern-either: - pattern-inside: function ($REQ, $RES, ...) {...} @@ -88,4 +88,4 @@ rules: owasp: 'A1: Injection' cwe: >- CWE-78: Improper Neutralization of Special Elements used in an OS - Command ('OS Command Injection') + 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/nodejsscan/error_info_disclosure.js b/rules/generic/error_disclosure.js similarity index 100% rename from nodejsscan/error_info_disclosure.js rename to rules/generic/error_disclosure.js diff --git a/nodejsscan/error_disclosure.yaml b/rules/generic/error_disclosure.yaml similarity index 98% rename from nodejsscan/error_disclosure.yaml rename to rules/generic/error_disclosure.yaml index 846f9da..4a72faa 100644 --- a/nodejsscan/error_disclosure.yaml +++ b/rules/generic/error_disclosure.yaml @@ -48,4 +48,4 @@ rules: severity: WARNING metadata: owasp: 'A3: Sensitive Data Exposure' - cwe: 'CWE-209: Generation of Error Message Containing Sensitive Information' + cwe: 'CWE-209: Generation of Error Message Containing Sensitive Information' \ 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/nodejsscan/logic_user_controlled_checks.js b/rules/generic/logic_bypass.js similarity index 100% rename from nodejsscan/logic_user_controlled_checks.js rename to rules/generic/logic_bypass.js diff --git a/nodejsscan/logic_bypass.yaml b/rules/generic/logic_bypass.yaml similarity index 99% rename from nodejsscan/logic_bypass.yaml rename to rules/generic/logic_bypass.yaml index cc6ff5b..108cf39 100644 --- a/nodejsscan/logic_bypass.yaml +++ b/rules/generic/logic_bypass.yaml @@ -52,4 +52,4 @@ rules: severity: ERROR metadata: owasp: 'A5: Broken Access Control' - cwe: 'CWE-807: Reliance on Untrusted Inputs in a Security Decision' + cwe: 'CWE-807: Reliance on Untrusted Inputs in a Security Decision' \ No newline at end of file diff --git a/nodejsscan/good_anti_csrf.yaml b/rules/good/good_anti_csrf.yaml similarity index 100% rename from nodejsscan/good_anti_csrf.yaml rename to rules/good/good_anti_csrf.yaml diff --git a/nodejsscan/good_helmet_checks.yaml b/rules/good/good_helmet_checks.yaml similarity index 100% rename from nodejsscan/good_helmet_checks.yaml rename to rules/good/good_helmet_checks.yaml diff --git a/nodejsscan/good_ratelimiting.yaml b/rules/good/good_ratelimiting.yaml similarity index 100% rename from nodejsscan/good_ratelimiting.yaml rename to rules/good/good_ratelimiting.yaml 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/nodejsscan/header_cors_star.js b/rules/headers/header_cors_star.js similarity index 100% rename from nodejsscan/header_cors_star.js rename to rules/headers/header_cors_star.js diff --git a/nodejsscan/header_cors_star.yaml b/rules/headers/header_cors_star.yaml similarity index 68% rename from nodejsscan/header_cors_star.yaml rename to rules/headers/header_cors_star.yaml index 6fa49b9..6c9eb97 100644 --- a/nodejsscan/header_cors_star.yaml +++ b/rules/headers/header_cors_star.yaml @@ -1,4 +1,3 @@ -# 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: @@ -25,40 +24,40 @@ rules: - pattern: | $APP.options('*', cors(...)) - pattern: > - $RES.set("=~/[Access-Control-Allow-Origin|access-control-allow-origin]/", + $RES.set("=~/access-control-allow-origin/i", '*', ...) - pattern: > $RES.set(..., { - "=~/[Access-Control-Allow-Origin|access-control-allow-origin]/" : + "=~/access-control-allow-origin/i" : '*' }, ...) - pattern: > - $RES.header("=~/[Access-Control-Allow-Origin|access-control-allow-origin]/", + $RES.header("=~/access-control-allow-origin/i", '*', ...) - pattern: > $RES.writeHead(..., - {"=~/[Access-Control-Allow-Origin|access-control-allow-origin]/": + {"=~/access-control-allow-origin/i": '*' }, ...); - pattern: > $VAL = '*'; ... - $RES.set("=~/[Access-Control-Allow-Origin|access-control-allow-origin]/", + $RES.set("=~/access-control-allow-origin/i", $VAL, ...); - pattern: > $VAL = '*'; ... $RES.set(..., { - "=~/[Access-Control-Allow-Origin|access-control-allow-origin]/" : + "=~/access-control-allow-origin/i" : $VAL }, ...); - pattern: > $VAL = '*'; ... - $RES.header("=~/[Access-Control-Allow-Origin|access-control-allow-origin]/", + $RES.header("=~/access-control-allow-origin/i", $VAL, ...); - pattern: > $VAL = '*'; ... $RES.writeHead(..., - {"=~/[Access-Control-Allow-Origin|access-control-allow-origin]/": + {"=~/access-control-allow-origin/i": $VAL }, ...); message: >- Access-Control-Allow-Origin response header is set to "*". This will @@ -68,4 +67,4 @@ rules: severity: WARNING metadata: owasp: 'A6: Security Misconfiguration' - cwe: 'CWE-346: Origin Validation Error' + cwe: 'CWE-346: Origin Validation Error' \ No newline at end of file diff --git a/nodejsscan/header_helmet_disabled.js b/rules/headers/header_helmet_disabled.js similarity index 100% rename from nodejsscan/header_helmet_disabled.js rename to rules/headers/header_helmet_disabled.js diff --git a/nodejsscan/header_helmet_disabled.yaml b/rules/headers/header_helmet_disabled.yaml similarity index 100% rename from nodejsscan/header_helmet_disabled.yaml rename to rules/headers/header_helmet_disabled.yaml diff --git a/nodejsscan/header_injection.js b/rules/headers/header_injection.js similarity index 100% rename from nodejsscan/header_injection.js rename to rules/headers/header_injection.js diff --git a/nodejsscan/header_injection.yaml b/rules/headers/header_injection.yaml similarity index 99% rename from nodejsscan/header_injection.yaml rename to rules/headers/header_injection.yaml index 0e64f4f..0267cf4 100644 --- a/nodejsscan/header_injection.yaml +++ b/rules/headers/header_injection.yaml @@ -52,4 +52,4 @@ rules: severity: ERROR metadata: owasp: 'A1: Injection' - cwe: 'CWE-644: Improper Neutralization of HTTP Headers for Scripting Syntax' + cwe: 'CWE-644: Improper Neutralization of HTTP Headers for Scripting Syntax' \ No newline at end of file diff --git a/nodejsscan/header_xss_protection.js b/rules/headers/header_xss_protection.js similarity index 91% rename from nodejsscan/header_xss_protection.js rename to rules/headers/header_xss_protection.js index 4155fae..eb187f9 100644 --- a/nodejsscan/header_xss_protection.js +++ b/rules/headers/header_xss_protection.js @@ -37,14 +37,12 @@ app.get('/', function (req, res) { 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); diff --git a/nodejsscan/header_xss_protection.yaml b/rules/headers/header_xss_protection.yaml similarity index 71% rename from nodejsscan/header_xss_protection.yaml rename to rules/headers/header_xss_protection.yaml index 3be3c29..529e965 100644 --- a/nodejsscan/header_xss_protection.yaml +++ b/rules/headers/header_xss_protection.yaml @@ -30,32 +30,32 @@ rules: - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - pattern-either: - pattern: | - $RES.header("=~/[X-XSS-Protection|x-xss-protection]/", 0, ...) + $RES.header("=~/x-xss-protection/i", 0, ...) - pattern: | - $RES.set("=~/[X-XSS-Protection|x-xss-protection]/", 0, ...) + $RES.set("=~/x-xss-protection/i", 0, ...) - pattern: > - $RES.set(..., { "=~/[X-XSS-Protection|x-xss-protection]/" : 0 }, + $RES.set(..., { "=~/x-xss-protection/i" : 0 }, ...) - pattern: > - $RES.writeHead(..., {"=~/[X-XSS-Protection|x-xss-protection]/": 0 + $RES.writeHead(..., {"=~/x-xss-protection/i": 0 }, ...); - pattern: | $VAL = 0; ... - $RES.header("=~/[X-XSS-Protection|x-xss-protection]/", $VAL, ...); + $RES.header("=~/x-xss-protection/i", $VAL, ...); - pattern: | $VAL = 0; ... - $RES.set("=~/[X-XSS-Protection|x-xss-protection]/", $VAL, ...); + $RES.set("=~/x-xss-protection/i", $VAL, ...); - pattern: > $VAL = 0; ... - $RES.set(..., { "=~/[X-XSS-Protection|x-xss-protection]/" : $VAL + $RES.set(..., { "=~/x-xss-protection/i" : $VAL }, ...); - pattern: > $VAL = 0; ... - $RES.writeHead(..., {"=~/[X-XSS-Protection|x-xss-protection]/": + $RES.writeHead(..., {"=~/x-xss-protection/i": $VAL }, ...); message: >- X-XSS-Protection header is set to 0. This will disable the browser's XSS @@ -65,4 +65,4 @@ rules: severity: ERROR metadata: owasp: 'A6: Security Misconfiguration' - cwe: 'CWE-693: Protection Mechanism Failure' + cwe: 'CWE-693: Protection Mechanism Failure' \ No newline at end of file diff --git a/nodejsscan/host_header_injection.js b/rules/headers/host_header_injection.js similarity index 100% rename from nodejsscan/host_header_injection.js rename to rules/headers/host_header_injection.js diff --git a/nodejsscan/host_header_injection.yaml b/rules/headers/host_header_injection.yaml similarity index 97% rename from nodejsscan/host_header_injection.yaml rename to rules/headers/host_header_injection.yaml index 3afde67..3230f70 100644 --- a/nodejsscan/host_header_injection.yaml +++ b/rules/headers/host_header_injection.yaml @@ -52,4 +52,4 @@ rules: severity: ERROR metadata: owasp: 'A1: Injection' - cwe: 'CWE-20: Improper Input Validation' + cwe: 'CWE-20: Improper Input Validation' \ 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.js b/rules/jwt/jwt_express_hardcoded.js new file mode 100644 index 0000000..a3bdf06 --- /dev/null +++ b/rules/jwt/jwt_express_hardcoded.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_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/nodejsscan/hardcoded_jwt.js b/rules/jwt/jwt_hardcoded.js similarity index 100% rename from nodejsscan/hardcoded_jwt.js rename to rules/jwt/jwt_hardcoded.js diff --git a/nodejsscan/hardcoded_jwt.yaml b/rules/jwt/jwt_hardcoded.yaml similarity index 95% rename from nodejsscan/hardcoded_jwt.yaml rename to rules/jwt/jwt_hardcoded.yaml index f398378..4324fc3 100644 --- a/nodejsscan/hardcoded_jwt.yaml +++ b/rules/jwt/jwt_hardcoded.yaml @@ -64,7 +64,8 @@ rules: $SECRET = "..."; ... $JOSE.JWT.verify($P, $JOSE.JWK.asKey($SECRET), ...); - message: Hardcoded JWT secret was found + message: Hardcoded JWT secret was found. Store it properly in + an environment variable. languages: - javascript severity: ERROR diff --git a/nodejsscan/jwt_none_algorithm.js b/rules/jwt/jwt_none_algorithm.js similarity index 100% rename from nodejsscan/jwt_none_algorithm.js rename to rules/jwt/jwt_none_algorithm.js diff --git a/nodejsscan/jwt_none_algorithm.yaml b/rules/jwt/jwt_none_algorithm.yaml similarity index 100% rename from nodejsscan/jwt_none_algorithm.yaml rename to rules/jwt/jwt_none_algorithm.yaml 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/nodejsscan/open_redirect.js b/rules/redirect/open_redirect.js similarity index 100% rename from nodejsscan/open_redirect.js rename to rules/redirect/open_redirect.js diff --git a/nodejsscan/open_redirect.yaml b/rules/redirect/open_redirect.yaml similarity index 71% rename from nodejsscan/open_redirect.yaml rename to rules/redirect/open_redirect.yaml index efc83d1..0780523 100644 --- a/nodejsscan/open_redirect.yaml +++ b/rules/redirect/open_redirect.yaml @@ -36,41 +36,41 @@ rules: $APP.$METHOD(..., function $FUNC($REQ, $RES) { ... }) - pattern-either: - pattern: | - $RES.header(..., "=~/[Ll]+ocation/", <... $REQ.$VAR ...>, ...) + $RES.header(..., "=~/location/i", <... $REQ.$VAR ...>, ...) - pattern: | - $RES.header(..., "=~/[Ll]+ocation/", <... $REQ.$VAR.$VARR ...>, ...) + $RES.header(..., "=~/location/i", <... $REQ.$VAR.$VARR ...>, ...) - pattern: | - $RES.writeHead(..., "=~/[Ll]+ocation/", <... $REQ.$VAR ...>, ...) + $RES.writeHead(..., "=~/location/i", <... $REQ.$VAR ...>, ...) - pattern: | - $RES.writeHead(..., "=~/[Ll]+ocation/", <... $REQ.$VAR.$VARR ...>, ...) + $RES.writeHead(..., "=~/location/i", <... $REQ.$VAR.$VARR ...>, ...) - pattern: | - $RES.writeHead(..., {"=~/[Ll]+ocation/": <... $REQ.$VAR ...> }, ...) + $RES.writeHead(..., {"=~/location/i": <... $REQ.$VAR ...> }, ...) - pattern: | - $RES.writeHead(..., {"=~/[Ll]+ocation/": <... $REQ.$VAR.$VARR ...> }, ...) + $RES.writeHead(..., {"=~/location/i": <... $REQ.$VAR.$VARR ...> }, ...) - pattern: | $INP = <... $REQ.$VAR ...>; ... - $RES.header(..., "=~/[Ll]+ocation/", <... $INP ...>, ...); + $RES.header(..., "=~/location/i", <... $INP ...>, ...); - pattern: | $INP = <... $REQ.$VAR.$VARR ...>; ... - $RES.header(..., "=~/[Ll]+ocation/", <... $INP ...>, ...); + $RES.header(..., "=~/location/i", <... $INP ...>, ...); - pattern: | $INP = <... $REQ.$VAR ...>; ... - $RES.writeHead(..., "=~/[Ll]+ocation/", <... $INP ...>, ...); + $RES.writeHead(..., "=~/location/i", <... $INP ...>, ...); - pattern: | $INP = <... $REQ.$VAR.$VARR ...>; ... - $RES.writeHead(..., "=~/[Ll]+ocation/", <... $INP ...>, ...); + $RES.writeHead(..., "=~/location/i", <... $INP ...>, ...); - pattern: | $INP = <... $REQ.$VAR ...>; ... - $RES.writeHead(..., {"=~/[Ll]+ocation/": <... $INP ...> }, ...); + $RES.writeHead(..., {"=~/location/i": <... $INP ...> }, ...); - pattern: | $INP = <... $REQ.$VAR.$VARR ...>; ... - $RES.writeHead(..., {"=~/[Ll]+ocation/": <... $INP ...> }, ...); + $RES.writeHead(..., {"=~/location/i": <... $INP ...> }, ...); message: >- Untrusted user input in response header('Location') can result in Open Redirect vulnerability. @@ -80,4 +80,4 @@ rules: metadata: owasp: 'A1: Injection' cwe: >- - CWE-601: URL Redirection to Untrusted Site ('Open Redirect') + CWE-601: URL Redirection to Untrusted Site ('Open Redirect') \ No newline at end of file diff --git a/nodejsscan/ssrf_node.js b/rules/ssrf/ssrf_node.js similarity index 100% rename from nodejsscan/ssrf_node.js rename to rules/ssrf/ssrf_node.js diff --git a/nodejsscan/ssrf_node.yaml b/rules/ssrf/ssrf_node.yaml similarity index 88% rename from nodejsscan/ssrf_node.yaml rename to rules/ssrf/ssrf_node.yaml index 8b52cb8..55be1dd 100644 --- a/nodejsscan/ssrf_node.yaml +++ b/rules/ssrf/ssrf_node.yaml @@ -3,31 +3,31 @@ rules: patterns: - pattern-either: - pattern-inside: | - $PKG = require('request'); + require('request'); ... - pattern-inside: | - $PKG = require('axios'); + require('axios'); ... - pattern-inside: | - $PKG = require('needle'); + require('needle'); ... - pattern-inside: | - $PKG = require('bent'); + require('bent'); ... - pattern-inside: | - $PKG = require('urllib'); + require('urllib'); ... - pattern-inside: | - $PKG = require('net'); + require('net'); ... - pattern-inside: | - $PKG = require('https'); + require('https'); ... - pattern-inside: | - $PKG = require('superagent'); + require('superagent'); ... - pattern-inside: | - $PKG = require('got'); + require('got'); ... - pattern-either: - pattern-inside: function ($REQ, $RES, ...) {...} @@ -49,9 +49,9 @@ rules: - pattern: | $PKG.put(<... $REQ.$VAR.$FOO ...>, ...) - pattern: | - needle("=~/[get|post|put|GET|POST|PUT]+/", <... $REQ.$VAR.$FOO ...>, ...) + needle("=~/^[get|post|put]+$/i", <... $REQ.$VAR.$FOO ...>, ...) - pattern: | - needle("=~/[get|post|put|GET|POST|PUT]+/", <... $REQ.$VAR ...>, ...) + needle("=~/^[get|post|put]+$/i", <... $REQ.$VAR ...>, ...) - pattern: | request(<... $REQ.$VAR ...>, ...) - pattern: | @@ -111,11 +111,11 @@ rules: - pattern: | $INP = <... $REQ.$VAR.$FOO ...>; ... - needle("=~/[get|post|put|GET|POST|PUT]+/", <... $INP ...>, ...); + needle("=~/^[get|post|put]+$/i", <... $INP ...>, ...); - pattern: | $INP = <... $REQ.$VAR ...>; ... - needle("=~/[get|post|put|GET|POST|PUT]+/", <... $INP ...>, ...); + needle("=~/^[get|post|put]+$/i", <... $INP ...>, ...); - pattern: | $INP = <... $REQ.$VAR ...>; ... @@ -188,4 +188,4 @@ rules: severity: ERROR metadata: owasp: 'A1: Injection' - cwe: 'CWE-918: Server-Side Request Forgery (SSRF)' + 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/nodejsscan/archive_path_overwrite.js b/rules/traversal/archive_path_overwrite.js similarity index 100% rename from nodejsscan/archive_path_overwrite.js rename to rules/traversal/archive_path_overwrite.js diff --git a/nodejsscan/archive_path_overwrite.yaml b/rules/traversal/archive_path_overwrite.yaml similarity index 100% rename from nodejsscan/archive_path_overwrite.yaml rename to rules/traversal/archive_path_overwrite.yaml diff --git a/nodejsscan/path_traversal.js b/rules/traversal/path_traversal.js similarity index 89% rename from nodejsscan/path_traversal.js rename to rules/traversal/path_traversal.js index 58f7561..d5a085f 100644 --- a/nodejsscan/path_traversal.js +++ b/rules/traversal/path_traversal.js @@ -30,11 +30,11 @@ app.get('/foo', function (req, res) { var downloadFileName = 'log_' + fileName + '.txt'; fs.readFileAsync(fileName) - .then(function(data) { - res.download(fileName, downloadFileName); - }) + .then(function (data) { + res.download(fileName, downloadFileName); + }) }) app.listen(8888); // do not match -fileSystem.readFile(ddd); +fileSystem.readFile(ddd); \ No newline at end of file diff --git a/nodejsscan/path_traversal.yaml b/rules/traversal/path_traversal.yaml similarity index 96% rename from nodejsscan/path_traversal.yaml rename to rules/traversal/path_traversal.yaml index 3b33c7a..5a0a8cf 100644 --- a/nodejsscan/path_traversal.yaml +++ b/rules/traversal/path_traversal.yaml @@ -3,16 +3,16 @@ rules: patterns: - pattern-either: - pattern-inside: | - $HTTP = require('http'); + require('http'); ... - pattern-inside: | - $EXPRESS = require('express'); + require('express'); ... - pattern-inside: | - $KOA = require('koa'); + require('koa'); ... - pattern-inside: | - $ELEC = require('electron'); + require('electron'); ... - pattern-either: - pattern-inside: function ($REQ, $RES, ...) {...} diff --git a/rules/traversal/resolve_path_traversal.js b/rules/traversal/resolve_path_traversal.js new file mode 100644 index 0000000..b7d5f58 --- /dev/null +++ b/rules/traversal/resolve_path_traversal.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/nodejsscan/xml_entity_expansion.js b/rules/xml/xml_entity_expansion_dos.js similarity index 100% rename from nodejsscan/xml_entity_expansion.js rename to rules/xml/xml_entity_expansion_dos.js diff --git a/nodejsscan/xml_entity_expansion_dos.yaml b/rules/xml/xml_entity_expansion_dos.yaml similarity index 96% rename from nodejsscan/xml_entity_expansion_dos.yaml rename to rules/xml/xml_entity_expansion_dos.yaml index f095bdc..7c853de 100644 --- a/nodejsscan/xml_entity_expansion_dos.yaml +++ b/rules/xml/xml_entity_expansion_dos.yaml @@ -28,4 +28,4 @@ rules: severity: ERROR metadata: owasp: 'A4: XML External Entities (XXE)' - cwe: "CWE-776: Improper Restriction of Recursive Entity References in DTDs ('XML Entity Expansion')" + cwe: "CWE-776: Improper Restriction of Recursive Entity References in DTDs ('XML Entity Expansion')" \ No newline at end of file diff --git a/nodejsscan/xpathi_node.js b/rules/xml/xpathi_node.js similarity index 100% rename from nodejsscan/xpathi_node.js rename to rules/xml/xpathi_node.js diff --git a/nodejsscan/xpathi_node.yaml b/rules/xml/xpathi_node.yaml similarity index 61% rename from nodejsscan/xpathi_node.yaml rename to rules/xml/xpathi_node.yaml index 31ecbfe..6991b5a 100644 --- a/nodejsscan/xpathi_node.yaml +++ b/rules/xml/xpathi_node.yaml @@ -9,41 +9,41 @@ rules: - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...}) - pattern-either: - pattern: | - $XPATH.parse(<... "=~/[\/\/].+/" + $REQ.$QUERY.$VAR ...>, ...) + $XPATH.parse(<... "=~/^[\/\/].+/" + $REQ.$QUERY.$VAR ...>, ...) - pattern: | - $XPATH.parse(<... "=~/[\/\/].+/" + $REQ.$PARAM ...>, ...) + $XPATH.parse(<... "=~/^[\/\/].+/" + $REQ.$PARAM ...>, ...) - pattern: | - $XPATH.parse(<... "=~/[\/\/].+/" + $REQ.$PARAM["..."] ...>, ...) + $XPATH.parse(<... "=~/^[\/\/].+/" + $REQ.$PARAM["..."] ...>, ...) - pattern: | - $XPATH.parse(<... "=~/[\/\/].+/" + $REQ.$PARAM("...") ...>, ...) + $XPATH.parse(<... "=~/^[\/\/].+/" + $REQ.$PARAM("...") ...>, ...) - pattern: | - $XPATH.parse(<... "=~/[\/\/].+/" + $REQ["..."] ...>, ...) + $XPATH.parse(<... "=~/^[\/\/].+/" + $REQ["..."] ...>, ...) - pattern: | - $XPATH.parse(<... "=~/[\/\/].+/" + $REQ("...") ...>, ...) + $XPATH.parse(<... "=~/^[\/\/].+/" + $REQ("...") ...>, ...) - pattern: | $INP = <... $REQ.$QUERY.$VAR ...>; ... - $XPATH.parse(<... "=~/[\/\/].+/" + $INP ...>, ...); + $XPATH.parse(<... "=~/^[\/\/].+/" + $INP ...>, ...); - pattern: | $INP = <... $REQ.$PARAM...>; ... - $XPATH.parse(<... "=~/[\/\/].+/" + $INP ...>, ...); + $XPATH.parse(<... "=~/^[\/\/].+/" + $INP ...>, ...); - pattern: | $INP = <... $REQ.$PARAM["..."] ...>; ... - $XPATH.parse(<... "=~/[\/\/].+/" + $INP ...>, ...); + $XPATH.parse(<... "=~/^[\/\/].+/" + $INP ...>, ...); - pattern: | $INP = <... $REQ.$PARAM("...") ...>; ... - $XPATH.parse(<... "=~/[\/\/].+/" + $INP ...>, ...); + $XPATH.parse(<... "=~/^[\/\/].+/" + $INP ...>, ...); - pattern: | $INP = <... $REQ["..."] ...>; ... - $XPATH.parse(<... "=~/[\/\/].+/" + $INP ...>, ...); + $XPATH.parse(<... "=~/^[\/\/].+/" + $INP ...>, ...); - pattern: | $INP = <... $REQ("...") ...>; ... - $XPATH.parse(<... "=~/[\/\/].+/" + $INP ...>, ...); + $XPATH.parse(<... "=~/^[\/\/].+/" + $INP ...>, ...); message: >- User controlled data in xpath.parse() can result in XPATH injection vulnerability. @@ -54,4 +54,4 @@ rules: owasp: 'A1: Injection' cwe: >- CWE-643: Improper Neutralization of Data within XPath Expressions - ('XPath Injection') + ('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/nodejsscan/xxe_node.js b/rules/xml/xxe_node.js similarity index 100% rename from nodejsscan/xxe_node.js rename to rules/xml/xxe_node.js diff --git a/nodejsscan/xxe_node.yaml b/rules/xml/xxe_node.yaml similarity index 99% rename from nodejsscan/xxe_node.yaml rename to rules/xml/xxe_node.yaml index d1b1c09..be7c3fa 100644 --- a/nodejsscan/xxe_node.yaml +++ b/rules/xml/xxe_node.yaml @@ -107,4 +107,4 @@ rules: severity: ERROR metadata: owasp: 'A4: XML External Entities (XXE)' - cwe: 'CWE-611: Improper Restriction of XML External Entity Reference' + 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 = ` + ]> +