From 3f6d254bd1144e3bc332558078f4bbb4293c8144 Mon Sep 17 00:00:00 2001 From: elsapet Date: Tue, 30 Jan 2024 17:45:18 +0200 Subject: [PATCH] feat(java): extend HTTP response splitting rule --- rules/java/lang/http_response_splitting.yml | 49 +++++++++++++++ rules/java/shared/lang/user_input.yml | 1 + .../java/lang/http_response_splitting/test.js | 18 ++++-- .../testdata/main.java | 61 +++++++++++++++++++ 4 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 tests/java/lang/http_response_splitting/testdata/main.java diff --git a/rules/java/lang/http_response_splitting.yml b/rules/java/lang/http_response_splitting.yml index fa57865c8..1e1440868 100644 --- a/rules/java/lang/http_response_splitting.yml +++ b/rules/java/lang/http_response_splitting.yml @@ -20,6 +20,55 @@ patterns: - variable: USER_INPUT detection: java_lang_http_response_splitting_user_input scope: result + - pattern: $.$($<_>, $); + filters: + - variable: HTTP_SERVLET_RES + detection: java_shared_lang_instance + scope: cursor + filters: + - variable: JAVA_SHARED_LANG_INSTANCE_TYPE + regex: \A(javax\.servlet\.http\.)?HttpServletResponse\z + - variable: METHOD + values: + - setHeader + - addHeader + - variable: USER_INPUT + detection: java_lang_http_response_splitting_user_input + scope: result + - pattern: $.$($<_>, $); + filters: + - variable: HTTP_SERVLET_RES + detection: java_shared_lang_instance + scope: cursor + filters: + - variable: JAVA_SHARED_LANG_INSTANCE_TYPE + regex: \A(javax\.servlet\.http\.)?HttpServletResponseWrapper\z + - variable: METHOD + values: + - setHeader + - addHeader + - variable: USER_INPUT + detection: java_lang_http_response_splitting_user_input + scope: result + - pattern: $.$($<_>, $); + filters: + - variable: HTTP_SERVLET_RES + detection: java_shared_lang_instance + scope: cursor + filters: + - variable: JAVA_SHARED_LANG_INSTANCE_TYPE + regex: \A(javax\.servlet\.http\.)?HttpServletRequest\z + - variable: METHOD + values: + - getParameter + - getParameterNames + - getParameterValues + - getParameterMap + - getHeader + - getPathInfo + - variable: USER_INPUT + detection: java_lang_http_response_splitting_user_input + scope: result auxiliary: - id: java_lang_http_response_splitting_user_input sanitizer: java_lang_http_response_splitting_sanitizer diff --git a/rules/java/shared/lang/user_input.yml b/rules/java/shared/lang/user_input.yml index 42d135e45..18c10d954 100644 --- a/rules/java/shared/lang/user_input.yml +++ b/rules/java/shared/lang/user_input.yml @@ -20,6 +20,7 @@ patterns: - getAttribute - getInputStream - getQueryString + - getPathInfo - getParameter - getParameterMap - getParameterNames diff --git a/tests/java/lang/http_response_splitting/test.js b/tests/java/lang/http_response_splitting/test.js index 3c77c7b9d..315cd32b9 100644 --- a/tests/java/lang/http_response_splitting/test.js +++ b/tests/java/lang/http_response_splitting/test.js @@ -1,19 +1,29 @@ -const { createInvoker, getEnvironment } = require("../../../helper.js") +const { createInvoker, createNewInvoker, getEnvironment } = require("../../../helper.js") const { ruleId, ruleFile, testBase } = getEnvironment(__dirname) describe(ruleId, () => { const invoke = createInvoker(ruleId, ruleFile, testBase) - + test("bad", () => { const testCase = "bad.java" expect(invoke(testCase)).toMatchSnapshot(); }) - + test("ok", () => { const testCase = "ok.java" expect(invoke(testCase)).toMatchSnapshot(); }) - + + // new test cases + const invokeV2 = createNewInvoker(ruleId, ruleFile, testBase) + test("http_response_splitting", () => { + const testCase = "main.java" + + const results = invokeV2(testCase) + + expect(results.Missing).toEqual([]) + expect(results.Extra).toEqual([]) + }) }) \ No newline at end of file diff --git a/tests/java/lang/http_response_splitting/testdata/main.java b/tests/java/lang/http_response_splitting/testdata/main.java new file mode 100644 index 000000000..468c0cdc4 --- /dev/null +++ b/tests/java/lang/http_response_splitting/testdata/main.java @@ -0,0 +1,61 @@ +package cookie; + +import org.apache.commons.text.StringEscapeUtils; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +public class FooBar extends HttpServlet { + public void bad(HttpServletRequest req, HttpServletResponse resp) { + String userInput = req.getParameter("barbaz"); + // bearer:expected java_lang_http_response_splitting + resp.addHeader("MY_HEADER", userInput); + + String paramNames = req.getParameterNames().nextElement(); + // bearer:expected java_lang_http_response_splitting + resp.setHeader("PARAM_NAMES", paramNames); + + String paramValues = req.getParameterValues("input")[0]; + // bearer:expected java_lang_http_response_splitting + resp.addHeader("CUSTOM_HEADER", paramValues); + + String paramMap = req.getParameterMap().get("input")[0]; + // bearer:expected java_lang_http_response_splitting + resp.addHeader("CUSTOM_HEADER", paramMap); + + String contextPath = req.getPathInfo(); + // bearer:expected java_lang_http_response_splitting + resp.addHeader("PATH", contextPath); + } + + public void alsoBad(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String header = req.getHeader("input"); + // bearer:expected java_lang_http_response_splitting + resp.addHeader("MY_HEADER", header); + // bearer:expected java_lang_http_response_splitting + resp.setHeader("MY_HEADER", header); + + String param = req.getParameter("input"); + HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper(resp); + // bearer:expected java_lang_http_response_splitting + wrapper.addHeader("PARAM_HEADER", param); + // bearer:expected java_lang_http_response_splitting + wrapper.setHeader("PARAM_HEADER", param); + } + + protected void ok(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String data = req.getParameter("input"); + String input = data.replaceAll("[\r\n]+", ""); + resp.addHeader("SAFE_INPUT", input); + resp.setHeader("SAFE_INPUT", input); + + var baz = StringEscapeUtils.unescapeJava(request.getParameter("baz")); + resp.setHeader("BAZ_VALUE", header); + + String contextPath = req.getPathInfo(); + contextPath = contextPath.replaceAll("[\r\n]+", ""); + resp.addHeader("ALL_GOOD", contextPath); + } +}