diff --git a/javascript/express/security/audit/express-xml2json-xxe-event.js b/javascript/express/security/audit/express-xml2json-xxe-event.js index c30f6b3fc3..cbdebc56ab 100644 --- a/javascript/express/security/audit/express-xml2json-xxe-event.js +++ b/javascript/express/security/audit/express-xml2json-xxe-event.js @@ -10,7 +10,8 @@ function test1() { req.on('data', function (chunk) { buf += chunk }); - // ruleid: express-xml2json-xxe-event + // The rule isn't written in a way that it can find this + // todoruleid: express-xml2json-xxe-event req.on('end', function () { req.body = expat.toJson(buf, {coerce: true, object: true}); next(); @@ -29,7 +30,8 @@ function test2() { req.on('data', function (chunk) { buf += chunk }); - // ruleid: express-xml2json-xxe-event + // The rule isn't written in a way that it can find this + // todoruleid: express-xml2json-xxe-event req.on('end', function () { req.body = expat.toJson(buf, {coerce: true, object: true}); next(); diff --git a/javascript/express/security/audit/express-xml2json-xxe-event.yaml b/javascript/express/security/audit/express-xml2json-xxe-event.yaml index fe2537298c..c0e513da24 100644 --- a/javascript/express/security/audit/express-xml2json-xxe-event.yaml +++ b/javascript/express/security/audit/express-xml2json-xxe-event.yaml @@ -69,3 +69,4 @@ rules: import 'xml2json'; ... - pattern: $REQ.on('...', function(...) { ... $EXPAT.toJson($INPUT,...); ... }) + - focus-metavariable: $INPUT diff --git a/javascript/express/security/audit/xss/direct-response-write.js b/javascript/express/security/audit/xss/direct-response-write.js index f0718cd16f..7f0b46f6dc 100644 --- a/javascript/express/security/audit/xss/direct-response-write.js +++ b/javascript/express/security/audit/xss/direct-response-write.js @@ -54,7 +54,7 @@ app.get('/3', function (req, res) { app.get('/2', function (req, res) { var user = { user: req.query.name }; // ruleid: direct-response-write - res.send('Response
' + user.name); + res.send('Response
' + user.user); }); diff --git a/javascript/sequelize/security/audit/sequelize-raw-query.yaml b/javascript/sequelize/security/audit/sequelize-raw-query.yaml index 368c91473c..176dfe59a3 100644 --- a/javascript/sequelize/security/audit/sequelize-raw-query.yaml +++ b/javascript/sequelize/security/audit/sequelize-raw-query.yaml @@ -40,3 +40,15 @@ rules: $QUERY = $SQL + $VALUE ... $DATABASE.sequelize.query($QUERY, ...) + - pattern: | + Sequelize.literal(`...${...}...`) + - pattern: | + $QUERY = `...${...}...` + ... + Sequelize.literal($QUERY) + - pattern: | + Sequelize.literal($SQL + $VALUE) + - pattern: | + $QUERY = $SQL + $VALUE + ... + Sequelize.literal($QUERY) diff --git a/python/flask/security/audit/flask-cors-misconfiguration.py b/python/flask/security/audit/flask-cors-misconfiguration.py new file mode 100644 index 0000000000..1fe28cebc0 --- /dev/null +++ b/python/flask/security/audit/flask-cors-misconfiguration.py @@ -0,0 +1,39 @@ +from flask import Flask, jsonify +from flask_cors import CORS, cross_origin + +app = Flask(__name__) + +# Enable global CORS for all origins and allow credentials +# ruleid: flask-cors-misconfiguration +CORS(app, supports_credentials=True, origins="*") + +# Enable global CORS for all origins and allow credentials using "resources" dictionary +# ruleid: flask-cors-misconfiguration +cors = CORS(app, resources={ + r"/*": {"origins": "*", "supports_credentials": True}}) + + +@app.route('/data', methods=['GET']) +def get_data(): + # This route uses the global CORS configuration + return jsonify({"message": "CORS is enabled for all origins with credentials support (global config)!"}) + + +@app.route('/special-data', methods=['GET']) +# CORS applied only to this route +# ruleid: flask-cors-misconfiguration +@cross_origin(supports_credentials=True, origins="*") +def get_special_data(): + # This route uses the CORS decorator for route-specific CORS settings + return jsonify({"message": "CORS is enabled with credentials (route-specific config)!"}) + + +@app.route('/safe-route', methods=['GET']) +# ok: flask-cors-misconfiguration +@cross_origin(supports_credentials=True, origins=["https://foo.com", "https://bar.com"]) +def safe_route(): + return jsonify({"message": "CORS is enabled only for specific origins!"}) + + +if __name__ == '__main__': + app.run() diff --git a/python/flask/security/audit/flask-cors-misconfiguration.yaml b/python/flask/security/audit/flask-cors-misconfiguration.yaml new file mode 100644 index 0000000000..106a01099a --- /dev/null +++ b/python/flask/security/audit/flask-cors-misconfiguration.yaml @@ -0,0 +1,38 @@ +rules: + - id: flask-cors-misconfiguration + message: >- + Setting 'support_credentials=True' together with 'origin="*"' is a CORS + misconfiguration that can allow third party origins to read sensitive + data. Using this configuration, flask_cors will dynamically reflects the + Origin of each request in the Access-Control-Allow-Origin header, allowing + all origins and allowing cookies and credentials to be sent along with + request. It is recommended to specify allowed origins instead of using "*" + when setting 'support_credentials=True'. + languages: + - python + severity: WARNING + patterns: + - pattern-either: + - pattern: | + @cross_origin(..., origins="*", supports_credentials=True, ...) + - pattern: | + CORS(..., supports_credentials=True, origins="*", ...) + - pattern: | + CORS(..., resources={"...": {...,"origins": "*", + "supports_credentials": True,...}}) + metadata: + category: security + subcategory: + - audit + cwe: + - "CWE-942: Permissive Cross-domain Policy with Untrusted Domains" + owasp: + - A07:2021 - Identification and Authentication Failures + confidence: HIGH + likelihood: LOW + impact: HIGH + technology: + - flask + references: + - https://pypi.org/project/Flask-Cors/ + - https://flask-cors.readthedocs.io/en/latest/index.html diff --git a/python/lang/security/insecure-uuid-version.py b/python/lang/security/insecure-uuid-version.py new file mode 100644 index 0000000000..95ce6cee86 --- /dev/null +++ b/python/lang/security/insecure-uuid-version.py @@ -0,0 +1,19 @@ +import uuid +def example_1(): + # ruleid:insecure-uuid-version + uuid = uuid.uuid1() + +from uuid import uuid1 +def example_2(): + # ruleid:insecure-uuid-version + uuid = uuid1() + +from uuid import * +def example_3(): + # ruleid:insecure-uuid-version + uuid = uuid1() + +import uuid +def unrelated_function(): + # ok:insecure-uuid-version + uuid = uuid4() diff --git a/python/lang/security/insecure-uuid-version.yaml b/python/lang/security/insecure-uuid-version.yaml new file mode 100644 index 0000000000..00d78f516e --- /dev/null +++ b/python/lang/security/insecure-uuid-version.yaml @@ -0,0 +1,33 @@ +rules: + - id: insecure-uuid-version + patterns: + - pattern: uuid.uuid1(...) + message: >- + Using UUID version 1 for UUID generation can lead to predictable UUIDs based on system information (e.g., MAC address, timestamp). This may lead to security risks such as the sandwich attack. Consider using `uuid.uuid4()` instead for better randomness and security. + metadata: + references: + - https://www.landh.tech/blog/20230811-sandwich-attack/ + cwe: + - 'CWE-330: Use of Insufficiently Random Values' + owasp: + - A02:2021 - Cryptographic Failures + asvs: + section: V6 Stored Cryptography Verification Requirements + control_id: 6.3.2 Insecure UUID Generation + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x14-V6-Cryptography.md#v63-random-values + version: '4' + category: security + technology: + - python + subcategory: + - audit + likelihood: LOW + impact: MEDIUM + confidence: MEDIUM + languages: + - python + severity: WARNING + fix-regex: + regex: uuid1 + replacement: uuid4 + diff --git a/scala/lang/security/audit/tainted-sql-string.scala b/scala/lang/security/audit/tainted-sql-string.scala index 59bd99623c..d1a12cff09 100644 --- a/scala/lang/security/audit/tainted-sql-string.scala +++ b/scala/lang/security/audit/tainted-sql-string.scala @@ -94,4 +94,9 @@ object Smth { logWarning(s"Create user $name") } } + + def throwException(name: String) = { + // ok: tainted-sql-string + throw new IllegalArgumentException(s"Can't create a ${name}") + } } diff --git a/scala/lang/security/audit/tainted-sql-string.yaml b/scala/lang/security/audit/tainted-sql-string.yaml index c02debe264..24805b5acd 100644 --- a/scala/lang/security/audit/tainted-sql-string.yaml +++ b/scala/lang/security/audit/tainted-sql-string.yaml @@ -73,6 +73,7 @@ rules: - pattern-regex: | .*\b(?i)(select|delete|insert|create|update|alter|drop)\b.* - pattern-not-inside: println(...) + - pattern-not-inside: throw new $EXCEPTION(...) pattern-sanitizers: - pattern-either: - patterns: diff --git a/typescript/react/security/react-insecure-request.jsx b/typescript/react/security/react-insecure-request.jsx index b36619c4c5..a8c33d43f2 100644 --- a/typescript/react/security/react-insecure-request.jsx +++ b/typescript/react/security/react-insecure-request.jsx @@ -34,3 +34,6 @@ const options = { url: 'https://www.example.com', }; axios(options); + +// ok: react-insecure-request +axios.get('http://localhost/foo'); diff --git a/typescript/react/security/react-insecure-request.tsx b/typescript/react/security/react-insecure-request.tsx index b36619c4c5..a8c33d43f2 100644 --- a/typescript/react/security/react-insecure-request.tsx +++ b/typescript/react/security/react-insecure-request.tsx @@ -34,3 +34,6 @@ const options = { url: 'https://www.example.com', }; axios(options); + +// ok: react-insecure-request +axios.get('http://localhost/foo'); diff --git a/typescript/react/security/react-insecure-request.yaml b/typescript/react/security/react-insecure-request.yaml index 7933cd1625..dc6356a1d8 100644 --- a/typescript/react/security/react-insecure-request.yaml +++ b/typescript/react/security/react-insecure-request.yaml @@ -23,39 +23,39 @@ rules: - typescript - javascript severity: ERROR - pattern-either: - - patterns: + patterns: - pattern-either: - - pattern-inside: | - import $AXIOS from 'axios'; - ... - $AXIOS.$METHOD(...) - - pattern-inside: | - $AXIOS = require('axios'); - ... - $AXIOS.$METHOD(...) - - pattern-either: - - pattern: $AXIOS.get("=~/[Hh][Tt][Tt][Pp]:\/\/.*/",...) - - pattern: $AXIOS.post("=~/[Hh][Tt][Tt][Pp]:\/\/.*/",...) - - pattern: $AXIOS.delete("=~/[Hh][Tt][Tt][Pp]:\/\/.*/",...) - - pattern: $AXIOS.head("=~/[Hh][Tt][Tt][Pp]:\/\/.*/",...) - - pattern: $AXIOS.patch("=~/[Hh][Tt][Tt][Pp]:\/\/.*/",...) - - pattern: $AXIOS.put("=~/[Hh][Tt][Tt][Pp]:\/\/.*/",...) - - pattern: $AXIOS.options("=~/[Hh][Tt][Tt][Pp]:\/\/.*/",...) - - patterns: - - pattern-either: - - pattern-inside: | - import $AXIOS from 'axios'; - ... - $AXIOS(...) - - pattern-inside: | - $AXIOS = require('axios'); - ... - $AXIOS(...) - - pattern-either: - - pattern: '$AXIOS({url: "=~/[Hh][Tt][Tt][Pp]:\/\/.*/"}, ...)' - - pattern: | - $OPTS = {url: "=~/[Hh][Tt][Tt][Pp]:\/\/.*/"} - ... - $AXIOS($OPTS, ...) - - pattern: fetch("=~/[Hh][Tt][Tt][Pp]:\/\/.*/", ...) + - patterns: + - pattern-either: + - pattern-inside: | + import $AXIOS from 'axios'; + ... + $AXIOS.$METHOD(...) + - pattern-inside: | + $AXIOS = require('axios'); + ... + $AXIOS.$METHOD(...) + - pattern: $AXIOS.$VERB("$URL",...) + - metavariable-regex: + metavariable: $VERB + regex: ^(get|post|delete|head|patch|put|options) + - patterns: + - pattern-either: + - pattern-inside: | + import $AXIOS from 'axios'; + ... + $AXIOS(...) + - pattern-inside: | + $AXIOS = require('axios'); + ... + $AXIOS(...) + - pattern-either: + - pattern: '$AXIOS({url: "$URL"}, ...)' + - pattern: | + $OPTS = {url: "$URL"} + ... + $AXIOS($OPTS, ...) + - pattern: fetch("$URL", ...) + - metavariable-regex: + metavariable: $URL + regex: ^([Hh][Tt][Tt][Pp]:\/\/(?!localhost).*) diff --git a/yaml/semgrep/metadata-owasp.test.yaml b/yaml/semgrep/metadata-owasp.test.yaml index 0f1946b24f..b7d264c4db 100644 --- a/yaml/semgrep/metadata-owasp.test.yaml +++ b/yaml/semgrep/metadata-owasp.test.yaml @@ -15,6 +15,22 @@ rules: metadata: # ok: metadata-owasp owasp: A05:2021 - Security Misconfiguration + - id: example-k8s-1 + message: Example + severity: ERROR + languages: [json, yaml] + pattern: "..." + metadata: + # ok: metadata-owasp + owasp: "K1: Insecure Workload Configurations" + - id: example-k8s-1b + message: Example + severity: ERROR + languages: [json, yaml] + pattern: "..." + metadata: + # ok: metadata-owasp + owasp: K01:2022 - Insecure Workload Configurations - id: example-bad-zero message: Example severity: ERROR @@ -75,6 +91,8 @@ rules: - A05:2021 - Security Misconfiguration # ok: metadata-owasp - A06:2017 - Security Misconfiguration + # ok: metadata-owasp + - K01:2022 - Insecure Workload Configurations - id: example-bad-list message: Example severity: ERROR diff --git a/yaml/semgrep/metadata-owasp.yaml b/yaml/semgrep/metadata-owasp.yaml index a0dec878cc..510a3018ee 100644 --- a/yaml/semgrep/metadata-owasp.yaml +++ b/yaml/semgrep/metadata-owasp.yaml @@ -2,7 +2,7 @@ rules: - id: metadata-owasp message: >- The `owasp` tag in Semgrep rule metadata should start with the format "A00:YYYY", - where A00 is the OWASP top ten number and YYYY is the OWASP top ten year. + where A00 is the OWASP Top 10 number and YYYY is the OWASP Top 10 year. severity: ERROR languages: [json, yaml] patterns: @@ -13,13 +13,13 @@ rules: # If there's a year, need leading zero, e.g. `A01:2021 blah` rather than `A1:2021 blah`. - patterns: - pattern: 'owasp: "..."' - - pattern-not: 'owasp: "=~/^A(0?[1-9]|10):\s+.+$/"' - - pattern-not: 'owasp: "=~/^A(0[1-9]|10):([0-9]{4})?\s+.+$/"' + - pattern-not: 'owasp: "=~/^(A|K|LLM)(0?[1-9]|10):\s+.+$/"' + - pattern-not: 'owasp: "=~/^(A|K|LLM)(0[1-9]|10):([0-9]{4})?\s+.+$/"' # A list, must have the year, e.g. `- A01:2021 blah` - patterns: - pattern-inside: "owasp: [...]" - pattern: '"$ANYTHING"' - - pattern-not-regex: .*A(0[1-9]|10):[0-9]{4}\s+.* + - pattern-not-regex: .*(A|K|LLM)(0[1-9]|10):[0-9]{4}\s+.* - pattern-not-regex: "owasp:" metadata: category: best-practice