diff --git a/rules/java/lang/insecure_allow_origin.yml b/rules/java/lang/insecure_allow_origin.yml new file mode 100644 index 000000000..715a930aa --- /dev/null +++ b/rules/java/lang/insecure_allow_origin.yml @@ -0,0 +1,59 @@ +imports: + - java_shared_lang_instance + - java_shared_lang_servlet_request + - java_shared_lang_user_input +patterns: + - pattern: | + $.$($
, $); + filters: + - variable: 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: HEADER + string_regex: \A(?i)(Access-Control-Allow-Origin)\z + - variable: USER_INPUT + detection: java_lang_insecure_allow_origin_user_input +auxiliary: + - id: java_lang_insecure_allow_origin_user_input + patterns: + - pattern: $; + filters: + - variable: USER_INPUT + detection: java_shared_lang_user_input + scope: cursor + - pattern: $.$().getAttribute(); + filters: + - variable: USER_INPUT_REQUEST + detection: java_shared_lang_servlet_request + scope: cursor + - variable: REQUEST_GET_METHOD + values: + - getSession + - getServletContext +languages: + - java +metadata: + description: Unsanitized user input in Access-Control-Allow-Origin + remediation_message: | + ## Description + Do not use unverified user-defined input to define Access-Control-Allow-Origin. + This can lead to unintended user access to sensitive data. + + ## Remediations + ❌ Avoid defining origins with user input wherever possible. + + ✅ If unavoidable, be sure to verify the input or to use a safe-list. + + ## Resources + - [OWASP Origin & Access-Control-Allow-Origin](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/11-Client-side_Testing/07-Testing_Cross_Origin_Resource_Sharing) + cwe_id: + - 942 + id: java_lang_insecure_allow_origin + documentation_url: https://docs.bearer.com/reference/rules/java_lang_insecure_allow_origin diff --git a/rules/java/shared/lang/user_input.yml b/rules/java/shared/lang/user_input.yml index 18c10d954..9b8637bde 100644 --- a/rules/java/shared/lang/user_input.yml +++ b/rules/java/shared/lang/user_input.yml @@ -39,6 +39,21 @@ patterns: - variable: JAVA_SHARED_LANG_USER_INPUT_PARAM_MAP detection: java_shared_lang_user_input_param_map scope: cursor + - pattern: $.get()[$<_>]; + filters: + - variable: JAVA_SHARED_LANG_USER_INPUT_PARAM_MAP + detection: java_shared_lang_user_input_param_map + scope: cursor + - pattern: $.nextElement(); + filters: + - variable: JAVA_SHARED_LANG_USER_INPUT_PARAM_NAMES + detection: java_shared_lang_user_input_param_names + scope: cursor + - pattern: $[$<_>]; + filters: + - variable: JAVA_SHARED_LANG_USER_INPUT_PARAM_VALUES + detection: java_shared_lang_user_input_param_values + scope: cursor auxiliary: - id: java_shared_lang_user_input_cookies patterns: @@ -54,6 +69,20 @@ auxiliary: - variable: JAVA_SHARED_LANG_USER_INPUT_REQUEST detection: java_shared_lang_servlet_request scope: cursor + - id: java_shared_lang_user_input_param_names + patterns: + - pattern: $.getParameterNames(); + filters: + - variable: JAVA_SHARED_LANG_USER_INPUT_REQUEST + detection: java_shared_lang_servlet_request + scope: cursor + - id: java_shared_lang_user_input_param_values + patterns: + - pattern: $.getParameterValues(); + filters: + - variable: JAVA_SHARED_LANG_USER_INPUT_REQUEST + detection: java_shared_lang_servlet_request + scope: cursor metadata: description: "Java user input." id: java_shared_lang_user_input diff --git a/tests/java/lang/insecure_allow_origin/test.js b/tests/java/lang/insecure_allow_origin/test.js new file mode 100644 index 000000000..60b6b2314 --- /dev/null +++ b/tests/java/lang/insecure_allow_origin/test.js @@ -0,0 +1,18 @@ +const { + createNewInvoker, + getEnvironment, +} = require("../../../helper.js") +const { ruleId, ruleFile, testBase } = getEnvironment(__dirname) + +describe(ruleId, () => { + const invoke = createNewInvoker(ruleId, ruleFile, testBase) + + test("insecure_allow_origin", () => { + const testCase = "main.java" + + const results = invoke(testCase) + + expect(results.Missing).toEqual([]) + expect(results.Extra).toEqual([]) + }) +}) \ No newline at end of file diff --git a/tests/java/lang/insecure_allow_origin/testdata/main.java b/tests/java/lang/insecure_allow_origin/testdata/main.java new file mode 100644 index 000000000..2c821765c --- /dev/null +++ b/tests/java/lang/insecure_allow_origin/testdata/main.java @@ -0,0 +1,120 @@ +package com.test.servlet.cors; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +public class Foo extends HttpServlet { + @Override + protected void bad(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String paramValue = request.getParameter("bad"); + String header = request.getHeader("bad"); + String queryString = request.getQueryString(); + + String[] parameterValues = request.getParameterValues("URL"); + Enumeration parameterNames = request.getParameterNames(); + Map parameterMap = request.getParameterMap(); + + String indexedParameterValue = parameterValues[0]; + String parameterNamesElem = parameterNames.nextElement(); + String indexedValueFromParameterMap = parameterMap.get("URL")[0]; + + if (paramValue != null) { + // bearer:expected java_lang_insecure_allow_origin + response.setHeader("Access-Control-Allow-Origin", paramValue); + + // bearer:expected java_lang_insecure_allow_origin + response.addHeader("Access-Control-Allow-Origin", paramValue); + + // bearer:expected java_lang_insecure_allow_origin + response.addHeader("access-control-allow-origin", paramValue); + + // bearer:expected java_lang_insecure_allow_origin + response.addHeader("access-control-allow-origin", header); + + // bearer:expected java_lang_insecure_allow_origin + response.addHeader("access-control-allow-origin", indexedParameterValue); + + // bearer:expected java_lang_insecure_allow_origin + response.addHeader("access-control-allow-origin", parameterNamesElem); + + // bearer:expected java_lang_insecure_allow_origin + response.addHeader("access-control-allow-origin", indexedValueFromParameterMap); + + String[] keyValuePairs = queryString.split("="); + String lastPair = keyValuePairs[keyValuePairs.length - 1]; + // bearer:expected java_lang_insecure_allow_origin + response.addHeader("access-control-allow-origin", lastPair); + + String headerName = "ACCESS-CONTROL-ALLOW-ORIGIN"; + + // bearer:expected java_lang_insecure_allow_origin + response.addHeader(headerName, paramValue); + + return; + } + } + + public void badSessionAttr(HttpServletRequest request, HttpServletResponse response) throws ServletException { + request.getSession().setAttribute("someAttrName", request.getParameter("bad")); + String sessionAttr = (String) request.getSession().getAttribute("attributeName"); + + // bearer:expected java_lang_insecure_allow_origin + response.addHeader("access-control-allow-origin", sessionAttr); + } + + public void badRequestAttr(HttpServletRequest request, HttpServletResponse response) throws ServletException { + request.setAttribute("someAttrName",request.getParameter("bad")); + String requestAttr = (String) request.getAttribute("someAttrName"); + + // bearer:expected java_lang_insecure_allow_origin + response.addHeader("access-control-allow-origin", requestAttr); + } + + public void badServletContext(HttpServletRequest request, HttpServletResponse response) throws ServletException { + request.getServletContext().setAttribute("someAttrName",request.getParameter("bad")); + String contextAttr = (String) request.getServletContext().getAttribute("someAttrName"); + + // bearer:expected java_lang_insecure_allow_origin + response.addHeader("access-control-allow-origin", contextAttr); + } + + public void badModifiedPath(HttpServletRequest request, HttpServletResponse response) throws ServletException { + String pathInfo = request.getPathInfo(); + String modifiedPath = pathInfo.replaceFirst("/",""); + + // bearer:expected java_lang_insecure_allow_origin + response.addHeader("Access-Control-Allow-Origin", modifiedPath); + } + + public void ok(HttpServletRequest request, HttpServletResponse response) throws ServletException { + String paramValue = request.getParameter("bad"); + if paramValue != null { + // set some other header with user-input + response.setHeader("X-Example-Header", paramValue); + } + + String pathInfo = request.getPathInfo(); + String modifiedPath = pathInfo.replaceFirst("/",""); + // set some other header with user-input + response.setHeader("X-Example-Header", modifiedPath); + + response.setHeader("Access-Control-Allow-Origin", "https://example.com"); + response.addHeader("Access-Control-Allow-Origin", "https://example.com"); + response.addHeader("Access-Control-Allow-Origin", getFromList("key")); + // bad for other reasons! + response.addHeader("Access-Control-Allow-Origin", "*"); + } + + public String getFromList(String key){ + HashMap corsList = new HashMap<>(); + corsList.put("key", "https://example.com"); + + return corsList.get(key); + } +} \ No newline at end of file