From 586e6b0249f75ba6471848aff006c9b22dc2c208 Mon Sep 17 00:00:00 2001 From: David Roe Date: Thu, 16 May 2024 10:31:27 +0100 Subject: [PATCH] feat: add http response splitting rule --- rules/python/lang/http_response_splitting.yml | 54 +++++++++++++++++++ .../shared/lang/http_server_handler.yml | 26 +++++++++ rules/python/shared/lang/user_input.yml | 7 +++ .../lang/http_response_splitting/test.js | 20 +++++++ .../http_response_splitting/testdata/main.py | 32 +++++++++++ 5 files changed, 139 insertions(+) create mode 100644 rules/python/lang/http_response_splitting.yml create mode 100644 rules/python/shared/lang/http_server_handler.yml create mode 100644 tests/python/lang/http_response_splitting/test.js create mode 100644 tests/python/lang/http_response_splitting/testdata/main.py diff --git a/rules/python/lang/http_response_splitting.yml b/rules/python/lang/http_response_splitting.yml new file mode 100644 index 00000000..12264473 --- /dev/null +++ b/rules/python/lang/http_response_splitting.yml @@ -0,0 +1,54 @@ +imports: + - python_shared_common_user_input + - python_shared_lang_http_server_handler +patterns: + - pattern: $.send_header($<...>$$<...>) + filters: + - variable: HANDLER + detection: python_shared_lang_http_server_handler + scope: cursor + - variable: USER_INPUT + detection: python_lang_http_response_splitting_user_input + scope: result +auxiliary: + - id: python_lang_http_response_splitting_user_input + sanitizer: python_lang_http_response_splitting_sanitizer + patterns: + - pattern: $ + filters: + - variable: UNSANITIZED_USER_INPUT + detection: python_shared_common_user_input + scope: cursor + - id: python_lang_http_response_splitting_sanitizer + patterns: + - pattern: $.replace($, $) + focus: ORIGINAL + filters: + - variable: SOURCE + string_regex: "\\r\\n|\\\\r\\\\n" + - not: + variable: REPLACEMENT + string_regex: "\\r\\n|\\\\r\\\\n" +languages: + - python +severity: high +metadata: + description: "Unsanitized user input in HTTP response (XSS)" + remediation_message: |- + ## Description + + Including unsanitized user input in a HTTP response could allow an attacker inject Carriage Return Line Feed (CRLF) characters into the response. An entirely attacker-controlled response can then be returned, creating a cross-site scripting (XSS) vulnerability. + + ## Remediations + + - **Do not** include user input in cookies or other HTTP headers without proper sanitization. This can prevent attackers from exploiting the input to manipulate the response. + - **Do** remove CRLF sequences from user input to mitigate the risk of response splitting and XSS attacks. Use the following code snippet as a reference for sanitizing input in Java: + ```python + input = request.getParameter("data"); + var sanitized = input.replaceAll("\r\n", ""); + cookie.setValue(sanitized); + ``` + cwe_id: + - 79 + id: python_lang_http_response_splitting + documentation_url: https://docs.bearer.com/reference/rules/python_lang_http_response_splitting diff --git a/rules/python/shared/lang/http_server_handler.yml b/rules/python/shared/lang/http_server_handler.yml new file mode 100644 index 00000000..6bf89f5c --- /dev/null +++ b/rules/python/shared/lang/http_server_handler.yml @@ -0,0 +1,26 @@ +type: shared +languages: + - python +imports: + - python_shared_lang_instance + - python_shared_lang_import2 +patterns: + - pattern: $ + filters: + - variable: INSTANCE + detection: python_shared_lang_instance + scope: cursor_strict + filters: + - variable: CLASS + detection: python_shared_lang_import2 + scope: cursor + filters: + - variable: MODULE1 + values: [http] + - variable: MODULE2 + values: [server] + - variable: NAME + values: [BaseHTTPRequestHandler] +metadata: + description: "Python http.server handler instance" + id: python_shared_lang_http_server_handler diff --git a/rules/python/shared/lang/user_input.yml b/rules/python/shared/lang/user_input.yml index dd85a141..e1bd0443 100644 --- a/rules/python/shared/lang/user_input.yml +++ b/rules/python/shared/lang/user_input.yml @@ -1,8 +1,15 @@ type: shared languages: - python +imports: + - python_shared_lang_http_server_handler patterns: - input($<...>) + - pattern: $.path + filters: + - variable: HANDLER + detection: python_shared_lang_http_server_handler + scope: cursor metadata: description: "Python lang user input." id: python_shared_lang_user_input diff --git a/tests/python/lang/http_response_splitting/test.js b/tests/python/lang/http_response_splitting/test.js new file mode 100644 index 00000000..01ae6799 --- /dev/null +++ b/tests/python/lang/http_response_splitting/test.js @@ -0,0 +1,20 @@ +const { + createNewInvoker, + getEnvironment, +} = require("../../../helper.js") +const { ruleId, ruleFile, testBase } = getEnvironment(__dirname) + +describe(ruleId, () => { + const invoke = createNewInvoker(ruleId, ruleFile, testBase) + + test("http_response_splitting", () => { + const testCase = "main.py" + + const results = invoke(testCase) + + expect(results).toEqual({ + Missing: [], + Extra: [] + }) + }) +}) \ No newline at end of file diff --git a/tests/python/lang/http_response_splitting/testdata/main.py b/tests/python/lang/http_response_splitting/testdata/main.py new file mode 100644 index 00000000..58f727e4 --- /dev/null +++ b/tests/python/lang/http_response_splitting/testdata/main.py @@ -0,0 +1,32 @@ +# this file is also testing the shared instance rule + +from http.server import BaseHTTPRequestHandler + +class WebRequestHandler(BaseHTTPRequestHandler, Foo): + def do_GET(self): + self.send_header("ok", "ok") + + # bearer:expected python_lang_http_response_splitting + self.send_header(self.path, "ok") + + def do_POST(self): + self.send_header(self.path.replace("\r\n", ""), "ok") + self.send_header("ok", self.path.replace("\r\n", "")) + + # bearer:expected python_lang_http_response_splitting + self.send_header("ok", self.path) + + +# contrived examples for testing instance rule +class Other: + def m(self, x: BaseHTTPRequestHandler) -> string: + # bearer:expected python_lang_http_response_splitting + x.send_header(input(), "ok") + + y = BaseHTTPRequestHandler(foo) + # bearer:expected python_lang_http_response_splitting + y.send_header(input(), "ok") + + def m(self, z: BaseHTTPRequestHandler = default) -> string: + # bearer:expected python_lang_http_response_splitting + z.send_header(input(), "ok")