From d44b14b63447cafa1f22e8ec68875f8ba1636679 Mon Sep 17 00:00:00 2001 From: ESS-ENN Date: Tue, 22 Oct 2024 18:38:48 +0530 Subject: [PATCH 1/2] debug-enabled-python --- .../python/security/debug-enabled-python.yml | 92 +++++++++++++++++++ .../debug-enabled-python-snapshot.yml | 47 ++++++++++ tests/python/debug-enabled-python-test.yml | 10 ++ 3 files changed, 149 insertions(+) create mode 100644 rules/python/security/debug-enabled-python.yml create mode 100644 tests/__snapshots__/debug-enabled-python-snapshot.yml create mode 100644 tests/python/debug-enabled-python-test.yml diff --git a/rules/python/security/debug-enabled-python.yml b/rules/python/security/debug-enabled-python.yml new file mode 100644 index 00000000..3e13e3c5 --- /dev/null +++ b/rules/python/security/debug-enabled-python.yml @@ -0,0 +1,92 @@ +id: debug-enabled-python +severity: warning +language: python +message: >- + Detected Flask app with debug=True. Do not deploy to production with + this flag enabled as it will leak sensitive information. Instead, consider + using Flask configuration variables or setting 'debug' using system + environment variables. +note: >- + [CWE-489] Active Debug Code. + [REFERENCES] + - https://labs.detectify.com/2015/10/02/how-patreon-got-hacked-publicly-exposed-werkzeug-debugger/ +utils: + MATCH_PATTERN_debug=True: + kind: call + all: + - has: + stopBy: neighbor + kind: attribute + all: + - has: + stopBy: neighbor + kind: identifier + regex: "^app$" + - has: + stopBy: neighbor + kind: identifier + regex: "^run$" + - has: + stopBy: neighbor + kind: argument_list + has: + stopBy: neighbor + kind: keyword_argument + regex: "^debug=True$" + - any: + - inside: + stopBy: end + kind: if_statement + follows: + stopBy: end + kind: import_from_statement + has: + stopBy: end + kind: dotted_name + has: + stopBy: neighbor + kind: identifier + regex: "^Flask$" + - inside: + stopBy: end + kind: function_definition + follows: + stopBy: end + kind: import_from_statement + has: + stopBy: end + kind: dotted_name + has: + stopBy: neighbor + kind: identifier + regex: "^Flask$" + - inside: + stopBy: end + kind: expression_statement + follows: + stopBy: end + kind: import_from_statement + has: + stopBy: end + kind: dotted_name + has: + stopBy: neighbor + kind: identifier + regex: "^Flask$" + - inside: + stopBy: end + kind: decorated_definition + follows: + stopBy: end + kind: import_from_statement + has: + stopBy: end + kind: dotted_name + has: + stopBy: neighbor + kind: identifier + regex: "^Flask$" +rule: + kind: call + any: + - matches: MATCH_PATTERN_debug=True diff --git a/tests/__snapshots__/debug-enabled-python-snapshot.yml b/tests/__snapshots__/debug-enabled-python-snapshot.yml new file mode 100644 index 00000000..65065284 --- /dev/null +++ b/tests/__snapshots__/debug-enabled-python-snapshot.yml @@ -0,0 +1,47 @@ +id: debug-enabled-python +snapshots: + ? | + from flask import Flask + if __name__ == "__main__": + app.run("0.0.0.0", debug=True) + : labels: + - source: app.run("0.0.0.0", debug=True) + style: primary + start: 51 + end: 81 + - source: app + style: secondary + start: 51 + end: 54 + - source: run + style: secondary + start: 55 + end: 58 + - source: app.run + style: secondary + start: 51 + end: 58 + - source: debug=True + style: secondary + start: 70 + end: 80 + - source: ("0.0.0.0", debug=True) + style: secondary + start: 58 + end: 81 + - source: Flask + style: secondary + start: 18 + end: 23 + - source: Flask + style: secondary + start: 18 + end: 23 + - source: from flask import Flask + style: secondary + start: 0 + end: 23 + - source: app.run("0.0.0.0", debug=True) + style: secondary + start: 51 + end: 81 diff --git a/tests/python/debug-enabled-python-test.yml b/tests/python/debug-enabled-python-test.yml new file mode 100644 index 00000000..47558a48 --- /dev/null +++ b/tests/python/debug-enabled-python-test.yml @@ -0,0 +1,10 @@ +id: debug-enabled-python +valid: + - | + def env(): + app.run("0.0.0.0", debug=os.environ.get("DEBUG", False)) +invalid: + - | + from flask import Flask + if __name__ == "__main__": + app.run("0.0.0.0", debug=True) From 6533185563613a7982292d2cb27aefb82e561ac6 Mon Sep 17 00:00:00 2001 From: ESS-ENN Date: Tue, 22 Oct 2024 18:39:37 +0530 Subject: [PATCH 2/2] avoid-bind-to-all-interfaces-python --- .../avoid-bind-to-all-interfaces-python.yml | 64 +++++++++++++++++++ ...bind-to-all-interfaces-python-snapshot.yml | 62 ++++++++++++++++++ ...oid-bind-to-all-interfaces-python-test.yml | 13 ++++ 3 files changed, 139 insertions(+) create mode 100644 rules/python/security/avoid-bind-to-all-interfaces-python.yml create mode 100644 tests/__snapshots__/avoid-bind-to-all-interfaces-python-snapshot.yml create mode 100644 tests/python/avoid-bind-to-all-interfaces-python-test.yml diff --git a/rules/python/security/avoid-bind-to-all-interfaces-python.yml b/rules/python/security/avoid-bind-to-all-interfaces-python.yml new file mode 100644 index 00000000..c3867b2b --- /dev/null +++ b/rules/python/security/avoid-bind-to-all-interfaces-python.yml @@ -0,0 +1,64 @@ +id: avoid-bind-to-all-interfaces-python +severity: warning +language: python +message: >- + Running `socket.bind` to 0.0.0.0, or empty string could unexpectedly + expose the server publicly as it binds to all available interfaces. + Consider instead getting correct address from an environment variable or + configuration file. +note: >- + [CWE-200] Exposure of Sensitive Information to an Unauthorized Actor. + [REFERENCES] + - https://owasp.org/Top10/A01_2021-Broken_Access_Control +utils: + MATCH_PATTERN_$S.bind: + kind: expression_statement + all: + - has: + stopBy: neighbor + kind: call + all: + - has: + stopBy: neighbor + kind: attribute + all: + - has: + stopBy: neighbor + kind: identifier + - has: + stopBy: neighbor + kind: identifier + regex: "^bind$" + - has: + stopBy: neighbor + kind: argument_list + has: + stopBy: neighbor + kind: tuple + has: + stopBy: neighbor + kind: string + regex: ^'0.0.0.0'|'::'|''$ + - follows: + stopBy: end + kind: expression_statement + has: + stopBy: end + kind: call + has: + stopBy: neighbor + kind: attribute + all: + - has: + stopBy: neighbor + kind: identifier + regex: "^socket$" + - has: + stopBy: neighbor + kind: identifier + regex: "^socket$" + +rule: + kind: expression_statement + any: + - matches: MATCH_PATTERN_$S.bind diff --git a/tests/__snapshots__/avoid-bind-to-all-interfaces-python-snapshot.yml b/tests/__snapshots__/avoid-bind-to-all-interfaces-python-snapshot.yml new file mode 100644 index 00000000..5a9a8c45 --- /dev/null +++ b/tests/__snapshots__/avoid-bind-to-all-interfaces-python-snapshot.yml @@ -0,0 +1,62 @@ +id: avoid-bind-to-all-interfaces-python +snapshots: + ? | + s = socket.socket(doesnt, matter) + s.bind(('',)) + s = socket.socket(doesnt, matter) + s.bind(('::', 1337)) + s = socket.socket(doesnt, matter) + s.bind(('0.0.0.0', 1337)) + : labels: + - source: s.bind(('',)) + style: primary + start: 34 + end: 47 + - source: s + style: secondary + start: 34 + end: 35 + - source: bind + style: secondary + start: 36 + end: 40 + - source: s.bind + style: secondary + start: 34 + end: 40 + - source: '''''' + style: secondary + start: 42 + end: 44 + - source: ('',) + style: secondary + start: 41 + end: 46 + - source: (('',)) + style: secondary + start: 40 + end: 47 + - source: s.bind(('',)) + style: secondary + start: 34 + end: 47 + - source: socket + style: secondary + start: 4 + end: 10 + - source: socket + style: secondary + start: 4 + end: 10 + - source: socket.socket + style: secondary + start: 4 + end: 17 + - source: socket.socket(doesnt, matter) + style: secondary + start: 4 + end: 33 + - source: s = socket.socket(doesnt, matter) + style: secondary + start: 0 + end: 33 diff --git a/tests/python/avoid-bind-to-all-interfaces-python-test.yml b/tests/python/avoid-bind-to-all-interfaces-python-test.yml new file mode 100644 index 00000000..53276f31 --- /dev/null +++ b/tests/python/avoid-bind-to-all-interfaces-python-test.yml @@ -0,0 +1,13 @@ +id: avoid-bind-to-all-interfaces-python +valid: + - | + s = socket.socket(doesnt, matter) + s.bind(('fe80::34cb:9850:4868:9d2c', 1337)) +invalid: + - | + s = socket.socket(doesnt, matter) + s.bind(('',)) + s = socket.socket(doesnt, matter) + s.bind(('::', 1337)) + s = socket.socket(doesnt, matter) + s.bind(('0.0.0.0', 1337))