diff --git a/PR_6_scala/scala/cookie/rule-CookieHTTPOnly.scala b/PR_6_scala/scala/cookie/rule-CookieHTTPOnly.scala new file mode 100644 index 0000000..d523084 --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-CookieHTTPOnly.scala @@ -0,0 +1,31 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package cookie + +import javax.servlet.http.Cookie +import javax.servlet.http.HttpServletResponse + +class CookieHTTPOnly { + // {fact rule=sensitive-cookie-without-http-only-flag@v1.0 defects=1} + def danger(res: HttpServletResponse): Unit = { + // ruleid: scala_cookie_rule-CookieHTTPOnly + val cookie = new Cookie("key", "value") + cookie.setSecure(true) + cookie.setMaxAge(60) + cookie.setHttpOnly(false) // danger + + res.addCookie(cookie) + } + // {/fact} + + // cookie.setHttpOnly(true) is missing + // {fact rule=sensitive-cookie-without-http-only-flag@v1.0 defects=1} + def danger2(res: HttpServletResponse): Unit = { + // ruleid: scala_cookie_rule-CookieHTTPOnly + val cookie = new Cookie("key", "value") + cookie.setSecure(true) + cookie.setMaxAge(60) + res.addCookie(cookie) + } + // {/fact} +} + diff --git a/PR_6_scala/scala/cookie/rule-CookieHTTPOnly.yml b/PR_6_scala/scala/cookie/rule-CookieHTTPOnly.yml new file mode 100644 index 0000000..5cfad9e --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-CookieHTTPOnly.yml @@ -0,0 +1,34 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_cookie_rule-CookieHTTPOnly" + languages: + - "scala" + message: | + A new cookie is created without the HttpOnly flag set. The HttpOnly flag is a directive to the + browser to make sure that the cookie can not be red by malicious script. When a user is the + target of a "Cross-Site Scripting", the attacker would benefit greatly from getting the session + id for example. + metadata: + category: "security" + cwe: "CWE-1004" + shortDescription: "Sensitive Cookie Without 'HttpOnly' Flag" + technology: + - "scala" + security-severity: "MEDIUM" + pattern-either: + - patterns: + - pattern: | + val $C = new javax.servlet.http.Cookie(..., ...); + ... + $RESP.addCookie($C); + - pattern-not-inside: | + val $C = new javax.servlet.http.Cookie(..., ...); + ... + $C.setHttpOnly(true); + ... + $RESP.addCookie($C); + - pattern: "(javax.servlet.http.Cookie $C).setHttpOnly(false);" + severity: "WARNING" diff --git a/PR_6_scala/scala/cookie/rule-CookieInsecure.scala b/PR_6_scala/scala/cookie/rule-CookieInsecure.scala new file mode 100644 index 0000000..cbd9306 --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-CookieInsecure.scala @@ -0,0 +1,45 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package cookie + +import javax.servlet.http.Cookie +import javax.servlet.http.HttpServletResponse + + +class CookieInsecure { + def danger(res: HttpServletResponse): Unit = { + // {fact rule=sensitive-information-leak@v1.0 defects=1} + // ruleid: scala_cookie_rule-CookieInsecure + val cookie = new Cookie("key", "value") + cookie.setHttpOnly(true) + // {/fact} + cookie.setMaxAge(60) + // {fact rule=sensitive-information-leak@v1.0 defects=1} + // ruleid: scala_cookie_rule-CookieInsecure + cookie.setSecure(false) // danger + // {/fact} + + res.addCookie(cookie) + } + + // {fact rule=sensitive-information-leak@v1.0 defects=1} + // cookie.setSecure(true); is missing + def danger2(res: HttpServletResponse): Unit = { + // ruleid: scala_cookie_rule-CookieInsecure + val cookie = new Cookie("key", "value") + cookie.setHttpOnly(true) + cookie.setMaxAge(60) + res.addCookie(cookie) + } + // {/fact} + + // {fact rule=sensitive-information-leak@v1.0 defects=0} + def ok(res: HttpServletResponse): Unit = { + val cookie = new Cookie("key", "value") + cookie.setHttpOnly(true) + cookie.setMaxAge(60) + cookie.setSecure(true) // safe + + res.addCookie(cookie) + } + // {/fact} +} diff --git a/PR_6_scala/scala/cookie/rule-CookieInsecure.yml b/PR_6_scala/scala/cookie/rule-CookieInsecure.yml new file mode 100644 index 0000000..b8566ec --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-CookieInsecure.yml @@ -0,0 +1,33 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_cookie_rule-CookieInsecure" + languages: + - "scala" + message: | + "A new cookie is created without the Secure flag set. The Secure flag is a + directive to the browser to make sure that the cookie is not sent for insecure communication + (http://)" + metadata: + category: "security" + cwe: "CWE-539" + shortDescription: "Information Exposure Through Persistent Cookies" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern-not-inside: | + val $C = new javax.servlet.http.Cookie(..., ...); + ... + $C.setSecure(true); + ... + $RESP.addCookie($C); + - pattern-either: + - pattern: | + val $C = new javax.servlet.http.Cookie(..., ...); + ... + $RESP.addCookie($C); + - pattern: "($C:javax.servlet.http.Cookie).setSecure(false);" + severity: "WARNING" diff --git a/PR_6_scala/scala/cookie/rule-CookiePersistent.scala b/PR_6_scala/scala/cookie/rule-CookiePersistent.scala new file mode 100644 index 0000000..85a0195 --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-CookiePersistent.scala @@ -0,0 +1,19 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package cookie + +import javax.servlet.http.Cookie +import javax.servlet.http.HttpServletResponse + +class CookiePersistent { + // {fact rule=insecure-cookie@v1.0 defects=1} + def danger(res: HttpServletResponse): Unit = { + val cookie = new Cookie("key", "value") + cookie.setSecure(true) + cookie.setHttpOnly(true) + // ruleid: scala_cookie_rule-CookiePersistent + cookie.setMaxAge(31536000) // danger + + res.addCookie(cookie) + } + // {/fact} +} diff --git a/PR_6_scala/scala/cookie/rule-CookiePersistent.yml b/PR_6_scala/scala/cookie/rule-CookiePersistent.yml new file mode 100644 index 0000000..d653ba2 --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-CookiePersistent.yml @@ -0,0 +1,25 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_cookie_rule-CookiePersistent" + languages: + - "scala" + message: | + "Storing sensitive data in a persistent cookie for an extended period can lead to a breach of + confidentiality or account compromise." + metadata: + category: "security" + cwe: "CWE-614" + shortDescription: "Sensitive Cookie in HTTPS Session Without 'Secure' Attribute" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern: | + ($C: Cookie).setMaxAge($AGE) + - metavariable-comparison: + comparison: "$AGE >= 31536000" + metavariable: "$AGE" + severity: "WARNING" diff --git a/PR_6_scala/scala/cookie/rule-CookieUsage.scala b/PR_6_scala/scala/cookie/rule-CookieUsage.scala new file mode 100644 index 0000000..4b9c0af --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-CookieUsage.scala @@ -0,0 +1,35 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package cookie + +import javax.servlet.ServletException +import javax.servlet.http.Cookie +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse +import java.io.IOException + +class CookieUsage { + @Override + @throws[ServletException] + @throws[IOException] + protected def doGet(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + for (cookie <- req.getCookies) { + // {fact rule=insecure-cookie@v1.0 defects=1} + // ruleid: scala_cookie_rule-CookieUsage + cookie.getName + // {/fact} + // {fact rule=insecure-cookie@v1.0 defects=1} + // ruleid: scala_cookie_rule-CookieUsage + cookie.getValue + // {/fact} + // {fact rule=insecure-cookie@v1.0 defects=1} + // ruleid: scala_cookie_rule-CookieUsage + cookie.getPath + // {/fact} + } + } + + def getCookieName(req: HttpServletRequest) = { + val c: Cookie = req.getCookies.head + c.getName + } +} diff --git a/PR_6_scala/scala/cookie/rule-CookieUsage.yml b/PR_6_scala/scala/cookie/rule-CookieUsage.yml new file mode 100644 index 0000000..28d1e66 --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-CookieUsage.yml @@ -0,0 +1,38 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_cookie_rule-CookieUsage" + languages: + - "scala" + message: | + The information stored in a custom cookie should not be sensitive or related to the session. + In most cases, sensitive data should only be stored in session and referenced by the user's + session cookie. + metadata: + category: "security" + cwe: "CWE-614" + shortDescription: "Sensitive Cookie in HTTPS Session Without 'Secure' Attribute" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern-inside: | + def $FUNC(..., $REQ: HttpServletRequest, ...): $TYPE = { + ... + } + - pattern-either: + - patterns: + - pattern-inside: | + for ($C <- $REQ.getCookies) { + ... + } + - pattern-either: + - pattern: "$C.getName" + - pattern: "$C.getValue" + - pattern: "$C.getPath" + - pattern: "($C: Cookie).getName()" + - pattern: "($C: Cookie).getValue" + - pattern: "($C: Cookie).getPath" + severity: "WARNING" diff --git a/PR_6_scala/scala/cookie/rule-HttpResponseSplitting.scala b/PR_6_scala/scala/cookie/rule-HttpResponseSplitting.scala new file mode 100644 index 0000000..c1ed2b6 --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-HttpResponseSplitting.scala @@ -0,0 +1,80 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package cookie + +import javax.servlet.ServletException +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 +import java.io.IOException + + +class HttpResponseSplitting extends HttpServlet { + + // {fact rule=http-response-splitting@v1.0 defects=1} + @throws[ServletException] + @throws[IOException] + override protected def doGet(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + val input = req.getParameter("input") + val c = new Cookie("name", null) + // ruleid: scala_cookie_rule-HttpResponseSplitting + c.setValue(input) + c.setHttpOnly(true) + c.setSecure(true) + resp.addCookie(c) + } + // {/fact} + + // {fact rule=http-response-splitting@v1.0 defects=1} + @throws[ServletException] + @throws[IOException] + override protected def doPost(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + val input = req.getParameter("input") + // ruleid: scala_cookie_rule-HttpResponseSplitting + val c = new Cookie("name", input) + c.setHttpOnly(true) + c.setSecure(true) + resp.addCookie(c) + } + // {/fact} + + // {fact rule=http-response-splitting@v1.0 defects=1} + @throws[ServletException] + @throws[IOException] + override protected def doDelete(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + val data = req.getParameter("input") + val input = data.replaceAll("\n", "") + // ruleid: scala_cookie_rule-HttpResponseSplitting + val c = new Cookie("name", input) + c.setHttpOnly(true) + c.setSecure(true) + resp.addCookie(c) + } + // {/fact} + + // {fact rule=http-response-splitting@v1.0 defects=0} + @throws[ServletException] + @throws[IOException] + override protected def doOptions(req: HttpServletRequest, resp: HttpServletResponse): Unit = { // BAD + val tainted = req.getParameter("input") + resp.setHeader("test", tainted) + // OK: False negative but reported by spotbugs + val data = req.getParameter("input") + val normalized = data.replaceAll("\n", "\n") + resp.setHeader("test", normalized) + val normalized2 = data.replaceAll("\n", req.getParameter("test")) + resp.setHeader("test2", normalized2) + // OK + val normalized3 = org.apache.commons.text.StringEscapeUtils.unescapeJava(tainted) + resp.setHeader("test3", normalized3) + val normalized4 = getString(tainted) + resp.setHeader("test4", normalized4) + val wrapper = new HttpServletResponseWrapper(resp) + wrapper.addHeader("test", tainted) + wrapper.setHeader("test2", tainted) + } + // {/fact} + + private def getString(s: String) = s +} diff --git a/PR_6_scala/scala/cookie/rule-HttpResponseSplitting.yml b/PR_6_scala/scala/cookie/rule-HttpResponseSplitting.yml new file mode 100644 index 0000000..f48c697 --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-HttpResponseSplitting.yml @@ -0,0 +1,42 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_cookie_rule-HttpResponseSplitting" + languages: + - "scala" + message: | + When an HTTP request contains unexpected CR and LF characters, the server may respond with an + output stream that is interpreted as two different HTTP responses (instead of one). An attacker + can control the second response and mount attacks such as cross-site scripting and cache + poisoning attacks. + metadata: + category: "security" + cwe: "CWE-113" + shortDescription: "Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP + Response Splitting')" + technology: + - "scala" + security-severity: "MEDIUM" + mode: "taint" + pattern-sanitizers: + - patterns: + - pattern-inside: |- + $STR.replaceAll("$REPLACE_CHAR", "$REPLACE"); + - pattern: "$STR" + - metavariable-regex: + metavariable: "$REPLACE_CHAR" + regex: "(.*\\\\r\\\\n.*)" + - metavariable-regex: + metavariable: "$REPLACE" + regex: "(?!(\\\\r\\\\n))" + - pattern: "org.owasp.encoder.Encode.forUriComponent(...)" + - pattern: "org.owasp.encoder.Encode.forUri(...)" + - pattern: "java.net.URLEncoder.encode(..., $CHARSET)" + pattern-sinks: + - pattern: "new javax.servlet.http.Cookie(\"$KEY\", ...)" + - pattern: "($C:javax.servlet.http.Cookie).setValue(...)" + pattern-sources: + - pattern: "($REQ: javax.servlet.http.HttpServletRequest).getParameter(...)" + severity: "WARNING" diff --git a/PR_6_scala/scala/cookie/rule-RequestParamToCookie.yml b/PR_6_scala/scala/cookie/rule-RequestParamToCookie.yml new file mode 100644 index 0000000..19f2e55 --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-RequestParamToCookie.yml @@ -0,0 +1,45 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_cookie_rule-RequestParamToCookie" + languages: + - "scala" + message: | + This code constructs an HTTP Cookie using an untrusted HTTP parameter. If this cookie is added + to an HTTP response, it will allow a HTTP response splitting vulnerability. See + http://en.wikipedia.org/wiki/HTTP_response_splitting for more information. + metadata: + category: "security" + cwe: "CWE-113" + shortDescription: "Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP + Response Splitting')" + technology: + - "scala" + security-severity: "CRITICAL" + mode: "taint" + pattern-sanitizers: + - patterns: + - pattern-inside: |- + $STR.replaceAll("$REPLACE_CHAR", "$REPLACE"); + - pattern: "$STR" + - metavariable-regex: + metavariable: "$REPLACE_CHAR" + regex: "(.*\\\\r\\\\n.*)" + - metavariable-regex: + metavariable: "$REPLACE" + regex: "(?!(\\\\r\\\\n))" + - pattern: "org.owasp.encoder.Encode.forUriComponent(...)" + - pattern: "org.owasp.encoder.Encode.forUri(...)" + - pattern: "java.net.URLEncoder.encode(..., $CHARSET)" + pattern-sinks: + - pattern: "new javax.servlet.http.Cookie(\"$KEY\", ...);" + - patterns: + - pattern-inside: | + $C = new javax.servlet.http.Cookie("$KEY", ...); + ... + - pattern: "$C.setValue(...);" + pattern-sources: + - pattern: "($REQ: HttpServletRequest).getParameter(...);" + severity: "ERROR" diff --git a/PR_6_scala/scala/cookie/rule-RequestParamToHeader.yml b/PR_6_scala/scala/cookie/rule-RequestParamToHeader.yml new file mode 100644 index 0000000..6cf7124 --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-RequestParamToHeader.yml @@ -0,0 +1,43 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_cookie_rule-RequestParamToHeader" + languages: + - "scala" + message: | + This code directly writes an HTTP parameter to an HTTP header, which allows for a HTTP + response splitting vulnerability. See http://en.wikipedia.org/wiki/HTTP_response_splitting for + more information. + metadata: + category: "security" + cwe: "CWE-113" + shortDescription: "Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP + Response Splitting')" + technology: + - "scala" + security-severity: "CRITICAL" + mode: "taint" + pattern-sanitizers: + - patterns: + - pattern-inside: |- + $STR.replaceAll("$REPLACE_CHAR", "$REPLACE"); + - pattern: "$STR" + - metavariable-regex: + metavariable: "$REPLACE_CHAR" + regex: "(.*\\\\r\\\\n.*)" + - metavariable-regex: + metavariable: "$REPLACE" + regex: "(?!(\\\\r\\\\n))" + - pattern: "org.owasp.encoder.Encode.forUriComponent(...)" + - pattern: "org.owasp.encoder.Encode.forUri(...)" + - pattern: "java.net.URLEncoder.encode(..., $CHARSET)" + pattern-sinks: + - pattern: "($RES: HttpServletResponse).setHeader(\"$KEY\", ...);" + - pattern: "($RES: HttpServletResponse).addHeader(\"$KEY\", ...);" + - pattern: "($WRP: HttpServletResponseWrapper).setHeader(\"$KEY\", ...);" + - pattern: "($WRP: HttpServletResponseWrapper).addHeader(\"$KEY\", ...);" + pattern-sources: + - pattern: "($REQ: HttpServletRequest).getParameter(...);" + severity: "ERROR" diff --git a/PR_6_scala/scala/cookie/rule-TrustBoundaryViolation.scala b/PR_6_scala/scala/cookie/rule-TrustBoundaryViolation.scala new file mode 100644 index 0000000..97a343c --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-TrustBoundaryViolation.scala @@ -0,0 +1,61 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package cookie + +import javax.servlet.http.HttpServletRequest + + +class TrustBoundaryViolation { //Taint input + // {fact rule=resource-leak@v1.0 defects=1} + def setSessionAttributeNameTainted(req: HttpServletRequest): Unit = { + val input = req.getParameter("input") + // ruleid: scala_cookie_rule-TrustBoundaryViolation + req.getSession.setAttribute(input, "true") + } + // {/fact} + + // {fact rule=resource-leak@v1.0 defects=1} + def setSessionAttributeValueTainted(req: HttpServletRequest): Unit = { + val input = req.getParameter("input") + // ruleid: scala_cookie_rule-TrustBoundaryViolation + req.getSession.setAttribute("user", input) + } + // {/fact} + + // {fact rule=resource-leak@v1.0 defects=1} + //Unknown source + def setSessionAttributeNameUnknownSource(req: HttpServletRequest, input: String): Unit = { + // ruleid: scala_cookie_rule-TrustBoundaryViolation + req.getSession.setAttribute(input, "true") + } + // {/fact} + + // {fact rule=resource-leak@v1.0 defects=1} + def setSessionAttributeValueUnknownSource(req: HttpServletRequest, input: String): Unit = { + // ruleid: scala_cookie_rule-TrustBoundaryViolation + req.getSession.setAttribute("user", input) //Reported as low + } + // {/fact} + + // {fact rule=resource-leak@v1.0 defects=1} + //Legacy api + def setSessionAttributeNameUnknownSourceLegacy(req: HttpServletRequest, input: String): Unit = { + // ruleid: scala_cookie_rule-TrustBoundaryViolation + req.getSession.putValue(input, "true") + } + // {/fact} + + // {fact rule=resource-leak@v1.0 defects=1} + def setSessionAttributeValueUnknownSourceLegacy(req: HttpServletRequest, input: String): Unit = { + // ruleid: scala_cookie_rule-TrustBoundaryViolation + req.getSession.putValue("user", input) + } + // {/fact} + + // {fact rule=resource-leak@v1.0 defects=0} + //Safe + def setSessionAttributeSafe(req: HttpServletRequest, input: String): Unit = { + if ("enable".equals(input)) req.getSession.setAttribute("user", "true") + else req.getSession.setAttribute("user", "false") + } + // {/fact} +} diff --git a/PR_6_scala/scala/cookie/rule-TrustBoundaryViolation.yml b/PR_6_scala/scala/cookie/rule-TrustBoundaryViolation.yml new file mode 100644 index 0000000..01358d5 --- /dev/null +++ b/PR_6_scala/scala/cookie/rule-TrustBoundaryViolation.yml @@ -0,0 +1,31 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_cookie_rule-TrustBoundaryViolation" + languages: + - "scala" + message: | + A trust boundary can be thought of as line drawn through a program. On one side + of the line, data is untrusted. On the other side of the line, data is assumed + to be trustworthy. The purpose of validation logic is to allow data to safely + cross the trust boundary - to move from untrusted to trusted. A trust boundary + violation occurs when a program blurs the line between what is trusted and what + is untrusted. By combining trusted and untrusted data in the same data + structure, it becomes easier for programmers to mistakenly trust unvalidated + data. + metadata: + category: "security" + cwe: "CWE-501" + shortDescription: "Trust Boundary Violation" + security-severity: "MEDIUM" + patterns: + - pattern-either: + - patterns: + - pattern: "($H: HttpServletRequest). ... .setAttribute($ARG1, $ARG2)" + - pattern-not: "($H: HttpServletRequest). ... .setAttribute(\"...\", \"...\")" + - patterns: + - pattern: "($H: HttpServletRequest). ... .putValue($ARG1, $ARG2)" + - pattern-not: "($H: HttpServletRequest). ... .putValue(\"...\", \"...\")" + severity: "WARNING" diff --git a/PR_6_scala/scala/cors/rule-PermissiveCORS.scala b/PR_6_scala/scala/cors/rule-PermissiveCORS.scala new file mode 100644 index 0000000..32abda6 --- /dev/null +++ b/PR_6_scala/scala/cors/rule-PermissiveCORS.scala @@ -0,0 +1,71 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package cors + +import javax.servlet.ServletException +import javax.servlet.http.HttpServlet +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse +import java.io.IOException + +class PermissiveCORS extends HttpServlet { + override protected def doGet(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + falsePositiveCORS(resp) + resp.getWriter.print(req.getSession.getAttribute("secret")) + } + + private def falsePositiveCORS(resp: HttpServletResponse): Unit = { + resp.addHeader("Access-Control-Allow-Origin", "http://example.com") // OK + } + + // Overly permissive Cross-domain requests accepted + // {fact rule=insecure-cors-policy@v1.0 defects=1} + def addPermissiveCORS(resp: HttpServletResponse): Unit = { + // ruleid: scala_cors_rule-PermissiveCORS + resp.addHeader("Access-Control-Allow-Origin", "*") // BAD + + } + // {/fact} + + // {fact rule=insecure-cors-policy@v1.0 defects=1} + def addPermissiveCORS2(resp: HttpServletResponse): Unit = { + // ruleid: scala_cors_rule-PermissiveCORS + resp.addHeader("access-control-allow-origin", "*") + } + // {/fact} + + // {fact rule=insecure-cors-policy@v1.0 defects=1} + def addWildcardsCORS(resp: HttpServletResponse): Unit = { + // ruleid: scala_cors_rule-PermissiveCORS + resp.addHeader("Access-Control-Allow-Origin", "*.example.com") + } + // {/fact} + + // {fact rule=insecure-cors-policy@v1.0 defects=1} + def addNullCORS(resp: HttpServletResponse): Unit = { + // ruleid: scala_cors_rule-PermissiveCORS + resp.addHeader("Access-Control-Allow-Origin", "null") + } + // {/fact} + + // {fact rule=insecure-cors-policy@v1.0 defects=1} + def setPermissiveCORS(resp: HttpServletResponse): Unit = { + // ruleid: scala_cors_rule-PermissiveCORS + resp.setHeader("Access-Control-Allow-Origin", "*") + } + // {/fact} + + // {fact rule=insecure-cors-policy@v1.0 defects=1} + def setPermissiveCORSWithRequestVariable(resp: HttpServletResponse, req: HttpServletRequest): Unit = { + // ruleid: scala_cors_rule-PermissiveCORS + resp.setHeader("Access-Control-Allow-Origin", req.getParameter("tainted")) + } + // {/fact} + + // {fact rule=insecure-cors-policy@v1.0 defects=1} + def setPermissiveCORSWithRequestVariable2(resp: HttpServletResponse, req: HttpServletRequest): Unit = { + val header = req.getParameter("tainted") + // ruleid: scala_cors_rule-PermissiveCORS + resp.addHeader("access-control-allow-origin", header) + } + // {/fact} +} diff --git a/PR_6_scala/scala/cors/rule-PermissiveCORS.yml b/PR_6_scala/scala/cors/rule-PermissiveCORS.yml new file mode 100644 index 0000000..a80ac4c --- /dev/null +++ b/PR_6_scala/scala/cors/rule-PermissiveCORS.yml @@ -0,0 +1,56 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_cors_rule-PermissiveCORS" + languages: + - "scala" + message: | + Prior to HTML5, Web browsers enforced the Same Origin Policy which ensures that in order for + JavaScript to access the contents of a Web page, both the JavaScript and the Web page must + originate from the same domain. Without the Same Origin Policy, a malicious website could serve + up JavaScript that loads sensitive information from other websites using a client's + credentials, cull through it, and communicate it back to the attacker. HTML5 makes it possible + for JavaScript to access data across domains if a new HTTP header called + Access-Control-Allow-Origin is defined. With this header, a Web server defines which other + domains are allowed to access its domain using cross-origin requests. However, caution should + be taken when defining the header because an overly permissive CORS policy will allow a + malicious application to communicate with the victim application in an inappropriate way, + leading to spoofing, data theft, relay and other attacks. + metadata: + category: "security" + cwe: "CWE-942" + shortDescription: "Permissive Cross-domain Policy with Untrusted Domains" + technology: + - "scala" + security-severity: "CRITICAL" + pattern-either: + - patterns: + - pattern-either: + - pattern: "($RESP:javax.servlet.http.HttpServletResponse).setHeader(\"$HEADER\", + \"$VAL\")" + - pattern: "($RESP:javax.servlet.http.HttpServletResponse).addHeader(\"$HEADER\", + \"$VAL\")" + - metavariable-regex: + metavariable: "$HEADER" + regex: "(?i)(Access-Control-Allow-Origin)" + - metavariable-regex: + metavariable: "$VAL" + regex: "(\\*|null)" + - patterns: + - pattern-inside: | + $REQVAL = ($REQ: javax.servlet.http.HttpServletRequest).getParameter(...) + ... + - pattern-either: + - pattern-inside: |- + ($RESP:javax.servlet.http.HttpServletResponse).setHeader("$HEADER", $REQVAL) + - pattern-inside: |- + ($RESP:javax.servlet.http.HttpServletResponse).addHeader("$HEADER", $REQVAL) + - patterns: + - pattern-either: + - pattern-inside: |- + ($RESP:javax.servlet.http.HttpServletResponse).setHeader("$HEADER",($REQ: javax.servlet.http.HttpServletRequest).getParameter(...)) + - pattern-inside: |- + ($RESP:javax.servlet.http.HttpServletResponse).addHeader("$HEADER",($REQ: javax.servlet.http.HttpServletRequest).getParameter(...)) + severity: "ERROR" diff --git a/PR_6_scala/scala/cors/rule-PermissiveCORSInjection.scala b/PR_6_scala/scala/cors/rule-PermissiveCORSInjection.scala new file mode 100644 index 0000000..cce7f72 --- /dev/null +++ b/PR_6_scala/scala/cors/rule-PermissiveCORSInjection.scala @@ -0,0 +1,2 @@ +// License: MIT (c) GitLab Inc. +// TODO: Placeholder file; add example code. \ No newline at end of file diff --git a/PR_6_scala/scala/cors/rule-PermissiveCORSInjection.yml b/PR_6_scala/scala/cors/rule-PermissiveCORSInjection.yml new file mode 100644 index 0000000..fd0a9de --- /dev/null +++ b/PR_6_scala/scala/cors/rule-PermissiveCORSInjection.yml @@ -0,0 +1,39 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_cors_rule-PermissiveCORSInjection" + languages: + - "java" + message: | + Prior to HTML5, Web browsers enforced the Same Origin Policy which ensures that in order for + JavaScript to access the contents of a Web page, both the JavaScript and the Web page must + originate from the same domain. Without the Same Origin Policy, a malicious website could serve + up JavaScript that loads sensitive information from other websites using a client's + credentials, cull through it, and communicate it back to the attacker. HTML5 makes it possible + for JavaScript to access data across domains if a new HTTP header called + Access-Control-Allow-Origin is defined. With this header, a Web server defines which other + domains are allowed to access its domain using cross-origin requests. However, caution should + be taken when defining the header because an overly permissive CORS policy will allow a + malicious application to communicate with the victim application in an inappropriate way, + leading to spoofing, data theft, relay and other attacks. + metadata: + category: "security" + cwe: "CWE-942" + shortDescription: "Permissive Cross-domain Policy with Untrusted Domains" + technology: + - "java" + security-severity: "CRITICAL" + mode: "taint" + pattern-sinks: + - patterns: + - pattern-either: + - pattern: "(HttpServletResponse $RES).setHeader(\"$HEADER\", ...)" + - pattern: "(HttpServletResponse $RES).addHeader(\"$HEADER\", ...)" + - metavariable-regex: + metavariable: "$HEADER" + regex: "(?i)(Access-Control-Allow-Origin)" + pattern-sources: + - pattern: "(HttpServletRequest $REQ).getParameter(...)" + severity: "ERROR" diff --git a/PR_6_scala/scala/crypto/rule-BlowfishKeySize.scala b/PR_6_scala/scala/crypto/rule-BlowfishKeySize.scala new file mode 100644 index 0000000..a21aa1b --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-BlowfishKeySize.scala @@ -0,0 +1,16 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package crypto + +import javax.crypto.KeyGenerator +import java.security.NoSuchAlgorithmException + +class BlowfishKeySize { + // {fact rule=insecure-cryptography@v1.0 defects=1} + @throws[NoSuchAlgorithmException] + def danger(): Unit = { + // ruleid: scala_crypto_rule-BlowfishKeySize + val keyGen = KeyGenerator.getInstance("Blowfish") + keyGen.init(64) + } + // {/fact} +} diff --git a/PR_6_scala/scala/crypto/rule-BlowfishKeySize.yml b/PR_6_scala/scala/crypto/rule-BlowfishKeySize.yml new file mode 100644 index 0000000..2e14c0d --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-BlowfishKeySize.yml @@ -0,0 +1,27 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-BlowfishKeySize" + languages: + - "scala" + message: | + A small key size makes the ciphertext vulnerable to brute force attacks. At least 128 bits of + entropy should be used when generating the key if use of Blowfish is required. + metadata: + category: "security" + cwe: "CWE-326" + shortDescription: "Inadequate Encryption Strength" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern-inside: | + $KEYGEN = javax.crypto.KeyGenerator.getInstance("Blowfish", ...); + ... + $KEYGEN.init($KEY_SIZE); + - metavariable-comparison: + comparison: "$KEY_SIZE < 128" + metavariable: "$KEY_SIZE" + severity: "WARNING" diff --git a/PR_6_scala/scala/crypto/rule-CipherCommon.scala b/PR_6_scala/scala/crypto/rule-CipherCommon.scala new file mode 100644 index 0000000..5033caa --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-CipherCommon.scala @@ -0,0 +1,69 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package crypto + +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException +import java.security.InvalidKeyException +import java.security.Key +import java.security.NoSuchAlgorithmException + + +class CipherCommon { // Detects: CIPHER_INTEGRITY, PADDING_ORACLE + @throws[NoSuchPaddingException] + @throws[NoSuchAlgorithmException] + @throws[IllegalBlockSizeException] + @throws[BadPaddingException] + @throws[InvalidKeyException] + def noIntegrityAndOraclePaddingAttack(key: Key, plainText: Array[Byte]): Unit = { + // {fact rule=insecure-cryptography@v1.0 defects=1} + val c = Cipher.getInstance("AES/CBC/PKCS5Padding") + // {/fact} + c.init(Cipher.ENCRYPT_MODE, key) + val cipherText = c.doFinal(plainText) + } + + // Detects: CIPHER_INTEGRITY, ECB_MODE + @throws[NoSuchPaddingException] + @throws[NoSuchAlgorithmException] + @throws[IllegalBlockSizeException] + @throws[BadPaddingException] + @throws[InvalidKeyException] + def noIntegrity(key: Key, plainText: Array[Byte]): Unit = { + // {fact rule=insecure-cryptography@v1.0 defects=1} + val c = Cipher.getInstance("DESede/ECB/PKCS5Padding") + // {/fact} + c.init(Cipher.ENCRYPT_MODE, key) + val cipherText = c.doFinal(plainText) + } + + // Detects: CIPHER_INTEGRITY, DES_USAGE + @throws[NoSuchPaddingException] + @throws[NoSuchAlgorithmException] + @throws[IllegalBlockSizeException] + @throws[BadPaddingException] + @throws[InvalidKeyException] + def noIntegrityAndDESUsage(key: Key, plainText: Array[Byte]): Unit = { + // {fact rule=insecure-cryptography@v1.0 defects=0} + val c = Cipher.getInstance("DES/GCM/PKCS5Padding") + // {/fact} + c.init(Cipher.ENCRYPT_MODE, key) + val cipherText = c.doFinal(plainText) + } + + + // Detects: CIPHER_INTEGRITY, TDES_USAGE + @throws[NoSuchPaddingException] + @throws[NoSuchAlgorithmException] + @throws[IllegalBlockSizeException] + @throws[BadPaddingException] + @throws[InvalidKeyException] + def noIntegrityAndDESedeUsage(key: Key, plainText: Array[Byte]): Unit = { + // {fact rule=insecure-cryptography@v1.0 defects=1} + val c = Cipher.getInstance("DESede/CBC/PKCS5Padding") + // {/fact} + c.init(Cipher.ENCRYPT_MODE, key) + val cipherText = c.doFinal(plainText) + } +} diff --git a/PR_6_scala/scala/crypto/rule-CipherDESInsecure.yml b/PR_6_scala/scala/crypto/rule-CipherDESInsecure.yml new file mode 100644 index 0000000..1e80b83 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-CipherDESInsecure.yml @@ -0,0 +1,25 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-CipherDESInsecure" + languages: + - "scala" + message: | + DES is considered strong ciphers for modern applications. Currently, NIST recommends the usage + of AES block ciphers instead of DES. + metadata: + category: "security" + cwe: "CWE-326" + shortDescription: "Inadequate Encryption Strength" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern-inside: |- + javax.crypto.Cipher.getInstance("$ALG") + - metavariable-regex: + metavariable: "$ALG" + regex: "^(DES)/.*" + severity: "WARNING" diff --git a/PR_6_scala/scala/crypto/rule-CipherDESedeInsecure.yml b/PR_6_scala/scala/crypto/rule-CipherDESedeInsecure.yml new file mode 100644 index 0000000..6b6c58f --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-CipherDESedeInsecure.yml @@ -0,0 +1,25 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-CipherDESedeInsecure" + languages: + - "scala" + message: | + Triple DES (also known as 3DES or DESede) is considered strong ciphers for modern + applications. NIST recommends the usage of AES block ciphers instead of 3DES. + metadata: + category: "security" + cwe: "CWE-326" + shortDescription: "Inadequate Encryption Strength" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern-inside: |- + javax.crypto.Cipher.getInstance("$ALG") + - metavariable-regex: + metavariable: "$ALG" + regex: "^(DESede)/.*" + severity: "WARNING" diff --git a/PR_6_scala/scala/crypto/rule-CipherECBMode.yml b/PR_6_scala/scala/crypto/rule-CipherECBMode.yml new file mode 100644 index 0000000..2ffda91 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-CipherECBMode.yml @@ -0,0 +1,25 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-CipherECBMode" + languages: + - "scala" + message: | + An authentication cipher mode which provides better confidentiality of the encrypted data + should be used instead of Electronic Code Book (ECB) mode, which does not provide good + confidentiality. Specifically, ECB mode produces the same output for the same input each time. + This allows an attacker to intercept and replay the data. + metadata: + category: "security" + cwe: "CWE-326" + shortDescription: "Inadequate Encryption Strength" + technology: + - "scala" + security-severity: "CRITICAL" + patterns: + - pattern-inside: |- + javax.crypto.Cipher.getInstance("...") + - pattern-regex: "(AES|DES(ede)?)(/ECB/*)" + severity: "ERROR" diff --git a/PR_6_scala/scala/crypto/rule-CipherIntegrity.scala b/PR_6_scala/scala/crypto/rule-CipherIntegrity.scala new file mode 100644 index 0000000..cce7f72 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-CipherIntegrity.scala @@ -0,0 +1,2 @@ +// License: MIT (c) GitLab Inc. +// TODO: Placeholder file; add example code. \ No newline at end of file diff --git a/PR_6_scala/scala/crypto/rule-CipherIntegrity.yml b/PR_6_scala/scala/crypto/rule-CipherIntegrity.yml new file mode 100644 index 0000000..263815b --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-CipherIntegrity.yml @@ -0,0 +1,32 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-CipherIntegrity" + languages: + - "scala" + message: | + The ciphertext produced is susceptible to alteration by an adversary. This mean that the + cipher provides no way to detect that the data has been tampered with. If the ciphertext can be + controlled by an attacker, it could be altered without detection. + metadata: + category: "security" + cwe: "CWE-353" + shortDescription: "Missing Support for Integrity Check" + technology: + - "scala" + security-severity: "CRITICAL" + patterns: + - pattern-inside: |- + javax.crypto.Cipher.getInstance("...") + - pattern-either: + - pattern-regex: "(/CBC/PKCS5Padding)" + - pattern-regex: "(AES|DES(ede)?)(/ECB/*)" + - pattern-regex: "(AES|DES(ede)?)(/CBC/*)" + - pattern-regex: "(AES|DES(ede)?)(/OFB/*)" + - pattern-regex: "(AES|DES(ede)?)(/CTR/*)" + - pattern-not-regex: ".*/(CCM|CWC|OCB|EAX|GCM)/.*" + - pattern-not-regex: "^(RSA)/.*" + - pattern-not-regex: "^(ECIES)$" + severity: "ERROR" diff --git a/PR_6_scala/scala/crypto/rule-CipherPaddingOracle.scala b/PR_6_scala/scala/crypto/rule-CipherPaddingOracle.scala new file mode 100644 index 0000000..cce7f72 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-CipherPaddingOracle.scala @@ -0,0 +1,2 @@ +// License: MIT (c) GitLab Inc. +// TODO: Placeholder file; add example code. \ No newline at end of file diff --git a/PR_6_scala/scala/crypto/rule-CipherPaddingOracle.yml b/PR_6_scala/scala/crypto/rule-CipherPaddingOracle.yml new file mode 100644 index 0000000..9791007 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-CipherPaddingOracle.yml @@ -0,0 +1,27 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-CipherPaddingOracle" + languages: + - "scala" + message: | + This specific mode of CBC with PKCS5Padding is susceptible to padding oracle attacks. An + adversary could potentially decrypt the message if the system exposed the difference between + plaintext with invalid padding or valid padding. The distinction between valid and invalid + padding is usually revealed through distinct error messages being returned for each condition. + metadata: + category: "security" + cwe: "CWE-696" + shortDescription: "Incorrect Behavior Order" + technology: + - "scala" + security-severity: "CRITICAL" + patterns: + - pattern-inside: |- + javax.crypto.Cipher.getInstance("...") + - pattern-regex: "(/CBC/PKCS5Padding)" + - pattern-not-regex: "^(RSA)/.*" + - pattern-not-regex: "^(ECIES)$" + severity: "ERROR" diff --git a/PR_6_scala/scala/crypto/rule-CustomMessageDigest.scala b/PR_6_scala/scala/crypto/rule-CustomMessageDigest.scala new file mode 100644 index 0000000..1ef29b3 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-CustomMessageDigest.scala @@ -0,0 +1,36 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package crypto + +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.security.MessageDigest +import java.util._ + + +// {fact rule=insecure-cryptography@v1.0 defects=1} +// ruleid: scala_crypto_rule-CustomMessageDigest +class CustomMessageDigest extends MessageDigest("WEAK") { + private val buffer = new ByteArrayOutputStream + + override protected def engineUpdate(input: Byte): Unit = { + buffer.write(input) + } + + @Override protected def engineUpdate(input: Array[Byte], offset: Int, len: Int): Unit = { + try buffer.write(input) + catch { + case e: IOException => + throw new RuntimeException(e) + } + } + + override protected def engineDigest = { + val content = buffer.toByteArray + Arrays.copyOf(content, 8) + } + + override protected def engineReset(): Unit = { + buffer.reset + } +} +// {/fact} diff --git a/PR_6_scala/scala/crypto/rule-CustomMessageDigest.yml b/PR_6_scala/scala/crypto/rule-CustomMessageDigest.yml new file mode 100644 index 0000000..b80f029 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-CustomMessageDigest.yml @@ -0,0 +1,25 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-CustomMessageDigest" + languages: + - "scala" + message: | + Implementing a custom MessageDigest is error-prone. National Institute of Standards and + Technology(NIST) recommends the use of SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, or + SHA-512/256. + metadata: + category: "security" + cwe: "CWE-327" + shortDescription: "Use of a Broken or Risky Cryptographic Algorithm" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern: | + class $CLAZZ extends java.security.MessageDigest(...) { + ... + } + severity: "WARNING" diff --git a/PR_6_scala/scala/crypto/rule-DefaultHTTPClient.scala b/PR_6_scala/scala/crypto/rule-DefaultHTTPClient.scala new file mode 100644 index 0000000..522a401 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-DefaultHTTPClient.scala @@ -0,0 +1,21 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package crypto + +import org.apache.http.client.HttpClient +import org.apache.http.client.methods.HttpGet +import org.apache.http.client.methods.HttpUriRequest +import org.apache.http.impl.client.DefaultHttpClient +import java.io.IOException + + +class DefaultHTTPClient { + // {fact rule=insecure-cryptography@v1.0 defects=1} + @throws[IOException] + def danger(): Unit = { + // ruleid: scala_crypto_rule-DefaultHTTPClient + val client = new DefaultHttpClient + val request = new HttpGet("https://test.com") + client.execute(request) + } + // {/fact} +} diff --git a/PR_6_scala/scala/crypto/rule-DefaultHTTPClient.yml b/PR_6_scala/scala/crypto/rule-DefaultHTTPClient.yml new file mode 100644 index 0000000..4023bd4 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-DefaultHTTPClient.yml @@ -0,0 +1,20 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-DefaultHTTPClient" + patterns: + - pattern: "new org.apache.http.impl.client.DefaultHttpClient(...)" + message: | + DefaultHttpClient with default constructor is not compatible with TLS 1.2 + languages: + - "scala" + severity: "WARNING" + metadata: + shortDescription: "Inadequate encryption strength" + category: "security" + cwe: "CWE-326" + technology: + - "scala" + security-severity: "MEDIUM" diff --git a/PR_6_scala/scala/crypto/rule-HazelcastSymmetricEncryption.scala b/PR_6_scala/scala/crypto/rule-HazelcastSymmetricEncryption.scala new file mode 100644 index 0000000..455abfe --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-HazelcastSymmetricEncryption.scala @@ -0,0 +1,43 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +// scaffold: dependencies=com.hazelcast.hazelcast@3.12.12 +package crypto + +import com.hazelcast.config.Config +import com.hazelcast.config.MapConfig +import com.hazelcast.config.NetworkConfig +import com.hazelcast.config.SymmetricEncryptionConfig +import com.hazelcast.core.Hazelcast +import com.hazelcast.core.IMap + + +class HazelcastSymmetricEncryption { + var cacheMap: IMap[String, String] = null + def init(): Unit = { //Specific map time to live + val myMapConfig = new MapConfig() + myMapConfig.setName("cachetest") + myMapConfig.setTimeToLiveSeconds(10) + //Package config + val myConfig = new Config() + // {fact rule=insecure-cryptography@v1.0 defects=1} + //Symmetric Encryption + // ruleid: scala_crypto_rule-HazelcastSymmetricEncryption + val symmetricEncryptionConfig = new SymmetricEncryptionConfig + symmetricEncryptionConfig.setAlgorithm("DESede") + symmetricEncryptionConfig.setSalt("saltysalt") + symmetricEncryptionConfig.setPassword("lamepassword") + symmetricEncryptionConfig.setIterationCount(1337) + // {/fact} + //Weak Network config.. + val networkConfig = new NetworkConfig() + networkConfig.setSymmetricEncryptionConfig(symmetricEncryptionConfig) + myConfig.setNetworkConfig(networkConfig) + Hazelcast.newHazelcastInstance(myConfig) + cacheMap = Hazelcast.getOrCreateHazelcastInstance.getMap("cachetest") + } + + def put(key: String, value: String): Unit = { + cacheMap.put(key, value) + } + + def get(key: String) = cacheMap.get(key) +} diff --git a/PR_6_scala/scala/crypto/rule-HazelcastSymmetricEncryption.yml b/PR_6_scala/scala/crypto/rule-HazelcastSymmetricEncryption.yml new file mode 100644 index 0000000..2eca604 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-HazelcastSymmetricEncryption.yml @@ -0,0 +1,22 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-HazelcastSymmetricEncryption" + languages: + - "scala" + message: | + The network communications for Hazelcast is configured to use a symmetric cipher (probably DES + or Blowfish). Those ciphers alone do not provide integrity or secure authentication. The use of + asymmetric encryption is preferred. + metadata: + category: "security" + cwe: "CWE-326" + shortDescription: "Inadequate Encryption Strength" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern: "new com.hazelcast.config.SymmetricEncryptionConfig()" + severity: "WARNING" diff --git a/PR_6_scala/scala/crypto/rule-InsufficientKeySizeRsa.scala b/PR_6_scala/scala/crypto/rule-InsufficientKeySizeRsa.scala new file mode 100644 index 0000000..c03405f --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-InsufficientKeySizeRsa.scala @@ -0,0 +1,126 @@ +// License: LGPL-3.0 License (c) find-sec-bugs + +package crypto + +import java.security._ +import java.security.spec.RSAKeyGenParameterSpec + + +/** + * The key size might need to be adjusted in the future. + * http://en.wikipedia.org/wiki/Key_size#Asymmetric_algorithm_key_lengths + */ +class InsufficientKeySizeRsa { + // {fact rule=insecure-cryptography@v1.0 defects=1} + @throws[NoSuchAlgorithmException] + def weakKeySize1 = { + val keyGen = KeyPairGenerator.getInstance("RSA") + // ruleid: scala_crypto_rule-InsufficientKeySizeRsa + keyGen.initialize(512) //BAD + + keyGen.generateKeyPair + } + // {/fact} + + // {fact rule=insecure-cryptography@v1.0 defects=1} + @throws[NoSuchAlgorithmException] + def weakKeySize2 = { + val keyGen = KeyPairGenerator.getInstance("RSA") + // ruleid: scala_crypto_rule-InsufficientKeySizeRsa + keyGen.initialize(128, new SecureRandom) //BAD //Different signature + + keyGen.generateKeyPair + } + // {/fact} + + // {fact rule=insecure-cryptography@v1.0 defects=1} + @throws[NoSuchAlgorithmException] + @throws[InvalidAlgorithmParameterException] + def weakKeySize3ParameterSpec = { + val keyGen = KeyPairGenerator.getInstance("RSA") + // ruleid: scala_crypto_rule-InsufficientKeySizeRsa + keyGen.initialize(new RSAKeyGenParameterSpec(128, RSAKeyGenParameterSpec.F4)) + val key = keyGen.generateKeyPair + key + } + // {/fact} + + // {fact rule=insecure-cryptography@v1.0 defects=1} + @throws[NoSuchAlgorithmException] + @throws[InvalidAlgorithmParameterException] + def weakKeySize4ParameterSpec = { + val keyGen = KeyPairGenerator.getInstance("RSA") + // ruleid: scala_crypto_rule-InsufficientKeySizeRsa + keyGen.initialize(new RSAKeyGenParameterSpec(128, RSAKeyGenParameterSpec.F4), new SecureRandom) + val key = keyGen.generateKeyPair + key + } + // {/fact} + + // {fact rule=insecure-cryptography@v1.0 defects=1} + @throws[NoSuchAlgorithmException] + def weakKeySize5Recommended = { + val keyGen = KeyPairGenerator.getInstance("RSA") + // ruleid: scala_crypto_rule-InsufficientKeySizeRsa + keyGen.initialize(1024) //BAD with lower priority + + keyGen.generateKeyPair + } + // {/fact} + + @throws[NoSuchAlgorithmException] + @throws[InvalidAlgorithmParameterException] + def okKeySizeParameterSpec = { + val keyGen = KeyPairGenerator.getInstance("RSA") + keyGen.initialize(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4)) //Different signature + + keyGen.generateKeyPair + } + + // {fact rule=insecure-cryptography@v1.0 defects=1} + @throws[NoSuchAlgorithmException] + @throws[NoSuchProviderException] + def weakKeySizeWithProviderString = { + val keyGen = KeyPairGenerator.getInstance("RSA", "BC") + // ruleid: scala_crypto_rule-InsufficientKeySizeRsa + keyGen.initialize(1024) + keyGen.generateKeyPair + } + // {/fact} + + // {fact rule=insecure-cryptography@v1.0 defects=1} + @throws[NoSuchAlgorithmException] + def weakKeySizeWithProviderObject1 = { + val keyGen = KeyPairGenerator.getInstance("RSA") + // ruleid: scala_crypto_rule-InsufficientKeySizeRsa + keyGen.initialize(1024) + keyGen.generateKeyPair + } + // {/fact} + + // {fact rule=insecure-cryptography@v1.0 defects=1} + @throws[NoSuchAlgorithmException] + def weakKeySizeWithProviderObject2 = { + val p = new ExampleProvider("info") + val keyGen = KeyPairGenerator.getInstance("RSA", p) + // ruleid: scala_crypto_rule-InsufficientKeySizeRsa + keyGen.initialize(1024) + keyGen.generateKeyPair + } + // {/fact} + + @throws[NoSuchAlgorithmException] + @throws[NoSuchProviderException] + def strongKeySizeWithProviderString = { + val keyGen = KeyPairGenerator.getInstance("RSA", "BC") + keyGen.initialize(2048) // OK: n >= 2048 + + keyGen.generateKeyPair + } + + private class ExampleProvider(info: String) extends Provider("example", 0.0, info) { + def this() { + this("example") + } + } +} diff --git a/PR_6_scala/scala/crypto/rule-InsufficientKeySizeRsa.yml b/PR_6_scala/scala/crypto/rule-InsufficientKeySizeRsa.yml new file mode 100644 index 0000000..df82560 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-InsufficientKeySizeRsa.yml @@ -0,0 +1,32 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-InsufficientKeySizeRsa" + languages: + - "scala" + message: | + Detected an insufficient key size for DSA. NIST recommends a key size + of 2048 or higher. + metadata: + category: "security" + cwe: "CWE-326" + shortDescription: "Inadequate Encryption Strength" + security-severity: "MEDIUM" + patterns: + - pattern-either: + - patterns: + - pattern-inside: | + $GEN = KeyPairGenerator.getInstance($ALG, ...); + ... + - pattern-either: + - pattern: "$VAR.initialize($SIZE, ...)" + - pattern: "new java.security.spec.RSAKeyGenParameterSpec($SIZE, ...)" + - metavariable-comparison: + comparison: "$SIZE < 2048" + metavariable: "$SIZE" + - metavariable-regex: + metavariable: "$ALG" + regex: "\"(RSA|DSA)\"" + severity: "WARNING" diff --git a/PR_6_scala/scala/crypto/rule-NullCipher.scala b/PR_6_scala/scala/crypto/rule-NullCipher.scala new file mode 100644 index 0000000..cce7f72 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-NullCipher.scala @@ -0,0 +1,2 @@ +// License: MIT (c) GitLab Inc. +// TODO: Placeholder file; add example code. \ No newline at end of file diff --git a/PR_6_scala/scala/crypto/rule-NullCipher.yml b/PR_6_scala/scala/crypto/rule-NullCipher.yml new file mode 100644 index 0000000..0d01545 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-NullCipher.yml @@ -0,0 +1,21 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-NullCipher" + languages: + - "scala" + message: | + The NullCipher implements the Cipher interface by returning ciphertext identical to the + supplied plaintext. In a few contexts, such as testing, a NullCipher may be appropriate. Avoid + using the NullCipher. Its accidental use can introduce a significant confidentiality risk. + metadata: + category: "security" + cwe: "CWE-327" + shortDescription: "Use of a Broken or Risky Cryptographic Algorithm" + technology: + - "scala" + security-severity: "MEDIUM" + pattern: "new javax.crypto.NullCipher()" + severity: "WARNING" diff --git a/PR_6_scala/scala/crypto/rule-NullCipherUse.scala b/PR_6_scala/scala/crypto/rule-NullCipherUse.scala new file mode 100644 index 0000000..f917186 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-NullCipherUse.scala @@ -0,0 +1,45 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package crypto + +import java.io.UnsupportedEncodingException +import java.security.InvalidAlgorithmParameterException +import java.security.KeyException +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NullCipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + + +object NullCipherUse { + @throws[Exception] + def main(args: Array[String]): Unit = { + val pt = "AAAAAAAAAAAAAAAA".getBytes("UTF-8") + val expectedCihper = Cipher.getInstance("AES/CBC/NoPadding") + val doNothingCihper = new NullCipher + printHex(encryptWithCipher(expectedCihper, pt)) + printHex(encryptWithCipher(doNothingCihper, pt)) + } + + @throws[KeyException] + @throws[InvalidAlgorithmParameterException] + @throws[IllegalBlockSizeException] + @throws[BadPaddingException] + @throws[UnsupportedEncodingException] + def encryptWithCipher(cipher: Cipher, value: Array[Byte]) = { //Key generation + val passkey = "BBBBBBBBBBBBBBBB".getBytes("UTF-8") + val expectedCihper = cipher + val key = new SecretKeySpec(passkey, "AES") + //Setting the key + expectedCihper.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(new Array[Byte](expectedCihper.getBlockSize))) + cipher.doFinal(value) + } + + private def printHex(resultBytes: Array[Byte]): Unit = { + for (b <- resultBytes) { + System.out.print(Integer.toHexString(b & 0xFF)) + } + System.out.println + } +} diff --git a/PR_6_scala/scala/crypto/rule-RsaNoPadding.scala b/PR_6_scala/scala/crypto/rule-RsaNoPadding.scala new file mode 100644 index 0000000..4bb7ca0 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-RsaNoPadding.scala @@ -0,0 +1,45 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +// source: https://github.com/find-sec-bugs/find-sec-bugs/blob/master/findsecbugs-samples-java/src/test/java/testcode/crypto/RsaNoPadding.java +// hash: a7694d0 + +package crypto + +import javax.crypto.Cipher + + +/** + * Code sample taken from : http://cwe.mitre.org/data/definitions/780.html + */ +class RsaNoPadding { + @throws[Exception] + def rsaCipherOk(): Unit = { + Cipher.getInstance("RSA/ECB/OAEPWithMD5AndMGF1Padding") + Cipher.getInstance("RSA") + Cipher.getInstance("RSA/ECB/OAEPWithMD5AndMGF1Padding", "BC") + } + + @throws[Exception] + def rsaCipherWeak(): Unit = { + // {fact rule=insecure-rsa-algorithm@v1.0 defects=1} + // ruleid: scala_crypto_rule-RsaNoPadding + Cipher.getInstance("RSA/NONE/NoPadding") + // {/fact} + // {fact rule=insecure-rsa-algorithm@v1.0 defects=1} + // ruleid: scala_crypto_rule-RsaNoPadding + Cipher.getInstance("RSA/NONE/NoPadding", "BC") + // {/fact} + } + + @throws[Exception] + def dataflowCipherWeak(): Unit = { + val cipher1 = null + Cipher.getInstance(cipher1) + val cipher2 = "RSA/NONE/NoPadding" + // {fact rule=insecure-rsa-algorithm@v1.0 defects=1} + // ruleid: scala_crypto_rule-RsaNoPadding + Cipher.getInstance(cipher2) + // {/fact} + val cipher3 = null + Cipher.getInstance(cipher3) + } +} diff --git a/PR_6_scala/scala/crypto/rule-RsaNoPadding.yml b/PR_6_scala/scala/crypto/rule-RsaNoPadding.yml new file mode 100644 index 0000000..fec4191 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-RsaNoPadding.yml @@ -0,0 +1,25 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-RsaNoPadding" + languages: + - "scala" + message: | + The software uses the RSA algorithm but does not incorporate Optimal Asymmetric + Encryption Padding (OAEP), which might weaken the encryption. + metadata: + cwe: "CWE-780" + shortDescription: "Use of RSA Algorithm without OAEP" + security-severity: "MEDIUM" + category: "security" + owasp: + - "A3:2017-Sensitive Data Exposure" + - "A02:2021-Cryptographic Failures" + patterns: + - pattern: "javax.crypto.Cipher.getInstance(\"$ALG\",...)" + - metavariable-regex: + metavariable: "$ALG" + regex: ".*NoPadding.*" + severity: "WARNING" diff --git a/PR_6_scala/scala/crypto/rule-WeakMessageDigest.scala b/PR_6_scala/scala/crypto/rule-WeakMessageDigest.scala new file mode 100644 index 0000000..d639f76 --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-WeakMessageDigest.scala @@ -0,0 +1,99 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package crypto + +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.security.NoSuchProviderException +import java.security.Provider +import java.security.Signature + + +object WeakMessageDigest { + @throws[NoSuchProviderException] + @throws[NoSuchAlgorithmException] + def weakDigestMoreSig(): Unit = { + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("MD5", "SUN") + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("MD4", "SUN") + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("MD2", "SUN") + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("MD5") + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("MD4") + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("MD2") + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("MD5", new WeakMessageDigest.ExampleProvider) + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("MD4", new WeakMessageDigest.ExampleProvider) + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("MD2", new WeakMessageDigest.ExampleProvider) + // {/fact} + MessageDigest.getInstance("SHA", "SUN") + MessageDigest.getInstance("SHA", new WeakMessageDigest.ExampleProvider) + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("SHA1", "SUN") + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("SHA1", new WeakMessageDigest.ExampleProvider) + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("SHA-1", "SUN") + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + MessageDigest.getInstance("SHA-1", new WeakMessageDigest.ExampleProvider) + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=0} + MessageDigest.getInstance("sha-384", "SUN") //OK! + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=0} + MessageDigest.getInstance("SHA-512", "SUN") + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + Signature.getInstance("MD5withRSA") + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + Signature.getInstance("MD2withDSA", "X") + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakMessageDigest + Signature.getInstance("SHA1withRSA", new WeakMessageDigest.ExampleProvider) + // {/fact} + // {fact rule=insecure-cryptography@v1.0 defects=0} + Signature.getInstance("SHA256withRSA") //OK + // {/fact} + + Signature.getInstance("uncommon name", "") + } + + private class ExampleProvider(info: String) extends Provider("example", 0.0, info) { + def this() { + this("example") + } + } +} diff --git a/PR_6_scala/scala/crypto/rule-WeakMessageDigest.yml b/PR_6_scala/scala/crypto/rule-WeakMessageDigest.yml new file mode 100644 index 0000000..aa8aded --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-WeakMessageDigest.yml @@ -0,0 +1,26 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-WeakMessageDigest" + languages: + - "scala" + message: | + DES is considered strong ciphers for modern applications. Currently, NIST recommends the usage + of AES block ciphers instead of DES. + metadata: + category: "security" + cwe: "CWE-326" + shortDescription: "Inadequate Encryption Strength" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern-either: + - pattern: "MessageDigest.getInstance(\"$ALG\", ...)" + - pattern: "Signature.getInstance(\"$ALG\", ...)" + - metavariable-regex: + metavariable: "$ALG" + regex: "(.*(MD5|MD4|MD2|SHA1|SHA-1).*)" + severity: "WARNING" diff --git a/PR_6_scala/scala/crypto/rule-WeakTLSProtocol.scala b/PR_6_scala/scala/crypto/rule-WeakTLSProtocol.scala new file mode 100644 index 0000000..49926cf --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-WeakTLSProtocol.scala @@ -0,0 +1,32 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package crypto + +import java.security.NoSuchAlgorithmException +import org.apache.http.impl.client.DefaultHttpClient +import javax.net.ssl.SSLContext +import java.lang.reflect.Array + + +object WeakTLSProtocol { + def main(args: Array): Unit = { + // {fact rule=improper-certificate-validation@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakTLSProtocol + new DefaultHttpClient // BAD + // {/fact} + + try { + // {fact rule=improper-certificate-validation@v1.0 defects=1} + // ruleid: scala_crypto_rule-WeakTLSProtocol + val context1 = SSLContext.getInstance("SSL") // BAD + // {/fact} + + // {fact rule=improper-certificate-validation@v1.0 defects=0} + val context2 = SSLContext.getInstance("TLS") // OK + // {/fact} + } catch { + case e: NoSuchAlgorithmException => + // TODO Auto-generated catch block + e.printStackTrace + } + } +} diff --git a/PR_6_scala/scala/crypto/rule-WeakTLSProtocol.yml b/PR_6_scala/scala/crypto/rule-WeakTLSProtocol.yml new file mode 100644 index 0000000..37ce0ff --- /dev/null +++ b/PR_6_scala/scala/crypto/rule-WeakTLSProtocol.yml @@ -0,0 +1,27 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_crypto_rule-WeakTLSProtocol" + languages: + - "scala" + message: | + A HostnameVerifier that accept any host are often use because of certificate + reuse on many hosts. As a consequence, this is vulnerable to Man-in-the-middleattacks + attacks since the client will trust any certificate. + metadata: + category: "security" + cwe: "CWE-295" + shortDescription: "Improper Certificate Validation" + security-severity: "MEDIUM" + patterns: + - pattern-either: + - pattern: "new org.apache.http.impl.client.DefaultHttpClient()" + - pattern: "javax.net.ssl.SSLContext.getInstance(\"SSL\")" + - patterns: + - pattern-inside: | + import javax.net.ssl._ + ... + - pattern: "SSLContext.getInstance(\"SSL\")" + severity: "WARNING" diff --git a/PR_6_scala/scala/endpoint/rule-InsecureServlet.scala b/PR_6_scala/scala/endpoint/rule-InsecureServlet.scala new file mode 100644 index 0000000..621e497 --- /dev/null +++ b/PR_6_scala/scala/endpoint/rule-InsecureServlet.scala @@ -0,0 +1,57 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +// source: https://github.com/find-sec-bugs/find-sec-bugs/blob/master/findsecbugs-samples-java/src/test/java/testcode/endpoint/BasicHttpServlet.java +// hash: a7694d0 + +package endpoint + +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 + + +class InsecureServlet extends HttpServlet { + + @throws[ServletException] + @throws[IOException] + override protected def doGet(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + useParameters(req) + // {fact rule=improper-input-validation@v1.0 defects=1} + // ruleid: scala_endpoint_rule-InsecureServlet + resp.getWriter.print("") + // {/fact} + + // {fact rule=improper-input-validation@v1.0 defects=1} + // ruleid: scala_endpoint_rule-InsecureServlet + resp.getWriter.print("

Welcome to " + req.getServerName) + // {/fact} + + // {fact rule=improper-input-validation@v1.0 defects=1} + // ruleid: scala_endpoint_rule-InsecureServlet + val sqlQuery = "UPDATE sessions(last_visit) VALUES(now()) WHERE where sid = '" + req.getRequestedSessionId + "')" + // {/fact} + + resp.getWriter.print("") + val referrer = req.getHeader("Referer") //Should have a higher priority + if (referrer != null && referrer.startsWith("http://company.ca")) { + req.getHeader("Host") + req.getHeader("User-Agent") + req.getHeader("X-Requested-With") + } + } + + private def useParameters(req: HttpServletRequest): Unit = { + val username = req.getParameter("username").asInstanceOf[String] + val roles = req.getParameterValues("roles").asInstanceOf[Array[String]] + val price = req.getParameterMap.get("hidden_price_value") + val parameters = req.getParameterNames + var isAdmin = false + while ( { + parameters.hasMoreElements + }) if (parameters.nextElement.equals("admin_mode")) { + isAdmin = true + } + } +} diff --git a/PR_6_scala/scala/endpoint/rule-InsecureServlet.yml b/PR_6_scala/scala/endpoint/rule-InsecureServlet.yml new file mode 100644 index 0000000..96af547 --- /dev/null +++ b/PR_6_scala/scala/endpoint/rule-InsecureServlet.yml @@ -0,0 +1,46 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_endpoint_rule-InsecureServlet" + languages: + - "scala" + message: | + The Servlet can read GET and POST parameters from various methods. The + value obtained should be considered unsafe." + metadata: + category: "security" + cwe: "CWE-20" + shortDescription: "Improper Input Validation" + security-severity: "MEDIUM" + mode: "taint" + pattern-sanitizers: + - pattern: "Encode.forHtml(...)" + - pattern: "org.owasp.esapi.Encoder.encodeForSQL(...)" + pattern-sinks: + - patterns: + - pattern-either: + - pattern: | + ... + $PAR + - pattern: | + ... + $PAR + ... + - pattern: | + $PAR + ... + - pattern: "$PAR" + pattern-sources: + - pattern-either: + - pattern: "($REQ: HttpServletRequest).getContentType" + - pattern: "($REQ: HttpServletRequest).getServerName" + - pattern: "($REQ: HttpServletRequest).getRequestedSessionId" + - pattern: "($REQ: HttpServletRequest).getParameterValues(...)" + - pattern: "($REQ: HttpServletRequest).getParameterMap" + - pattern: "($REQ: HttpServletRequest).getParameterNames" + - pattern: "($REQ: HttpServletRequest).getParameter(...)" + - patterns: + - pattern-inside: | + ($REQ: HttpServletRequest).getSession + - pattern: "$SESS.getAttribute(\"...\")" + - pattern: | + ($REQ: HttpServletRequest).getSession.getAttribute("...") + severity: "WARNING" diff --git a/PR_6_scala/scala/endpoint/rule-JaxRsEndpoint.scala b/PR_6_scala/scala/endpoint/rule-JaxRsEndpoint.scala new file mode 100644 index 0000000..db1e260 --- /dev/null +++ b/PR_6_scala/scala/endpoint/rule-JaxRsEndpoint.scala @@ -0,0 +1,57 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package endpoint + +import javax.ws.rs.Path +import org.apache.commons.text.StringEscapeUtils + + +@Path("/test") +class JaxRsEndpoint { + def randomFunc(s: String) = s + // {fact rule=improper-input-validation@v1.0 defects=1} + @Path("/hello0") + // ruleid: scala_endpoint_rule-JaxRsEndpoint + def danger0(user: String) = "Hello " + user // BAD + // {/fact} + + + // {fact rule=improper-input-validation@v1.0 defects=1} + @Path("/hello1") + def danger1(user: String) = { + // ruleid: scala_endpoint_rule-JaxRsEndpoint + val tainted = randomFunc(user) + "Hello " + tainted + } + // {/fact} + + // {fact rule=improper-input-validation@v1.0 defects=1} + @Path("/hello2") + def danger3(user: String) = { + // ruleid: scala_endpoint_rule-JaxRsEndpoint + "Hello " + user + } + // {/fact} + + // {fact rule=improper-input-validation@v1.0 defects=1} + @Path("/hello2") + def danger4(user: String): String = { + // ruleid: scala_endpoint_rule-JaxRsEndpoint + return "Hello " + user + } + // {/fact} + + // {fact rule=improper-input-validation@v1.0 defects=0} + @Path("/hello2") + def ok1(user: String) = { + // ok: scala_endpoint_rule-JaxRsEndpoint + val sanitized = StringEscapeUtils.unescapeJava(user) + "Hello " + sanitized // OK + } + // {/fact} + + // {fact rule=improper-input-validation@v1.0 defects=0} + def ok2(user: String): String = { + return "Hello " + user // OK + } + // {/fact} +} diff --git a/PR_6_scala/scala/endpoint/rule-JaxRsEndpoint.yml b/PR_6_scala/scala/endpoint/rule-JaxRsEndpoint.yml new file mode 100644 index 0000000..569af88 --- /dev/null +++ b/PR_6_scala/scala/endpoint/rule-JaxRsEndpoint.yml @@ -0,0 +1,34 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_endpoint_rule-JaxRsEndpoint" + languages: + - "scala" + message: | + This method is part of a REST Web Service (JSR311). The security of this web service should be + analyzed. For example: + - Authentication, if enforced, should be tested. + - Access control, if enforced, should be tested. + - The inputs should be tracked for potential vulnerabilities. + - The communication should ideally be over SSL. + - If the service supports writes (e.g., via POST), its vulnerability to CSRF should be + investigated. + metadata: + category: "security" + cwe: "CWE-20" + shortDescription: "Improper Input Validation" + technology: + - "scala" + security-severity: "INFO" + mode: "taint" + pattern-sinks: + - pattern: "<...$VAR...>" + pattern-sources: + - patterns: + - pattern-inside: | + @javax.ws.rs.Path("...") + def $FUNC(..., $VAR: $TYPE, ...) = ... + - pattern: "$VAR" + severity: "INFO" diff --git a/PR_6_scala/scala/endpoint/rule-JaxWsEndpoint.scala b/PR_6_scala/scala/endpoint/rule-JaxWsEndpoint.scala new file mode 100644 index 0000000..aad6976 --- /dev/null +++ b/PR_6_scala/scala/endpoint/rule-JaxWsEndpoint.scala @@ -0,0 +1,66 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package endpoint + +import org.apache.commons.text.StringEscapeUtils +import javax.jws.WebMethod +import javax.jws.WebService + + +@WebService +class JaxWsEndpoint { + // {fact rule=improper-input-validation@v1.0 defects=0} + @WebMethod(operationName = "timestamp") + def ping = System.currentTimeMillis // OK + // {/fact} + + def randomFunc(s: String) = s + + // {fact rule=improper-input-validation@v1.0 defects=1} + @WebMethod + // ruleid: scala_endpoint_rule-JaxWsEndpoint + def danger0(user: String) = "Hello " + user // BAD + // {/fact} + + // {fact rule=improper-input-validation@v1.0 defects=1} + @WebMethod + def danger1(user: String) = { + // ruleid: scala_endpoint_rule-JaxWsEndpoint + val tainted = randomFunc(user) + "Hello " + tainted + } + // {/fact} + + // {fact rule=improper-input-validation@v1.0 defects=1} + @WebMethod + def danger3(user: String) = { + // ruleid: scala_endpoint_rule-JaxWsEndpoint + "Hello " + user + } + // {/fact} + + // {fact rule=improper-input-validation@v1.0 defects=0} + @WebMethod(action="/hello2") + def ok1(user: String) = { + // ok: scala_endpoint_rule-JaxWsEndpoint + val sanitized = StringEscapeUtils.unescapeJava(user) + "Hello " + sanitized // OK + } + // {/fact} + + // {fact rule=improper-input-validation@v1.0 defects=0} + def ok2(user: String): String = { + return "Hello " + user // OK + } + // {/fact} + + // {fact rule=improper-input-validation@v1.0 defects=0} + @WebMethod + def ok3(user: String) = { + // ok: scala_endpoint_rule-JaxWsEndpoint + val sanitized = StringEscapeUtils.unescapeJava(user) + "Hello " + sanitized + } + // {/fact} + + def ok4 = 8000 +} diff --git a/PR_6_scala/scala/endpoint/rule-JaxWsEndpoint.yml b/PR_6_scala/scala/endpoint/rule-JaxWsEndpoint.yml new file mode 100644 index 0000000..0940f6a --- /dev/null +++ b/PR_6_scala/scala/endpoint/rule-JaxWsEndpoint.yml @@ -0,0 +1,33 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_endpoint_rule-JaxWsEndpoint" + languages: + - "scala" + message: | + This method is part of a SOAP Web Service (JSR224). The security of this web service should be + analyzed. For example: + - Authentication, if enforced, should be tested. + - Access control, if enforced, should be tested. + - The inputs should be tracked for potential vulnerabilities. + - The communication should ideally be over SSL. + metadata: + category: "security" + cwe: "CWE-20" + owasp: "A7:2017-Cross-Site Scripting (XSS)" + shortDescription: "Improper Input Validation" + technology: + - "scala" + security-severity: "INFO" + mode: "taint" + pattern-sinks: + - pattern: "<...$VAR...>" + pattern-sources: + - patterns: + - pattern-inside: | + @javax.jws.WebMethod(...) + def $FUNC(..., $VAR: $TYPE, ...) = ... + - pattern: "$VAR" + severity: "INFO" diff --git a/PR_6_scala/scala/endpoint/rule-UnencryptedSocket.scala b/PR_6_scala/scala/endpoint/rule-UnencryptedSocket.scala new file mode 100644 index 0000000..e0ba50e --- /dev/null +++ b/PR_6_scala/scala/endpoint/rule-UnencryptedSocket.scala @@ -0,0 +1,89 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +import javax.net.ssl.SSLServerSocketFactory +import javax.net.ssl.SSLSocketFactory +import java.io._ +import java.net.InetAddress +import java.net.ServerSocket +import java.net.Socket + +object UnencryptedSocket { + @throws[IOException] + // {fact rule=insecure-connection@v1.0 defects=0} + private[this] def sslServerSocket(): Unit = { + // ok: scala_endpoint_rule-UnencryptedSocket + val ssoc = SSLServerSocketFactory.getDefault.createServerSocket(1234) + ssoc.close + // {/fact} + } + + @throws[IOException] + private[this] def plainServerSocket(): Unit = { + val ssoc = new ServerSocket(1234) + ssoc.close + } + + @throws[IOException] + private[this] def otherConstructors(): Unit = { + val ssoc1 = new ServerSocket + ssoc1.close + val ssoc2 = new ServerSocket(1234, 10) + ssoc2.close + val address = Array(127.toByte, 0.toByte, 0.toByte, 1.toByte) + val ssoc3 = new ServerSocket(1234, 10, InetAddress.getByAddress(address)) + ssoc3.close + } + + @throws[IOException] + // {fact rule=insecure-connection@v1.0 defects=0} + private[this] def sslSocket(): Unit = { + // ok: scala_endpoint_rule-UnencryptedSocket + val soc = SSLSocketFactory.getDefault.createSocket("www.google.com", 443) + doGetRequest(soc) + // {/fact} + } + + @throws[IOException] + private[this] def plainSocket(): Unit = { + // {fact rule=insecure-connection@v1.0 defects=1} + // ruleid: scala_endpoint_rule-UnencryptedSocket + val soc = new Socket("www.google.com", 80) + doGetRequest(soc) + // {/fact} + } + + @throws[IOException] + private[this] def other(): Unit = { + // {fact rule=insecure-connection@v1.0 defects=1} + // ruleid: scala_endpoint_rule-UnencryptedSocket + val soc1 = new Socket("www.google.com", 80, true) + // {/fact} + doGetRequest(soc1) + val address = Array(127.toByte, 0.toByte, 0.toByte, 1.toByte) + // {fact rule=insecure-connection@v1.0 defects=1} + val soc2 = + // ruleid: scala_endpoint_rule-UnencryptedSocket + new Socket("www.google.com", 80, InetAddress.getByAddress(address), 13337) + // {/fact} + doGetRequest(soc2) + val remoteAddress = Array(74.toByte, 125.toByte, 226.toByte, 193.toByte) + // {fact rule=insecure-connection@v1.0 defects=1} + // ruleid: scala_endpoint_rule-UnencryptedSocket + val soc3 = new Socket(InetAddress.getByAddress(remoteAddress), 80) + // {/fact} + doGetRequest(soc2) + } + + @throws[IOException] + private[this] def doGetRequest(soc: Socket): Unit = { + val w = new PrintWriter(soc.getOutputStream) + w.write("GET / HTTP/1.0\nHost: www.google.com\n\n") + w.flush + val r = new BufferedReader(new InputStreamReader(soc.getInputStream)) + var line = r.readLine + while (line != null) { + println(line) + line = r.readLine + } + soc.close + } +} diff --git a/PR_6_scala/scala/endpoint/rule-UnencryptedSocket.yml b/PR_6_scala/scala/endpoint/rule-UnencryptedSocket.yml new file mode 100644 index 0000000..c3b0795 --- /dev/null +++ b/PR_6_scala/scala/endpoint/rule-UnencryptedSocket.yml @@ -0,0 +1,24 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_endpoint_rule-UnencryptedSocket" + languages: + - "scala" + message: | + Beyond using an SSL socket, you need to make sure your use of SSLSocketFactory + does all the appropriate certificate validation checks to make sure you are not + subject to man-in-the-middle attacks. Please read the OWASP Transport Layer + Protection Cheat Sheet for details on how to do this correctly. + metadata: + cwe: "CWE-319" + shortDescription: "Cleartext transmission of sensitive information" + security-severity: "MEDIUM" + owasp: + - "A3:2017-Sensitive Data Exposure" + - "A02:2021-Cryptographic Failures" + category: "security" + patterns: + - pattern: "new java.net.Socket(...)" + severity: "WARNING" diff --git a/PR_6_scala/scala/endpoint/rule-UnvalidatedRedirect.scala b/PR_6_scala/scala/endpoint/rule-UnvalidatedRedirect.scala new file mode 100644 index 0000000..144123c --- /dev/null +++ b/PR_6_scala/scala/endpoint/rule-UnvalidatedRedirect.scala @@ -0,0 +1,45 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package endpoint + +import javax.servlet.ServletException +import javax.servlet.http.HttpServlet +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse +import java.io.IOException + + +class UnvalidatedRedirect extends HttpServlet { + + @throws[ServletException] + @throws[IOException] + override protected def doGet(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + val url = req.getParameter("urlRedirect") + unvalidatedRedirect1(resp, url) + } + + // {fact rule=open-redirect@v1.0 defects=1} + @throws[IOException] + private def unvalidatedRedirect1(resp: HttpServletResponse, url: String): Unit = { + // ruleid: scala_endpoint_rule-UnvalidatedRedirect + if (url != null) resp.sendRedirect(url) + } + // {/fact} + + // {fact rule=open-redirect@v1.0 defects=1} + def unvalidatedRedirect2(resp: HttpServletResponse, url: String): Unit = { + // ruleid: scala_endpoint_rule-UnvalidatedRedirect + if (url != null) resp.addHeader("Location", url) + } + // {/fact} + + ///The following cases are safe for sure + @throws[IOException] + def falsePositiveRedirect1(resp: HttpServletResponse): Unit = { + val url = "/Home" + if (url != null) resp.sendRedirect(url) + } + + def falsePositiveRedirect2(resp: HttpServletResponse): Unit = { + resp.addHeader("Location", "/login.jsp") + } +} diff --git a/PR_6_scala/scala/endpoint/rule-UnvalidatedRedirect.yml b/PR_6_scala/scala/endpoint/rule-UnvalidatedRedirect.yml new file mode 100644 index 0000000..22b22f7 --- /dev/null +++ b/PR_6_scala/scala/endpoint/rule-UnvalidatedRedirect.yml @@ -0,0 +1,32 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_endpoint_rule-UnvalidatedRedirect" + languages: + - "scala" + message: | + Unvalidated redirects occur when an application redirects a user to a + destination URL specified by a user supplied parameter that is not validated. + Such vulnerabilities can be used to facilitate phishing attacks. + metadata: + category: "security" + cwe: "CWE-601" + shortDescription: "URL Redirection to Untrusted Site ('Open Redirect')" + security-severity: "CRITICAL" + patterns: + - pattern-either: + - patterns: + - pattern: "($REQ: HttpServletResponse).sendRedirect(...)" + - pattern-not: "($REQ: HttpServletResponse).sendRedirect(\"...\")" + - patterns: + - pattern: "($REQ: HttpServletResponse).addHeader(...)" + - pattern-not: "($REQ: HttpServletResponse).addHeader(\"...\", \"...\")" + - patterns: + - pattern: "($REQ: HttpServletResponse).encodeURL(...)" + - pattern-not: "($REQ: HttpServletResponse).encodeURL(\"...\")" + - patterns: + - pattern: "($REQ: HttpServletResponse).encodeRedirectUrl(...)" + - pattern-not: "($REQ: HttpServletResponse).encodeRedirectUrl(\"...\")" + severity: "ERROR" diff --git a/PR_6_scala/scala/endpoint/rule-WeakHostNameVerification.scala b/PR_6_scala/scala/endpoint/rule-WeakHostNameVerification.scala new file mode 100644 index 0000000..241f494 --- /dev/null +++ b/PR_6_scala/scala/endpoint/rule-WeakHostNameVerification.scala @@ -0,0 +1,94 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package endpoint + +import javax.net.ssl._ +import java.security.KeyManagementException +import java.security.NoSuchAlgorithmException +import java.security.cert.CertificateException +import java.security.cert.X509Certificate + + +class WeakHostNameVerification { + def useAllHosts(): Unit = { + HttpsURLConnection.setDefaultHostnameVerifier(new AllHosts) + } + + @throws[NoSuchAlgorithmException] + @throws[KeyManagementException] + def useTrustAllManager(): Unit = { + val trustAllCerts = Array[TrustManager](new TrustAllManager) + val sslContext = SSLContext.getInstance("SSL") + sslContext.init(null, trustAllCerts, null) + HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory) + } + + def useSecurityBypasser(): Unit = { + SecurityBypasser.destroyAllSSLSecurityForTheEntireVMForever() + } +} + +class AllHosts extends HostnameVerifier { + // {fact rule=improper-certificate-validation@v1.0 defects=1} + // ruleid: scala_endpoint_rule-WeakHostNameVerification + def verify(hostname: String, session: SSLSession) = true + // {/fact} +} + +class TrustAllManager extends X509TrustManager { + // {fact rule=improper-certificate-validation@v1.0 defects=1} + // ruleid: scala_endpoint_rule-WeakHostNameVerification + @throws[CertificateException] + def checkClientTrusted(cert: Array[X509Certificate], authType: String): Unit = { + } + // {/fact} + + // {fact rule=improper-certificate-validation@v1.0 defects=1} + // ruleid: scala_endpoint_rule-WeakHostNameVerification + @throws[CertificateException] + def checkServerTrusted(cert: Array[X509Certificate], authType: String): Unit = { + } + // {/fact} + + // {fact rule=improper-certificate-validation@v1.0 defects=1} + // ruleid: scala_endpoint_rule-WeakHostNameVerification + def getAcceptedIssuers(): Array[X509Certificate] = null + // {/fact} +} + +class TrustAllManagerTwo extends X509TrustManager { + // {fact rule=improper-certificate-validation@v1.0 defects=1} + // ruleid: scala_endpoint_rule-WeakHostNameVerification + @throws[CertificateException] + def checkClientTrusted(cert: Array[X509Certificate], authType: String): Unit = { + } + // {/fact} + + // {fact rule=improper-certificate-validation@v1.0 defects=1} + // ruleid: scala_endpoint_rule-WeakHostNameVerification + @throws[CertificateException] + def checkServerTrusted(cert: Array[X509Certificate], authType: String): Unit = { + } + // {/fact} + + def getAcceptedIssuers(): Array[X509Certificate] = { + Array() + } +} + +object SecurityBypasser { + def destroyAllSSLSecurityForTheEntireVMForever(): Unit = { + try { + val trustAllCerts = Array[TrustManager](new TrustAllManager) + val sslContext = SSLContext.getInstance("SSL") + sslContext.init(null, trustAllCerts, null) + HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory) + HttpsURLConnection.setDefaultHostnameVerifier(new AllHosts) + } catch { + case e: NoSuchAlgorithmException => + e.printStackTrace + case e: KeyManagementException => + e.printStackTrace + } + } +} + diff --git a/PR_6_scala/scala/endpoint/rule-WeakHostNameVerification.yml b/PR_6_scala/scala/endpoint/rule-WeakHostNameVerification.yml new file mode 100644 index 0000000..f1945b9 --- /dev/null +++ b/PR_6_scala/scala/endpoint/rule-WeakHostNameVerification.yml @@ -0,0 +1,43 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_endpoint_rule-WeakHostNameVerification" + languages: + - "scala" + message: | + A HostnameVerifier that accept any host are often use because of certificate + reuse on many hosts. As a consequence, this is vulnerable to Man-in-the-middle + attacks since the client will trust any certificate. + metadata: + category: "security" + cwe: "CWE-295" + shortDescription: "Improper Certificate Validation" + security-severity: "MEDIUM" + patterns: + - pattern-either: + - patterns: + - pattern-inside: | + class $V extends HostnameVerifier { + ... + } + - pattern-either: + - pattern: "def verify(...) = true" + - pattern: | + def verify(...) = { + return true + } + - patterns: + - pattern-inside: | + class $V extends X509TrustManager { + ... + } + - pattern-either: + - pattern: "def checkClientTrusted(...): Unit = {}" + - pattern: "def checkServerTrusted(...): Unit = {}" + - pattern: "def checkClientTrusted(...) = {}" + - pattern: "def checkServerTrusted(...) = {}" + - pattern: "def getAcceptedIssuers(): Array[X509Certificate] = null" + - pattern: "def getAcceptedIssuers(): Array[X509Certificate] = {}" + severity: "WARNING" diff --git a/PR_6_scala/scala/file/rule-FileUploadFileName.scala b/PR_6_scala/scala/file/rule-FileUploadFileName.scala new file mode 100644 index 0000000..0eda184 --- /dev/null +++ b/PR_6_scala/scala/file/rule-FileUploadFileName.scala @@ -0,0 +1,25 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package file + +import org.apache.commons.fileupload.FileItem +import org.apache.commons.fileupload.FileUploadException +import org.apache.commons.fileupload.disk.DiskFileItemFactory +import org.apache.commons.fileupload.servlet.ServletFileUpload +import javax.servlet.http.HttpServletRequest +import java.util._ +import scala.jdk.CollectionConverters._ + +class FileUploadFileName { + + // {fact rule=path-traversal@v1.0 defects=1} + @throws[FileUploadException] + def handleFileCommon(req: HttpServletRequest): Unit = { + val upload = new ServletFileUpload(new DiskFileItemFactory()) + val fileItems = upload.parseRequest(req) + for (item <- fileItems.asScala) { + // ruleid: scala_file_rule-FileUploadFileName + println("Saving " + item.getName() + "...") + } + } + // {/fact} +} diff --git a/PR_6_scala/scala/file/rule-FileUploadFileName.yml b/PR_6_scala/scala/file/rule-FileUploadFileName.yml new file mode 100644 index 0000000..8ee3c70 --- /dev/null +++ b/PR_6_scala/scala/file/rule-FileUploadFileName.yml @@ -0,0 +1,36 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_file_rule-FileUploadFileName" + languages: + - "scala" + message: | + The filename provided by the FileUpload API can be tampered with by the client to reference + unauthorized files. The provided filename should be properly validated to ensure it's properly + structured, contains no unauthorized path characters (e.g., / \), and refers to an authorized + file. + metadata: + category: "security" + cwe: "CWE-22" + shortDescription: "Improper limitation of a pathname to a restricted directory + ('Path Traversal')" + technology: + - "scala" + owasp: + - "A5:2017-Broken Access Control" + - "A01:2021-Broken Access Control" + security-severity: "MEDIUM" + patterns: + - pattern: | + def $FUNC (..., $REQ: HttpServletRequest, ... ) = { + ... + val $FILES = ($SFU: ServletFileUpload).parseRequest($REQ) + ... + for ($FILE <- $FILES.asScala) { + ... + } + } + - pattern: "$ITEM.getName()" + severity: "WARNING" diff --git a/PR_6_scala/scala/file/rule-FilenameUtils.scala b/PR_6_scala/scala/file/rule-FilenameUtils.scala new file mode 100644 index 0000000..944839c --- /dev/null +++ b/PR_6_scala/scala/file/rule-FilenameUtils.scala @@ -0,0 +1,71 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package file + +import org.apache.commons.io.FilenameUtils._ +import java.io.File +import java.io.IOException + + +object FilenameUtils { + @throws[IOException] + def main(args: Array[String]): Unit = { + val maliciousPath = "/test%00/././../../././secret/note.cfg\u0000example.jpg" + testPath(maliciousPath) + } + + @throws[IOException] + private def testPath(maliciousPath: String): Unit = { + // {fact rule=path-traversal@v1.0 defects=1} + // ruleid: scala_file_rule-FilenameUtils + val path = normalize(maliciousPath) + System.out.println("Expected:" + path + " -> Actual:" + canonical(path)) + // {/fact} + + // {fact rule=path-traversal@v1.0 defects=1} + // ruleid: scala_file_rule-FilenameUtils + val extension = getExtension(maliciousPath) + // {/fact} + + + // {fact rule=path-traversal@v1.0 defects=1} + // ruleid: scala_file_rule-FilenameUtils + System.out.println("Expected:" + extension + " -> Actual:" + getExtension(canonical(path))) + // {/fact} + + + // {fact rule=path-traversal@v1.0 defects=1} + // ruleid: scala_file_rule-FilenameUtils + val isExt = isExtension(maliciousPath, "jpg") + // {/fact} + + // {fact rule=path-traversal@v1.0 defects=1} + // ruleid: scala_file_rule-FilenameUtils + System.out.println("Expected:" + isExt + " -> Actual:" + isExtension(canonical(path), "jpg")) + // {/fact} + + // {fact rule=path-traversal@v1.0 defects=1} + // ruleid: scala_file_rule-FilenameUtils + val name = getName(maliciousPath) + // {/fact} + + // {fact rule=path-traversal@v1.0 defects=1} + // ruleid: scala_file_rule-FilenameUtils + System.out.println("Expected:" + name + " -> Actual:" + getName(canonical(name))) + // {/fact} + + + // {fact rule=path-traversal@v1.0 defects=1} + // ruleid: scala_file_rule-FilenameUtils + val baseName = getBaseName(maliciousPath) + // {/fact} + + // {fact rule=path-traversal@v1.0 defects=1} + // ruleid: scala_file_rule-FilenameUtils + System.out.println("Expected:" + baseName + " -> Actual:" + getBaseName(canonical(baseName))) + // {/fact} + } + + @throws[IOException] + private def canonical(path: String) = new File(path).getCanonicalPath +} + diff --git a/PR_6_scala/scala/file/rule-FilenameUtils.yml b/PR_6_scala/scala/file/rule-FilenameUtils.yml new file mode 100644 index 0000000..2de6b02 --- /dev/null +++ b/PR_6_scala/scala/file/rule-FilenameUtils.yml @@ -0,0 +1,44 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_file_rule-FilenameUtils" + languages: + - "scala" + message: | + A file is opened to read its content. The filename comes from an input + parameter. If an unfiltered parameter is passed to this file API, files from an + arbitrary filesystem location could be read. + metadata: + category: "security" + cwe: "CWE-22" + shortDescription: "Improper limitation of a pathname to a restricted directory + ('Path Traversal')" + technology: + - "scala" + owasp: + - "A5:2017-Broken Access Control" + - "A01:2021-Broken Access Control" + security-severity: "MEDIUM" + pattern-either: + - patterns: + - pattern-inside: | + import org.apache.commons.io.FilenameUtils._ + ... + - pattern-either: + - pattern: "normalize(...)" + - pattern: "getExtension(...)" + - pattern: "isExtensions(...)" + - pattern: "isExtension(...)" + - pattern: "getName(...)" + - pattern: "getBaseName(...)" + - patterns: + - pattern-either: + - pattern: "org.apache.commons.io.FilenameUtils.normalize(...)" + - pattern: "org.apache.commons.io.FilenameUtils.getExtension(...)" + - pattern: "org.apache.commons.io.FilenameUtils.isExtensions(...)" + - pattern: "org.apache.commons.io.FilenameUtils.isExtension(...)" + - pattern: "org.apache.commons.io.FilenameUtils.getName(...)" + - pattern: "org.apache.commons.io.FilenameUtils.getBaseName(...)" + severity: "WARNING" diff --git a/PR_6_scala/scala/form/rule-FormValidate.scala b/PR_6_scala/scala/form/rule-FormValidate.scala new file mode 100644 index 0000000..5c7df80 --- /dev/null +++ b/PR_6_scala/scala/form/rule-FormValidate.scala @@ -0,0 +1,23 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package form + +import org.apache.struts.validator.ValidatorForm +// {fact rule=improper-input-validation@v1.0 defects=1} +// ruleid: scala_form_rule-FormValidate +class FormValidate extends ValidatorForm { + private var name: String = null + private var email: String = null + + def getName = name + + def setName(n: String) { + this.name = n + } + + def getEmail = email + + def setEmail(email: String) { + this.email = email + } +} +// {/fact} diff --git a/PR_6_scala/scala/form/rule-FormValidate.yml b/PR_6_scala/scala/form/rule-FormValidate.yml new file mode 100644 index 0000000..37ef381 --- /dev/null +++ b/PR_6_scala/scala/form/rule-FormValidate.yml @@ -0,0 +1,26 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_form_rule-FormValidate" + languages: + - "scala" + message: | + Form inputs should have minimal input validation. Preventive validation helps provide defense + in depth against a variety of risks. + metadata: + category: "security" + cwe: "CWE-20" + shortDescription: "Improper Input Validation" + security-severity: "MEDIUM" + patterns: + - pattern-inside: | + class $CLASS extends $SC { + ... + } + - metavariable-regex: + metavariable: "$SC" + regex: "(ActionForm|ValidatorForm)" + - pattern-not: "public void validate() { ... }" + severity: "WARNING" diff --git a/PR_6_scala/scala/inject/rule-AWSQueryInjection.scala b/PR_6_scala/scala/inject/rule-AWSQueryInjection.scala new file mode 100644 index 0000000..ae01149 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-AWSQueryInjection.scala @@ -0,0 +1,64 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package inject + +import java.io.IOException +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse +import javax.servlet.http.HttpServlet +import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.services.simpledb.AmazonSimpleDB +import com.amazonaws.services.simpledb.AmazonSimpleDBClient +import com.amazonaws.services.simpledb.model.SelectRequest +import com.amazonaws.services.simpledb.model.SelectResult + + +class AWSQueryInjection extends HttpServlet { + @throws[IOException] + override def doGet(request: HttpServletRequest, response: HttpServletResponse): Unit = { + try { + val customerID = request.getParameter("customerID") + val awsCredentials = new BasicAWSCredentials("test", "test") + val sdbc = new AmazonSimpleDBClient(awsCredentials) + val query = "select * from invoices where customerID = '" + customerID + // {fact rule=nosql-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-AWSQueryInjection + val sdbResult = sdbc.select(new SelectRequest(query)) //BAD + // {/fact} + + // {fact rule=nosql-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-AWSQueryInjection + val sdbResult2 = sdbc.select(new SelectRequest(query, false)) + // {/fact} + val sdbRequest = new SelectRequest() + + // {fact rule=nosql-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-AWSQueryInjection + val sdbResult3 = sdbc.select(sdbRequest.withSelectExpression(query)) + // {/fact} + val query2 = "select * from invoices where customerID = 123" + // {fact rule=nosql-injection@v1.0 defects=0} + val sdbResult4 = sdbc.select(new SelectRequest(query2)) //OK + // {/fact} + } catch { + case _: Throwable => + } + } + + def danger(customerID: Nothing, productCategory: Nothing): Unit = { + val sdbc = AmazonSimpleDBClient.builder.build + val query = "select * from invoices where productCategory = '" + productCategory + "' and customerID = '" + customerID + "' order by '" + // {fact rule=nosql-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-AWSQueryInjection + val sdbResult = sdbc.select(new SelectRequest(query)) + // {/fact} + } + + def danger2(customerID: Nothing, productCategory: Nothing): Unit = { + val sdbc = AmazonSimpleDBClient.builder.build + val query = "select * from invoices where productCategory = '" + productCategory + "' and customerID = '" + customerID + "' order by '" + // {fact rule=nosql-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-AWSQueryInjection + val sdbResult = sdbc.select(new SelectRequest(query, false)) + // {/fact} + } +} diff --git a/PR_6_scala/scala/inject/rule-AWSQueryInjection.yml b/PR_6_scala/scala/inject/rule-AWSQueryInjection.yml new file mode 100644 index 0000000..2ecff5d --- /dev/null +++ b/PR_6_scala/scala/inject/rule-AWSQueryInjection.yml @@ -0,0 +1,46 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-AWSQueryInjection" + languages: + - "scala" + message: | + Constructing SimpleDB queries containing user input can allow an attacker to view unauthorized + records. + metadata: + category: "security" + cwe: "CWE-943" + shortDescription: "Improper Neutralization of Special Elements in Data Query Logic" + technology: + - "scala" + security-severity: "CRITICAL" + mode: "taint" + pattern-sinks: + - pattern: "new com.amazonaws.services.simpledb.model.SelectRequest($QUERY, ...);" + - patterns: + - pattern-inside: | + $DB.select(($SR: com.amazonaws.services.simpledb.model.SelectRequest).withSelectExpression($QUERY,...)); + - pattern: "$QUERY" + - metavariable-pattern: + metavariable: "$DB" + pattern-either: + - pattern: "($DB: com.amazonaws.services.simpledb.AmazonSimpleDB)" + - pattern: "($DB: com.amazonaws.services.simpledb.AmazonSimpleDBClient)" + pattern-sources: + - patterns: + - pattern-inside: | + def $FUNC(..., $REQ: HttpServletRequest, ...): $TYPE = { + ... + } + - pattern: "$REQ" + - patterns: + - pattern-inside: | + def $FUNC(..., $X: $TYPE, ...): $RET_TYPE = { + ... + $QUERY = <...$X...> + ... + } + - pattern: "$QUERY" + severity: "ERROR" diff --git a/PR_6_scala/scala/inject/rule-BeanPropertyInjection.scala b/PR_6_scala/scala/inject/rule-BeanPropertyInjection.scala new file mode 100644 index 0000000..01cdbc0 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-BeanPropertyInjection.scala @@ -0,0 +1,58 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +// scaffold: dependencies=commons-beanutils.commons-beanutils@1.9.4 +package inject + +import org.apache.commons.beanutils.BeanUtils +import org.apache.commons.beanutils.BeanUtilsBean +import javax.servlet.http.HttpServletRequest +import java.lang.reflect.InvocationTargetException +import java.util + + +class BeanPropertyInjection { + + // {fact rule=external-control-setting@v1.0 defects=1} + @throws[InvocationTargetException] + @throws[IllegalAccessException] + def danger(bean: Nothing, request: HttpServletRequest): Unit = { + val map = new util.HashMap[String, String]() + // ruleid: scala_inject_rule-BeanPropertyInjection + map.put("test", request.getParameter("test")) + BeanUtils.populate(bean, map) + } + // {/fact} + + @throws[InvocationTargetException] + @throws[IllegalAccessException] + def danger2(bean: Nothing, request: HttpServletRequest): Unit = { + val map = new util.HashMap[String, String]() + val names = request.getParameterNames + // {fact rule=external-control-setting@v1.0 defects=1} + // ruleid: scala_inject_rule-BeanPropertyInjection + while ( { + names.hasMoreElements + }) { + val name = names.nextElement.asInstanceOf[Nothing] + map.put(name, request.getParameterValues(name).last) + } + BeanUtils.populate(bean, map) + // {/fact} + } + + @throws[InvocationTargetException] + @throws[IllegalAccessException] + def danger3(bean: Nothing, request: HttpServletRequest): Unit = { + val map = new util.HashMap[String, String]() + val names = request.getParameterNames + // {fact rule=external-control-setting@v1.0 defects=1} + // ruleid: scala_inject_rule-BeanPropertyInjection + while ( { + names.hasMoreElements + }) { + val name = names.nextElement.asInstanceOf[Nothing] + map.put(name, request.getParameterValues("x").last) + } + new BeanUtilsBean().populate(bean, map) + // {/fact} + } +} diff --git a/PR_6_scala/scala/inject/rule-BeanPropertyInjection.yml b/PR_6_scala/scala/inject/rule-BeanPropertyInjection.yml new file mode 100644 index 0000000..4764f9d --- /dev/null +++ b/PR_6_scala/scala/inject/rule-BeanPropertyInjection.yml @@ -0,0 +1,42 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-BeanPropertyInjection" + languages: + - "scala" + message: | + An attacker can set arbitrary bean properties that can compromise system integrity. An + attacker can leverage this functionality to access special bean properties like + class.classLoader that will allow them to override system properties and potentially execute + arbitrary code. + metadata: + category: "security" + cwe: "CWE-15" + shortDescription: "External Control of System or Configuration Setting" + technology: + - "scala" + security-severity: "CRITICAL" + patterns: + - pattern-inside: |- + def $FUNC(..., $REQ: HttpServletRequest, ...): $TYPE = { ... } + - pattern-either: + - pattern: | + $MAP.put(..., $REQ.getParameter(...)) + ... + $BEAN_UTIL.populate(..., $MAP) + - pattern: | + while (...) { + ... + $MAP.put(..., $REQ.getParameterValues(...). ...) + } + ... + $BEAN_UTIL.populate(..., $MAP) + - metavariable-pattern: + metavariable: "$BEAN_UTIL" + pattern-either: + - pattern: "(BeanUtilsBean $B)" + - pattern: "new BeanUtilsBean()" + - pattern: "org.apache.commons.beanutils.BeanUtils" + severity: "ERROR" diff --git a/PR_6_scala/scala/inject/rule-CLRFInjectionLogs.scala b/PR_6_scala/scala/inject/rule-CLRFInjectionLogs.scala new file mode 100644 index 0000000..2fbf84e --- /dev/null +++ b/PR_6_scala/scala/inject/rule-CLRFInjectionLogs.scala @@ -0,0 +1,150 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +// scaffold: dependencies=com.amazonaws.aws-java-sdk-simpledb@1.12.187 +package inject + +import javax.servlet.http.HttpServletRequest +import java.util.ResourceBundle +import java.util.function.Supplier +import java.util.logging._ + + +object CLRFInjectionLogs { + var req = null +} + +class CLRFInjectionLogs { + def javaUtilLogging(req: HttpServletRequest): Unit = { + val tainted = req.getParameter("test") + val safe = "safe" + val logger = Logger.getLogger(classOf[Nothing].getName) + logger.setLevel(Level.ALL) + val handler = new ConsoleHandler + handler.setLevel(Level.ALL) + logger.addHandler(handler) + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.config(tainted) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.entering(tainted, safe) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.entering("safe", safe, tainted) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.entering(safe, "safe", Array[String](tainted)) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.exiting(safe, tainted) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.exiting(safe, "safe", tainted) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.fine(tainted) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.finer(tainted.trim) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.finest(tainted) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.info(tainted) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.log(Level.INFO, tainted) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.log(Level.INFO, tainted, safe) + // {/fact} + + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.log(Level.INFO, "safe", Array[String](tainted)) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.log(Level.INFO, tainted) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.logp(Level.INFO, tainted, safe, "safe") + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.logp(Level.INFO, safe, "safe", tainted, safe) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.logp(Level.INFO, "safe", safe.toLowerCase, safe, Array[String](tainted)) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.logrb(Level.INFO, tainted, "safe", "bundle", safe, Array[String](safe)) + // {/fact} + + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.severe(tainted + "safe" + safe) + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.warning(tainted.replaceAll("\n", "")) // still insecure (CR not replaced) + // {/fact} + + // these should not be reported + logger.fine(safe) + logger.log(Level.INFO, "safe".toUpperCase, safe + safe) + logger.logp(Level.INFO, safe, safe, safe, Array[String](safe)) + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.logrb(Level.INFO, safe, safe, tainted + "bundle", safe) // bundle name can be tainted + // {/fact} + + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.info(tainted.replace('\n', ' ').replace('\r', ' ')) + var encoded = tainted.replace("\r", "").toUpperCase + encoded = "safe" + encoded.toLowerCase + // {/fact} + + // {fact rule=file-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CLRFInjectionLogs + logger.warning(encoded.replace("\n", " (new line)")) + logger.fine(tainted.replaceAll("[\r\n]+", "")) + // {/fact} + } +} diff --git a/PR_6_scala/scala/inject/rule-CLRFInjectionLogs.yml b/PR_6_scala/scala/inject/rule-CLRFInjectionLogs.yml new file mode 100644 index 0000000..68a2ede --- /dev/null +++ b/PR_6_scala/scala/inject/rule-CLRFInjectionLogs.yml @@ -0,0 +1,66 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-CLRFInjectionLogs" + languages: + - "scala" + message: | + When data from an untrusted source is put into a logger and not neutralized correctly, an + attacker could forge log entries or include malicious content. Inserted false entries could be + used to skew statistics, distract the administrator or even to implicate another party in the + commission of a malicious act. If the log file is processed automatically, the attacker can + render the file unusable by corrupting the format of the file or injecting unexpected + characters. An attacker may also inject code or other commands into the log file and take + advantage of a vulnerability in the log processing utility (e.g. command injection or XSS). + metadata: + category: "security" + cwe: "CWE-93" + shortDescription: "Improper Neutralization of CRLF Sequences ('CRLF Injection')" + technology: + - "scala" + security-severity: "CRITICAL" + mode: "taint" + pattern-sanitizers: + - patterns: + - pattern-inside: |- + $STR.replaceAll("$REPLACE_CHAR", "$REPLACE"); + - pattern: "$STR" + - metavariable-regex: + metavariable: "$REPLACE_CHAR" + regex: "(.*\\\\r\\\\n.*)" + - metavariable-regex: + metavariable: "$REPLACE" + regex: "(?!(\\\\r\\\\n))" + - pattern: "org.owasp.encoder.Encode.forUriComponent(...)" + - pattern: "org.owasp.encoder.Encode.forUri(...)" + - pattern: "java.net.URLEncoder.encode(..., $CHARSET)" + pattern-sinks: + - patterns: + - patterns: + - pattern: "$LOGGER.$METHOD(...,<...$TAINTED...>,...)" + - focus-metavariable: "$TAINTED" + - metavariable-regex: + metavariable: "$METHOD" + regex: "(log|logp|logrb|entering|exiting|fine|finer|finest|info|debug|trace|warn|warning|config|error|severe)" + - metavariable-pattern: + metavariable: "$LOGGER" + pattern-either: + - pattern: "Logger" + - pattern: "log" + - pattern: "logger" + - pattern: "org.pmw.tinylog.Logger" + - pattern: "org.apache.log4j.Logger" + - pattern: "org.apache.logging.log4j.Logger" + - pattern: "org.slf4j.Logger" + - pattern: "org.apache.commons.logging.Log" + - pattern: "java.util.logging.Logger" + pattern-sources: + - patterns: + - pattern-inside: | + def $FUNC(..., $REQ: HttpServletRequest, ...) : $TYPE = { + ... + } + - pattern: "$REQ.getParameter(...)" + severity: "ERROR" diff --git a/PR_6_scala/scala/inject/rule-CommandInjection.scala b/PR_6_scala/scala/inject/rule-CommandInjection.scala new file mode 100644 index 0000000..3af79b3 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-CommandInjection.scala @@ -0,0 +1,38 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +// scaffold: dependencies=com.amazonaws.aws-java-sdk-simpledb@1.12.187 +package inject + +import java.io.IOException +import java.util.Arrays + +class CommandInjection { + @throws[IOException] + def danger(cmd: String): Unit = { + val r = Runtime.getRuntime + r.exec(cmd) + r.exec(Array[String]("test")) + // {fact rule=os-command-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CommandInjection + r.exec(Array[String]("bash", cmd)) + // {/fact} + + + // {fact rule=os-command-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CommandInjection + r.exec(Array[String]("/bin/sh", "-c", cmd)) + // {/fact} + } + + def danger2(cmd: String): Unit = { + val b = new ProcessBuilder() + // {fact rule=os-command-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CommandInjection + b.command(cmd) + // {/fact} + b.command("test") + // {fact rule=os-command-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-CommandInjection + b.command(Arrays.asList("/bin/sh", "-c", cmd)) + // {/fact} + } +} diff --git a/PR_6_scala/scala/inject/rule-CommandInjection.yml b/PR_6_scala/scala/inject/rule-CommandInjection.yml new file mode 100644 index 0000000..186efaf --- /dev/null +++ b/PR_6_scala/scala/inject/rule-CommandInjection.yml @@ -0,0 +1,69 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-CommandInjection" + languages: + - "scala" + message: | + The highlighted API is used to execute a system command. If unfiltered input is passed to this + API, it can lead to arbitrary command execution. + metadata: + category: "security" + cwe: "CWE-78" + shortDescription: "Improper Neutralization of Special Elements used in an OS Command + ('OS Command Injection')" + technology: + - "scala" + security-severity: "MEDIUM" + pattern-either: + - patterns: + - pattern-inside: | + def $FUNC(..., $PARAM: String, ...): $TYPE = { + ... + } + - pattern-inside: | + val $RT = Runtime.getRuntime + ... + - pattern-either: + - pattern: "$RT.exec($PARAM)" + - pattern: | + var $CMDARR = new Array[String]("$SHELL",...,$PARAM,...) + ... + $RT.exec($CMDARR,...) + - pattern: "$RT.exec(Array[String](\"$SHELL\",...,$PARAM,...), ...)" + - pattern: "$RT.exec(java.util.String.format(\"...\", ...,$PARAM,...))" + - pattern: "$RT.exec(($A: String) + ($B: String))" + - metavariable-regex: + metavariable: "$SHELL" + regex: "(/.../)?(sh|bash|ksh|csh|tcsh|zsh)$" + - pattern-not: "$RT.exec(\"...\",\"...\",\"...\",...)" + - pattern-not: "$RT.exec(new Array[String](\"...\",\"...\",\"...\",...),...)" + - patterns: + - pattern-inside: | + def $FUNC(...,$PARAM: String, ...): $TYPE = { + ... + } + - pattern-inside: | + val $PB = new ProcessBuilder() + ... + - pattern-either: + - pattern: "$PB.command($PARAM,...)" + - patterns: + - pattern-either: + - pattern: "$PB.command(\"$SHELL\",...,$PARAM,...)" + - pattern: | + var $CMDARR = java.util.Arrays.asList("$SHELL",...,$PARAM,...) + ... + $PB.command($CMDARR,...) + - pattern: "$PB.command(java.util.Arrays.asList(\"$SHELL\",...,$PARAM,...),...)" + - pattern: "$PB.command(java.util.String.format(\"...\", ...,$PARAM,...))" + - pattern: "$PB.command(($A: String) + ($B: String))" + - metavariable-regex: + metavariable: "$SHELL" + regex: "(/.../)?(sh|bash|ksh|csh|tcsh|zsh)$" + - pattern-not: "$PB.command(\"...\",\"...\",\"...\",...)" + - pattern-not: | + $PB.command(java.util.Arrays.asList("...","...","...",...)) + severity: "WARNING" diff --git a/PR_6_scala/scala/inject/rule-CustomInjection.scala b/PR_6_scala/scala/inject/rule-CustomInjection.scala new file mode 100644 index 0000000..1e01c03 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-CustomInjection.scala @@ -0,0 +1,111 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +// scaffold: dependencies=com.amazonaws.aws-java-sdk-simpledb@1.12.187 +import java.sql.Connection +import java.sql.ResultSet +import java.sql.SQLException +import java.sql.Statement +import javax.sql.DataSource + +class CustomInjection { + // {fact rule=sql-injection@v1.0 defects=1} + @throws[SQLException] + def danger(dataSource: DataSource, input: String): Unit = { + val sql = "select * from Users where name = " + input + val connection = dataSource.getConnection + try { + val statement = connection.createStatement + try { + val resultSet = statement.executeQuery(sql) + System.out.println(resultSet) + } catch { + case _: Throwable => + } finally if (statement != null) statement.close() + } catch { + case _: Throwable => + } + } + // {/fact} + + // {fact rule=sql-injection@v1.0 defects=1} + @throws[SQLException] + def danger2(dataSource: DataSource, input: String): Unit = { + val value = String.format("%s", input) + val sql = "select * from Users where name = " + input + val connection = dataSource.getConnection + try { + val statement = connection.createStatement + try { + val resultSet = statement.executeQuery(sql) + System.out.println(resultSet) + } catch { + case _: Throwable => + } finally if (statement != null) statement.close() + } catch { + case _: Throwable => + } + } + // {/fact} + + // {fact rule=sql-injection@v1.0 defects=1} + @throws[SQLException] + def danger3(dataSource: DataSource, input: String): Unit = { + val connection = dataSource.getConnection + try { + val statement = connection.createStatement + try { + val resultSet = + // ruleid: scala_inject_rule-CustomInjection + statement.executeQuery("select * from Users where name = " + input) + System.out.println(resultSet) + } catch { + case _: Throwable => + } finally if (statement != null) statement.close() + } catch { + case _: Throwable => + } + } + // {/fact} + + + // {fact rule=sql-injection@v1.0 defects=1} + @throws[SQLException] + def danger4(dataSource: DataSource, input: String): Unit = { + val connection = dataSource.getConnection + try { + val statement = connection.createStatement + try { + // ruleid: scala_inject_rule-CustomInjection + val resultSet = statement.executeQuery( + "select * from Users where name = " + String.format("%s", input) + ) + System.out.println(resultSet) + } catch { + case _: Throwable => + } finally if (statement != null) statement.close() + } catch { + case _: Throwable => + } + } + // {/fact} + + // {fact rule=sql-injection@v1.0 defects=1} + @throws[SQLException] + def danger5(dataSource: DataSource, input: String): Unit = { + val connection = dataSource.getConnection + try { + val statement = connection.createStatement + try { + // ruleid: scala_inject_rule-CustomInjection + val resultSet = statement.executeQuery( + String.format("select * from Users where name = %s", input) + ) + System.out.println(resultSet) + } catch { + case _: Throwable => + } finally if (statement != null) statement.close() + } catch { + case _: Throwable => + } + } + // {/fact} +} diff --git a/PR_6_scala/scala/inject/rule-CustomInjection.yml b/PR_6_scala/scala/inject/rule-CustomInjection.yml new file mode 100644 index 0000000..ddbb711 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-CustomInjection.yml @@ -0,0 +1,42 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-CustomInjection" + languages: + - "scala" + message: | + The method identified is susceptible to injection. The input should be validated and properly + escaped. + metadata: + category: "security" + cwe: "CWE-89" + shortDescription: "Improper Neutralization of Special Elements used in an SQL + Command ('SQL Injection')" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern-either: + - pattern-inside: | + val $ST = connection.createStatement + ... + - pattern-either: + - pattern: | + val $QUERY = ... + $VAR + ... + ... + $ST.executeQuery($QUERY) + - pattern: | + val $QUERY = ... + $VAR + ... + $ST.executeQuery($QUERY) + - pattern: | + val $QUERY = String.format("...",...,$VAR,...) + ... + $ST.executeQuery($QUERY) + - pattern: "$ST.executeQuery(($SB: StringBuilder).toString())" + - pattern: "$ST.executeQuery(... + $VAR + ...)" + - pattern: "$ST.executeQuery(... + $VAR)" + - pattern: "$ST.executeQuery(...,String.format(\"...\",...,$VAR,...), ...)" + severity: "WARNING" diff --git a/PR_6_scala/scala/inject/rule-CustomInjectionSQLString.scala b/PR_6_scala/scala/inject/rule-CustomInjectionSQLString.scala new file mode 100644 index 0000000..cce7f72 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-CustomInjectionSQLString.scala @@ -0,0 +1,2 @@ +// License: MIT (c) GitLab Inc. +// TODO: Placeholder file; add example code. \ No newline at end of file diff --git a/PR_6_scala/scala/inject/rule-CustomInjectionSQLString.yml b/PR_6_scala/scala/inject/rule-CustomInjectionSQLString.yml new file mode 100644 index 0000000..a4908c1 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-CustomInjectionSQLString.yml @@ -0,0 +1,46 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-CustomInjectionSQLString" + languages: + - "scala" + message: | + The method identified is susceptible to injection. The input should be validated and properly + escaped. + metadata: + category: "security" + cwe: "CWE-89" + shortDescription: "Improper Neutralization of Special Elements used in an SQL + Command ('SQL Injection')" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern-inside: | + def $FOO(..., $SQLIN: String, ...): $TYPE = { + ... + } + - pattern-either: + - pattern: | + "$SQL_STR" + $SQLIN + - pattern: "String.format(\"$SQL_STR\", ... + $SQLIN + ...)" + - pattern: | + "$SQL_STR".concat(...) + - pattern: "(StringBuilder $BUILDER). ... .append(\"$SQL_STR\")" + - patterns: + - pattern-inside: | + StringBuilder $BUILDER = new StringBuilder(... + "$SQL_STR" + ...); + ... + - pattern: "$BUILDER.append(...)" + - pattern-not: "$BUILDER.append(\"...\")" + - patterns: + - pattern-inside: | + $QUERY = "$SQL_STR"; + ... + - pattern: "$QUERY += ..." + - metavariable-regex: + metavariable: "$SQL_STR" + regex: "(?i)(select|insert|create|update|alter|delete|drop)\\b" + severity: "WARNING" diff --git a/PR_6_scala/scala/inject/rule-ELInjection.scala b/PR_6_scala/scala/inject/rule-ELInjection.scala new file mode 100644 index 0000000..b6fa4ab --- /dev/null +++ b/PR_6_scala/scala/inject/rule-ELInjection.scala @@ -0,0 +1,31 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package inject + +import javax.el._ +import javax.faces.context.FacesContext + + +class ELInjection { + // {fact rule=code-injection@v1.0 defects=1} + def valueExpr(expression: String) = { + val context = FacesContext.getCurrentInstance + val expressionFactory = context.getApplication.getExpressionFactory + val elContext = context.getELContext + // ruleid: scala_inject_rule-ELInjection + val vex = expressionFactory.createValueExpression(elContext, expression, classOf[Nothing]) + vex.getValue(elContext).asInstanceOf[Nothing] + } + // {/fact} + + + // {fact rule=code-injection@v1.0 defects=1} + def methodExpr(expression: String) = { + val context = FacesContext.getCurrentInstance + val expressionFactory = context.getApplication.getExpressionFactory + val elContext = context.getELContext + // ruleid: scala_inject_rule-ELInjection + val ex = expressionFactory.createMethodExpression(elContext, expression, classOf[Nothing], null) + ex.getMethodInfo(elContext) + } + // {/fact} +} diff --git a/PR_6_scala/scala/inject/rule-ELInjection.yml b/PR_6_scala/scala/inject/rule-ELInjection.yml new file mode 100644 index 0000000..7974484 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-ELInjection.yml @@ -0,0 +1,35 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-ELInjection" + languages: + - "scala" + message: | + An expression is built with a dynamic value. The source of the value(s) should be verified to + avoid that unfiltered values fall into this risky code evaluation. + metadata: + category: "security" + cwe: "CWE-94" + shortDescription: "Improper Control of Generation of Code ('Code Injection')" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern-inside: | + import javax.el._ + ... + - pattern-either: + - pattern-inside: | + def $FUNC(..., $EXPR: String, ...) : $TYPE = { + ... + } + - pattern-inside: | + def $FUNC(..., $EXPR: String, ...) = { + ... + } + - pattern-either: + - pattern: "$X.createValueExpression(..., $EXPR, ...)" + - pattern: "$X.createMethodExpression(..., $EXPR, ...)" + severity: "WARNING" diff --git a/PR_6_scala/scala/inject/rule-FileDisclosure.scala b/PR_6_scala/scala/inject/rule-FileDisclosure.scala new file mode 100644 index 0000000..d6137ee --- /dev/null +++ b/PR_6_scala/scala/inject/rule-FileDisclosure.scala @@ -0,0 +1,98 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +// source: https://github.com/find-sec-bugs/find-sec-bugs/blob/master/findsecbugs-samples-java/src/test/java/testcode/file/FileDisclosure.java +// hash: a7694d0 +package inject + +import java.io.IOException +import org.apache.struts.action.ActionForward +import org.springframework.web.servlet.ModelAndView +import javax.servlet.RequestDispatcher +import javax.servlet.http.HttpServlet +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse +import java.util + + +// REQUESTDISPATCHER_FILE_DISCLOSURE +class FileDisclosure extends HttpServlet { + @throws[IOException] + override def doGet(request: HttpServletRequest, response: HttpServletResponse): Unit = { + try { + val returnURL = request.getParameter("returnURL") + /** ****Struts ActionForward vulnerable code tests***** */ + // {fact rule=external-access-to-files-or-directories@v1.0 defects=1} + // ruleid: scala_inject_rule-FileDisclosure + val forward = new ActionForward(returnURL) //BAD + // {/fact} + + // {fact rule=external-access-to-files-or-directories@v1.0 defects=1} + // ruleid: scala_inject_rule-FileDisclosure + val forward2 = new ActionForward(returnURL, true) + // {/fact} + + // {fact rule=external-access-to-files-or-directories@v1.0 defects=1} + // ruleid: scala_inject_rule-FileDisclosure + val forward3 = new ActionForward("name", returnURL, true) + // {/fact} + + // {fact rule=external-access-to-files-or-directories@v1.0 defects=1} + // ruleid: scala_inject_rule-FileDisclosure + val forward4 = new ActionForward("name", returnURL, true) + val forward5 = new ActionForward + + // {/fact} + + + // {fact rule=external-access-to-files-or-directories@v1.0 defects=1} + // ruleid: scala_inject_rule-FileDisclosure + forward5.setPath(returnURL) //BAD + // {/fact} + + // {fact rule=external-access-to-files-or-directories@v1.0 defects=0} + //false positive test - returnURL moved from path to name (safe argument) + val forward6 = new ActionForward(returnURL, "path", true) //OK + // {/fact} + + // {fact rule=external-access-to-files-or-directories@v1.0 defects=1} + /** ****Spring ModelAndView vulnerable code tests***** */ + // ruleid: scala_inject_rule-FileDisclosure + val mv = new ModelAndView(returnURL) + // {/fact} + + // {fact rule=external-access-to-files-or-directories@v1.0 defects=1} + val mv4 = new ModelAndView + // ruleid: scala_inject_rule-FileDisclosure + mv4.setViewName(returnURL) + // {/fact} + + + //false positive test - returnURL moved from viewName to modelName (safe argument) + } catch { + case e: Exception => + System.out.println(e) + } + } + + @throws[IOException] + def doGet2(request: HttpServletRequest, response: HttpServletResponse): Unit = { + try { + val jspFile = request.getParameter("jspFile") + var requestDispatcher = request.getRequestDispatcher(jspFile) + // {fact rule=file-system-access@v1.0 defects=1} + // ruleid: scala_inject_rule-FileDisclosure + requestDispatcher.include(request, response) + // {/fact} + + // {fact rule=file-system-access@v1.0 defects=1} + requestDispatcher = request.getSession.getServletContext.getRequestDispatcher(jspFile) + // ruleid: scala_inject_rule-FileDisclosure + requestDispatcher.forward(request, response) + // {/fact} + + + } catch { + case e: Exception => + System.out.println(e) + } + } +} diff --git a/PR_6_scala/scala/inject/rule-FileDisclosure.yml b/PR_6_scala/scala/inject/rule-FileDisclosure.yml new file mode 100644 index 0000000..c6df376 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-FileDisclosure.yml @@ -0,0 +1,60 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-FileDisclosure" + languages: + - "scala" + message: | + Constructing a server-side redirect path with user input could allow an + attacker to download application binaries (including application classes or + jar files) or view arbitrary files within protected directories. + metadata: + category: "security" + cwe: "CWE-552" + shortDescription: "Files or Directories Accessible to External Parties" + security-severity: "CRITICAL" + mode: "taint" + pattern-sinks: + - patterns: + - pattern: "new org.springframework.web.servlet.ModelAndView($FST)" + - pattern: "$FST" + - patterns: + - pattern: "new org.springframework.web.servlet.ModelAndView($FST, $SND)" + - pattern: "$FST" + - patterns: + - pattern: "new org.springframework.web.servlet.ModelAndView($FST, $SND, $TRD)" + - pattern: "$FST" + - patterns: + - pattern: "new org.apache.struts.action.ActionForward($FST)" + - pattern: "$FST" + - patterns: + - pattern: "new org.apache.struts.action.ActionForward($FST, $SND)" + - pattern: "$FST" + - patterns: + - pattern: "new org.apache.struts.action.ActionForward($FST, $SND, $TRD)" + - pattern: "$SND" + - patterns: + - pattern: "new org.apache.struts.action.ActionForward($FST, $SND, $TRD)" + - pattern: "$TRD" + - patterns: + - pattern-inside: | + $ACTION = new org.apache.struts.action.ActionForward() + ... + - pattern: "$ACTION.setPath(...)" + - patterns: + - pattern-inside: | + $MVC = new org.springframework.web.servlet.ModelAndView() + ... + - pattern: "$MVC.setViewName(...);" + - patterns: + - pattern-inside: | + $REQ = $HTTP.getRequestDispatcher(...) + ... + - pattern-either: + - pattern: "$REQ.include($FST, $SND)" + - pattern: "$REQ.forward($FST, $SND)" + pattern-sources: + - pattern: "($VAR: javax.servlet.http.HttpServletRequest).getParameter(...)" + severity: "ERROR" diff --git a/PR_6_scala/scala/inject/rule-HttpParameterPollution.scala b/PR_6_scala/scala/inject/rule-HttpParameterPollution.scala new file mode 100644 index 0000000..9586705 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-HttpParameterPollution.scala @@ -0,0 +1,49 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package inject + +import org.apache.commons.httpclient.methods.GetMethod +import org.apache.http.client.methods.HttpGet +import javax.servlet.http.HttpServlet +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse +import java.io.IOException +import java.net.URLEncoder +import com.google.common.net.UrlEscapers.urlPathSegmentEscaper + + +class HttpParameterPollution extends HttpServlet { + override def doGet(request: HttpServletRequest, response: HttpServletResponse): Unit = { + try { + val item = request.getParameter("item") + //in HttpClient 4.x, there is no GetMethod anymore. Instead there is HttpGet + // {fact rule=os-command-injection@v1.0 defects=0} + val httpget = new HttpGet("http://host.com?param=" + URLEncoder.encode(item)) //OK + // {/fact} + + + + // {fact rule=os-command-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-HttpParameterPollution + val httpget2 = new HttpGet("http://host.com?param=" + item) //BAD + // {/fact} + + // {fact rule=os-command-injection@v1.0 defects=0} + val httpget3 = new HttpGet("http://host.com?param=" + urlPathSegmentEscaper().escape(item)) + // {/fact} + + // {fact rule=os-command-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-HttpParameterPollution + val get = new GetMethod("http://host.com?param=" + item) + // {/fact} + + + // {fact rule=os-command-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-HttpParameterPollution + get.setQueryString("item=" + item) //BAD + // {/fact} + } catch { + case e: Exception => + System.out.println(e) + } + } +} diff --git a/PR_6_scala/scala/inject/rule-HttpParameterPollution.yml b/PR_6_scala/scala/inject/rule-HttpParameterPollution.yml new file mode 100644 index 0000000..515b02d --- /dev/null +++ b/PR_6_scala/scala/inject/rule-HttpParameterPollution.yml @@ -0,0 +1,34 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-HttpParameterPollution" + languages: + - "scala" + message: | + Concatenating unvalidated user input into a URL can allow an attacker to override the value of + a request parameter. Attacker may be able to override existing parameter values, inject a new + parameter or exploit variables out of a direct reach. HTTP Parameter Pollution (HPP) attacks + consist of injecting encoded query string delimiters into other existing parameters. If a web + application does not properly sanitize the user input, a malicious user may compromise the + logic of the application to perform either client-side or server-side attacks. + metadata: + category: "security" + cwe: "CWE-88" + shortDescription: "Improper Neutralization of Argument Delimiters in a Command + ('Argument Injection')" + technology: + - "scala" + security-severity: "CRITICAL" + mode: "taint" + pattern-sanitizers: + - pattern: "java.net.URLEncoder.encode(...)" + - pattern: "com.google.common.net.UrlEscapers.urlPathSegmentEscaper().escape(...)" + pattern-sinks: + - pattern: "new org.apache.http.client.methods.HttpGet(...)" + - pattern: "new org.apache.commons.httpclient.methods.GetMethod(...)" + - pattern: "($GM: org.apache.commons.httpclient.methods.GetMethod).setQueryString(...)" + pattern-sources: + - pattern: "($REQ: HttpServletRequest ).getParameter(...)" + severity: "ERROR" diff --git a/PR_6_scala/scala/inject/rule-LDAPInjection.scala b/PR_6_scala/scala/inject/rule-LDAPInjection.scala new file mode 100644 index 0000000..aeec848 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-LDAPInjection.scala @@ -0,0 +1,185 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package inject + +import org.springframework.ldap.core.DefaultNameClassPairMapper +import org.springframework.ldap.core.DirContextProcessor +import org.springframework.ldap.core.LdapEntryIdentificationContextMapper +import org.springframework.ldap.core.LdapTemplate +import org.springframework.ldap.core.support.CountNameClassPairCallbackHandler +import org.springframework.ldap.core.support.DefaultIncrementalAttributesMapper +import javax.naming.Context +import javax.naming.NamingEnumeration +import javax.naming.NamingException +import javax.naming.directory.DirContext +import javax.naming.directory.InitialDirContext +import javax.naming.directory.SearchControls +import javax.naming.directory.SearchResult +import java.util +import java.util.Properties + + +object LDAPInjection { + private val ldapURI = "ldaps://ldap.server.com/dc=ldap,dc=server,dc=com" + private val contextFactory = "com.sun.jndi.ldap.LdapCtxFactory" + + /** *************** JNDI LDAP ********************* */ + private[inject] def authenticate(username: String, password: String) = try { + val props = new Properties(); + props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory") + props.put(Context.PROVIDER_URL, "ldap://ldap.example.com") + props.put(Context.REFERRAL, "ignore") + props.put(Context.SECURITY_PRINCIPAL, dnFromUser(username)) + props.put(Context.SECURITY_CREDENTIALS, password) + new InitialDirContext(props) + true + } catch { + case e: Exception => + false + } + + @throws[NamingException] + private def dnFromUser(username: String) = { + val props = new Properties(); + props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory") + props.put(Context.PROVIDER_URL, "ldap://ldap.example.com") + props.put(Context.REFERRAL, "ignore") + val context = new InitialDirContext(props) + val ctrls = new SearchControls + ctrls.setReturningAttributes(Array[String]("givenName", "sn")) + ctrls.setSearchScope(SearchControls.SUBTREE_SCOPE) + val answers = context.search("dc=People,dc=example,dc=com", "(uid=" + username + ")", ctrls) + val result = answers.next + result.getNameInNamespace + } + + @throws[Exception] + private def ldapContext(env: util.Hashtable[String,String]) = { + env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory) + env.put(Context.PROVIDER_URL, ldapURI) + env.put(Context.SECURITY_AUTHENTICATION, "none") + val ctx = new InitialDirContext(env) + ctx + } + + @throws[Exception] + def testBind(dn: Nothing, password: String): Boolean = { + val env = new util.Hashtable[String,String] + env.put(Context.SECURITY_AUTHENTICATION, "simple") //false positive + + env.put(Context.SECURITY_PRINCIPAL, dn) + env.put(Context.SECURITY_CREDENTIALS, password) + try ldapContext(env) + catch { + case e: Exception => + return false + } + true + } + + /** *************** JNDI LDAP SPECIAL ********************* */ + @throws[NamingException] + def main(param: Nothing): Unit = { + val ctx: DirContext = null + val base = "ou=users,ou=system" + val sc = new SearchControls() + sc.setSearchScope(SearchControls.SUBTREE_SCOPE) + val filter = "(&(objectclass=person))(|(uid=" + param + ")(street={0}))" + val filters = Array[Object]("The streetz 4 Ms bar") + System.out.println("Filter " + filter) + ctx.search(base, filter, filters, sc) + } +} + +class LDAPInjection { + /** *************** SPRING LDAP ********************* */ + @throws[NamingException] + def queryVulnerableToInjection(template: LdapTemplate, jndiInjectMe: String, searchControls: SearchControls, dirContextProcessor: DirContextProcessor): Unit = { + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.list(jndiInjectMe) + // {/fact} + + + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.list(jndiInjectMe, new DefaultNameClassPairMapper) + // {/fact} + + + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.list(jndiInjectMe, new CountNameClassPairCallbackHandler) + // {/fact} + + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.lookup(jndiInjectMe) + // {/fact} + val mapper = new DefaultIncrementalAttributesMapper("") + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.lookup(jndiInjectMe, mapper) + // {/fact} + + + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.lookup(jndiInjectMe, mapper) + // {/fact} + + + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.search(jndiInjectMe, "dn=1", searchControls, mapper) + // {/fact} + + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.search(jndiInjectMe, "dn=1", searchControls, mapper, dirContextProcessor) + // {/fact} + + + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.search(jndiInjectMe, "dn=1", searchControls, mapper, dirContextProcessor) + // {/fact} + + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.search(jndiInjectMe, "dn=1", searchControls, mapper, dirContextProcessor) + // {/fact} + + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.search(jndiInjectMe, "dn=1", mapper) + // {/fact} + + + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.search(jndiInjectMe, "dn=1", SearchControls.OBJECT_SCOPE, new Array[String](0), mapper) + // {/fact} + + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.search(jndiInjectMe, "dn=1", SearchControls.OBJECT_SCOPE, mapper) + // {/fact} + + // {fact rule=ldap-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-LDAPInjection + template.search(jndiInjectMe, "dn=1", mapper) + // {/fact} + } + + @throws[NamingException] + def safeQuery(template: LdapTemplate, searchControls: String, dirContextProcessor: DirContextProcessor): Unit = { + val safeQuery = "uid=test" + template.list(safeQuery) + template.list(safeQuery, new DefaultNameClassPairMapper) + template.list(safeQuery, new CountNameClassPairCallbackHandler()) + template.lookup(safeQuery) + val mapper = new DefaultIncrementalAttributesMapper("") + template.lookup(safeQuery, mapper) + template.lookup(safeQuery, mapper) + } +} diff --git a/PR_6_scala/scala/inject/rule-LDAPInjection.yml b/PR_6_scala/scala/inject/rule-LDAPInjection.yml new file mode 100644 index 0000000..651eb7d --- /dev/null +++ b/PR_6_scala/scala/inject/rule-LDAPInjection.yml @@ -0,0 +1,52 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-LDAPInjection" + languages: + - "scala" + message: | + Just like SQL, all inputs passed to an LDAP query need to be passed in safely. Unfortunately, + LDAP doesn't have prepared statement interfaces like SQL. Therefore, the primary defense + against LDAP injection is strong input validation of any untrusted data before including it in + an LDAP query. + metadata: + category: "security" + cwe: "CWE-90" + shortDescription: "Improper Neutralization of Special Elements used in an LDAP + Query ('LDAP Injection')" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern-either: + - pattern-inside: | + def $FUNC(..., $VAR: String, ...): $TYPE = { + ... + } + - pattern-inside: | + def $FUNC(..., $X: String, ...): $TYPE = { + ... + $VAR = ... + $X; + ... + } + - pattern-either: + - pattern: "($P: java.util.Properties).put($KEY, $VAR)" + - pattern: "$CTX.lookup(..., $VAR, ...)" + - pattern: "$CTX.search(..., $VAR, ...)" + - pattern: "$CTX.list(..., $VAR, ...)" + - metavariable-pattern: + metavariable: "$CTX" + pattern-either: + - pattern: "($CTX: javax.naming.directory.DirContext)" + - pattern: "($CTX: javax.naming.directory.Context)" + - pattern: "($CTX: javax.naming.Context)" + - pattern: "($CTX: javax.naming.directory.InitialDirContext)" + - pattern: "($CTX: javax.naming.ldap.LdapContext)" + - pattern: "($CTX: com.unboundid.ldap.sdk.LDAPConnection)" + - pattern: "($CTX: javax.naming.event.EventDirContext)" + - pattern: "($CTX: com.sun.jndi.ldap.LdapCtx)" + - pattern: "($CTX: org.springframework.ldap.core.LdapTemplate)" + - pattern: "($CTX: org.springframework.ldap.core.LdapOperations)" + severity: "WARNING" diff --git a/PR_6_scala/scala/inject/rule-OgnlInjection.scala b/PR_6_scala/scala/inject/rule-OgnlInjection.scala new file mode 100644 index 0000000..36aba23 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-OgnlInjection.scala @@ -0,0 +1,178 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package inject + +import com.opensymphony.xwork2.ognl.OgnlReflectionProvider +import com.opensymphony.xwork2.ognl.OgnlUtil +import com.opensymphony.xwork2.util.TextParseUtil +import ognl.OgnlException +import com.opensymphony.xwork2.ognl + +import javax.management.ReflectionException +import java.beans.IntrospectionException +import java.util + + +class OgnlInjection { + @throws[OgnlException] + @throws[ReflectionException] + def unsafeOgnlUtil(ognlUtil: OgnlUtil, input: String, propsInput: util.HashMap[String,String]): Unit = { + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + ognlUtil.setValue(input, null, null, "12345") + // {/fact} + + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + ognlUtil.getValue(input, null, null, null + // {/fact} + + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + ognlUtil.setProperty(input, "12345", null, null) + // {/fact} + + + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + ognlUtil.setProperty(input, "12345", null, null, true) + // {/fact} + + + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + ognlUtil.setProperties(propsInput, new Object()) + // {/fact} + + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + ognlUtil.setProperties(propsInput, null, null, true) + // {/fact} + + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + ognlUtil.setProperties(propsInput, null, true) + // {/fact} + + + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + ognlUtil.setProperties(propsInput, null) + // {/fact} + //ognlUtil.callMethod(input, null, null); + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + ognlUtil.compile(input) + // {/fact} + + + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + ognlUtil.compile(input) + // {/fact} + } + + @throws[OgnlException] + @throws[ReflectionException] + def safeOgnlUtil(ognlUtil: OgnlUtil): Unit = { + val input = "thisissafe" + val map = new util.HashMap[String,String]() + ognlUtil.setValue(input, null, null, "12345") + ognlUtil.getValue(input, null, null, null) + ognlUtil.setProperty(input, "12345", null, null) + ognlUtil.setProperty(input, "12345", null, null, true) + ognlUtil.setProperties(map, null, null) + ognlUtil.setProperties(map, null, null, true) + ognlUtil.setProperties(map, null, true) + ognlUtil.setProperties(map, null) + ognlUtil.compile(input) + ognlUtil.compile(input) + } + + @throws[ReflectionException] + @throws[IntrospectionException] + def unsafeOgnlReflectionProvider(input: Nothing, propsInput: Nothing, reflectionProvider: Nothing, `type`: Nothing): Unit = { + var reflectionProvider: OgnlReflectionProvider = null + reflectionProvider.getGetMethod(`type`, input) + reflectionProvider.getSetMethod(`type`, input) + reflectionProvider.getField(`type`, input) + reflectionProvider.setProperties(propsInput, null, null, true) + reflectionProvider.setProperties(propsInput, null, null) + reflectionProvider.setProperties(propsInput, null) + reflectionProvider.setProperty(input, "test", null, null) + // reflectionProvider.setProperty( input, "test",null, null, true); + reflectionProvider.getValue(input, null, null) + reflectionProvider.setValue(input, null, null, null) + } + + @throws[IntrospectionException] + @throws[ReflectionException] + def safeOgnlReflectionProvider(reflectionProvider: Nothing, `type`: Nothing): Unit = { + var reflectionProvider: OgnlReflectionProvider = null + val input = "thisissafe" + val constant1 = "" + val constant2 = "" + + val map = new util.HashMap[String,String]() + reflectionProvider.getGetMethod(`type`, input) + reflectionProvider.getSetMethod(`type`, input) + reflectionProvider.getField(`type`, input) + reflectionProvider.setProperties(map, null, null, true) + reflectionProvider.setProperties(map, null, null) + reflectionProvider.setProperties(map, null) + reflectionProvider.setProperty("test", constant1, null, null) + // reflectionProvider.setProperty("test", constant2, null, null, true); + reflectionProvider.getValue(input, null, null) + reflectionProvider.setValue(input, null, null, null) + } + + def unsafeTextParseUtil(input: String): Unit = { + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + TextParseUtil.translateVariables(input, null) + // {/fact} + + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + TextParseUtil.translateVariables(input, null, null) + // {/fact} + + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + TextParseUtil.translateVariables('a', input, null) + // {/fact} + + + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + TextParseUtil.translateVariables('a', input, null, null) + // {/fact} + + + + // {fact rule=expression-language-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-OgnlInjection + TextParseUtil.translateVariables('a', input, null, null, null, 0) + // {/fact} + } + + def safeTextParseUtil(stack: Nothing, parsedValueEvaluator: Nothing, `type`: Nothing): Unit = { + val input = "1+1" + TextParseUtil.translateVariables(input, stack) + TextParseUtil.translateVariables(input, stack, parsedValueEvaluator) + TextParseUtil.translateVariables('a', input, stack) + TextParseUtil.translateVariables('a', input, stack, `type`) + TextParseUtil.translateVariables('a', input, stack, `type`, parsedValueEvaluator, 0) + } +} diff --git a/PR_6_scala/scala/inject/rule-OgnlInjection.yml b/PR_6_scala/scala/inject/rule-OgnlInjection.yml new file mode 100644 index 0000000..9455df4 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-OgnlInjection.yml @@ -0,0 +1,96 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-OgnlInjection" + patterns: + - pattern-either: + - pattern-inside: | + def $FUNC(..., $VAR: String, ...): $TYPE = { + ... + } + - pattern-inside: | + def $FUNC(..., $VAR: Map[$K,$V], ...): $TYPE = { + ... + } + - pattern-inside: | + def $FUNC(..., $VAR: java.util.HashMap[$K,$V], ...): $TYPE = { + ... + } + - pattern-either: + - pattern: "com.opensymphony.xwork2.util.TextParseUtil.translateVariables(..., + $VAR, ...)" + - pattern: "com.opensymphony.xwork2.util.TextParseUtil.translateVariablesCollection(..., + $VAR, ...)" + - pattern: "com.opensymphony.xwork2.util.TextParseUtil.shallBeIncluded(..., $VAR, + ...)" + - pattern: "com.opensymphony.xwork2.util.TextParseUtil.commaDelimitedStringToSet(..., + $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.util.TextParser).evaluate(..., $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.util.OgnlTextParser).evaluate(..., $VAR, + ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlReflectionProvider).getGetMethod(..., + $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlReflectionProvider).getSetMethod(..., + $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlReflectionProvider).getField(..., + $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlReflectionProvider).setProperties(..., + $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlReflectionProvider).setProperty(...,$VAR, + ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlReflectionProvider).getValue(...,$VAR, + ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlReflectionProvider).setValue(...,$VAR, + ...)" + - pattern: "($P:com.opensymphony.xwork2.util.reflection.ReflectionProvider).getGetMethod(..., + $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.util.reflection.ReflectionProvider).getSetMethod(..., + $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.util.reflection.ReflectionProvider).getField(..., + $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.util.reflection.ReflectionProvider).setProperties(..., + $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.util.reflection.ReflectionProvider).setProperty(..., + $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.util.reflection.ReflectionProvider).getValue(..., + $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.util.reflection.ReflectionProvider).setValue(..., + $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlUtil).setProperties(..., $VAR, + ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlUtil).setProperty(..., $VAR, + ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlUtil).getValue(..., $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlUtil).setValue(..., $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlUtil).callMethod(..., $VAR, ...)" + - pattern: "($P:com.opensymphony.xwork2.ognl.OgnlUtil).compile(..., $VAR, ...)" + - pattern: "($P:org.apache.struts2.util.VelocityStrutsUtil).evaluate(...)" + - pattern: "org.apache.struts2.util.StrutsUtil.findString(...)" + - pattern: "org.apache.struts2.util.StrutsUtil.findValue(..., $VAL)" + - pattern: "org.apache.struts2.util.StrutsUtil.getText(...)" + - pattern: "org.apache.struts2.util.StrutsUtil.translateVariables(...)" + - pattern: "org.apache.struts2.util.StrutsUtil.makeSelectList(..., $VAR, ...)" + - pattern: "($T:org.apache.struts2.views.jsp.ui.OgnlTool).findValue(..., $VAR, + ...)" + - pattern: "($V:com.opensymphony.xwork2.util.ValueStack).findString(...)" + - pattern: "($V:com.opensymphony.xwork2.util.ValueStack).findValue(..., $VAR, + ...)" + - pattern: "($V:com.opensymphony.xwork2.util.ValueStack).setValue(..., $VAR, ...)" + - pattern: "($V:com.opensymphony.xwork2.util.ValueStack).setParameter(..., $VAR, + ...)" + message: | + "A expression is built with a dynamic value. The source of the value(s) should be verified to + avoid that unfiltered values fall into this risky code evaluation." + languages: + - "scala" + severity: "WARNING" + metadata: + shortDescription: "Expression injection (OGNL)" + category: "security" + cwe: "CWE-917" + technology: + - "scala" + security-severity: "MEDIUM" diff --git a/PR_6_scala/scala/inject/rule-PathTraversal.scala b/PR_6_scala/scala/inject/rule-PathTraversal.scala new file mode 100644 index 0000000..c8d27dc --- /dev/null +++ b/PR_6_scala/scala/inject/rule-PathTraversal.scala @@ -0,0 +1,74 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package inject + +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.FileReader +import java.io.FileWriter +import java.io.IOException +import java.io.RandomAccessFile +import java.net.URI +import java.net.URISyntaxException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths + + +object PathTraversal { + private[inject] val safefinalString = "SAFE" + // {fact rule=path-traversal@v1.0 defects=1} + // DETECTS: PATH_TRAVERSAL_IN + @throws[IOException] + @throws[URISyntaxException] + def unsafe(input: String): Unit = { + new File(input) + new File("test/" + input, "misc.jpg") + new RandomAccessFile(input, "r") + new FileReader(input) + new FileInputStream(input) + new FileWriter("safe".toUpperCase) + new File(new String("safe")) + File.createTempFile(input, "safe") + File.createTempFile("safe", input) + new File(safefinalString) + } + // {/fact} +} + +class PathTraversal { // nio path traversal, DETECTS: PATH_TRAVERSAL_IN + // {fact rule=path-traversal@v1.0 defects=1} + def loadFile(path: String): Unit = { + Paths.get(path) + Paths.get(path, "foo") + Paths.get(path, "foo", "bar") + Paths.get("foo", path) + Paths.get("foo", "bar", path) + Paths.get("foo") + Paths.get("foo", "bar") + Paths.get("foo", "bar", "allsafe") + } + // {/fact} + + @throws[IOException] + def tempDir(input: String): Unit = { + val p = Paths.get("/") + Files.createTempFile(p, input, "") + Files.createTempFile(p, "", input) + Files.createTempFile(input, "") + Files.createTempFile("", input) + Files.createTempDirectory(p, input) + Files.createTempDirectory(input) + } + + // DETECTS: PATH_TRAVERSAL_OUT + // {fact rule=path-traversal@v1.0 defects=1} + @throws[IOException] + def pathTraversalWrite(input: String): Unit = { + new FileWriter(input) + new FileWriter(input, true) + new FileWriter(input) + new FileWriter(input, true) + } + // {/fact} +} diff --git a/PR_6_scala/scala/inject/rule-PathTraversalIn.yml b/PR_6_scala/scala/inject/rule-PathTraversalIn.yml new file mode 100644 index 0000000..45c1ed3 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-PathTraversalIn.yml @@ -0,0 +1,64 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-PathTraversalIn" + languages: + - "scala" + message: | + A file is opened to read its content. The filename comes from an input parameter. If an + unfiltered parameter is passed to this file API, files from an arbitrary filesystem location + could be read. This rule identifies potential path traversal vulnerabilities. In many cases, + the constructed file path cannot be controlled by the user. + metadata: + owasp: + - "A5:2017-Broken Access Control" + - "A01:2021-Broken Access Control" + category: "security" + cwe: "CWE-22" + shortDescription: "Improper limitation of a pathname to a restricted directory + ('Path Traversal')" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern-either: + - patterns: + - pattern-inside: | + def $FUNC(...,$ARGS: Array[String], ...): $TYPE = { + ... + } + - pattern-inside: | + $VAR = $ARGS($IDX) + ... + - pattern-inside: | + def $FUNC(...,$VAR: String, ...): $TYPE = { + ... + } + - pattern-not-inside: | + ... + org.apache.commons.io.FilenameUtils.getName($VAR) + ... + - pattern-either: + - patterns: + - pattern-inside: | + $U = new java.net.URI($VAR) + ... + - pattern-either: + - pattern: "new java.io.File($U)" + - pattern: "java.nio.file.Paths.get($U)" + - pattern: "new java.io.RandomAccessFile(..., $VAR,...)" + - pattern: "new java.io.FileReader(<...$VAR...>, ...)" + - pattern: "new javax.activation.FileDataSource(..., $VAR, ...)" + - pattern: "new java.io.FileInputStream(..., $VAR, ...)" + - pattern: "new java.io.File(<...$VAR...>, ...)" + - pattern: "java.nio.file.Paths.get(...,$VAR,...)" + - pattern: "java.io.File.createTempFile(...,$VAR, ...)" + - pattern: "java.io.File.createTempDirectory(...,$VAR,...)" + - pattern: "java.nio.file.Files.createTempFile(..., $VAR, ...)" + - pattern: "java.nio.file.Files.createTempDirectory(..., $VAR, ...)" + - pattern: "scala.io.Source.from(<...$VAR...>)" + - pattern: "scala.io.Source.fromFile(<...$VAR...>)" + - pattern: "scala.io.Source.fromString(<...$VAR...>)" + severity: "ERROR" diff --git a/PR_6_scala/scala/inject/rule-PathTraversalOut.yml b/PR_6_scala/scala/inject/rule-PathTraversalOut.yml new file mode 100644 index 0000000..770aa0c --- /dev/null +++ b/PR_6_scala/scala/inject/rule-PathTraversalOut.yml @@ -0,0 +1,50 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-PathTraversalOut" + languages: + - "scala" + message: | + A file is opened to write to its contents. The filename comes from an input parameter. If an + unfiltered parameter is passed to this file API, files at an arbitrary filesystem location + could be modified. This rule identifies potential path traversal vulnerabilities. In many + cases, the constructed file path cannot be controlled by the user. + metadata: + owasp: + - "A5:2017-Broken Access Control" + - "A01:2021-Broken Access Control" + category: "security" + cwe: "CWE-22" + shortDescription: "Improper limitation of a pathname to a restricted directory + ('Path Traversal')" + technology: + - "scala" + security-severity: "MEDIUM" + mode: "taint" + pattern-sanitizers: + - pattern: "org.apache.commons.io.FilenameUtils.getName(...)" + pattern-sinks: + - patterns: + - pattern-inside: |- + new java.io.FileWriter($PATH, ...) + - pattern: "$PATH" + - patterns: + - pattern-inside: |- + new java.io.FileOutputStream($PATH, ...) + - pattern: "$PATH" + pattern-sources: + - patterns: + - pattern-inside: | + def $FUNC(..., $ARGS: Array[String], ...): $TYPE = { + ... + } + - pattern: "$ARGS[$IDX]" + - patterns: + - pattern-inside: | + def $FUNC(..., $VAR: String, ...): $TYPE = { + ... + } + - pattern: "$VAR" + severity: "WARNING" diff --git a/PR_6_scala/scala/inject/rule-SpotbugsPathTraversal.scala b/PR_6_scala/scala/inject/rule-SpotbugsPathTraversal.scala new file mode 100644 index 0000000..7b0148f --- /dev/null +++ b/PR_6_scala/scala/inject/rule-SpotbugsPathTraversal.scala @@ -0,0 +1,107 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package inject + +import javax.servlet.ServletException +import javax.servlet.http.HttpServlet +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.FileReader +import java.io.FileWriter +import java.io.IOException +import java.io.RandomAccessFile +import java.net.URI +import java.net.URISyntaxException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths + + +class SpotbugsPathTraversal extends HttpServlet { // DETECTS: PT_ABSOLUTE_PATH_TRAVERSAL + + // {fact rule=path-traversal@v1.0 defects=1} + @Override + @throws[ServletException] + @throws[IOException] + override protected def doGet(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + val input = req.getParameter("input") + new File(input + "/abs/path") // BAD, DETECTS: PT_RELATIVE_PATH_TRAVERSAL + } + // {/fact} + + + // {fact rule=path-traversal@v1.0 defects=1} + @throws[ServletException] + @throws[IOException] + protected def danger2(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + val input1 = req.getParameter("input1") + new File(input1) // BAD + } + // {/fact} + + @throws[ServletException] + @throws[IOException] + @throws[URISyntaxException] + protected def danger3(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + // {fact rule=path-traversal@v1.0 defects=1} + val input = req.getParameter("test") + new File(input) + new File("test/" + input, "misc.jpg") + new RandomAccessFile(input, "r") // BAD, DETECTS: PT_ABSOLUTE_PATH_TRAVERSAL + + new File(new URI(input)) + new FileReader(input) + new FileInputStream(input) + // false positive test + new RandomAccessFile("safe", input) + new FileWriter("safe".toUpperCase) + new File(new URI("safe")) + File.createTempFile(input, "safe") + File.createTempFile("safe", input) + // {/fact} + } + + // nio path traversal + + // {fact rule=path-traversal@v1.0 defects=1} + def loadFile(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + val path = req.getParameter("test") + Paths.get(path) + Paths.get(path, "foo") + Paths.get(path, "foo", "bar") + Paths.get("foo", path) + Paths.get("foo", "bar", path) + Paths.get("foo") + Paths.get("foo", "bar") + Paths.get("foo", "bar", "allsafe") + } + // {/fact} + + + // {fact rule=path-traversal@v1.0 defects=1} + @throws[IOException] + def tempDir(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + val input = req.getParameter("test") + val p = Paths.get("/") + Files.createTempFile(p, input, "") + Files.createTempFile(p, "", input) + Files.createTempFile(input, "") + Files.createTempFile("", input) + Files.createTempDirectory(p, input) + Files.createTempDirectory(input) + } + // {/fact} + + // {fact rule=path-traversal@v1.0 defects=1} + @throws[IOException] + def writer(req: HttpServletRequest, resp: HttpServletResponse): Unit = { + val input = req.getParameter("test") + new FileWriter(input) + new FileWriter(input, true) + new FileOutputStream(input) + new FileOutputStream(input, true) + } + // {/fact} +} diff --git a/PR_6_scala/scala/inject/rule-SpotbugsPathTraversalAbsolute.yml b/PR_6_scala/scala/inject/rule-SpotbugsPathTraversalAbsolute.yml new file mode 100644 index 0000000..d2f9582 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-SpotbugsPathTraversalAbsolute.yml @@ -0,0 +1,61 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-SpotbugsPathTraversalAbsolute" + languages: + - "scala" + message: | + "The software uses an HTTP request parameter to construct a pathname that should be within a + restricted directory, but it does not properly neutralize absolute path sequences such as + "/abs/path" that can resolve to a location that is outside of that directory. See + http://cwe.mitre.org/data/definitions/36.html for more information." + metadata: + owasp: + - "A5:2017-Broken Access Control" + - "A01:2021-Broken Access Control" + category: "security" + cwe: "CWE-22" + shortDescription: "Improper limitation of a pathname to a restricted directory + ('Path Traversal')" + technology: + - "scala" + security-severity: "MEDIUM" + mode: "taint" + pattern-sanitizers: + - pattern: "org.apache.commons.io.FilenameUtils.getName(...)" + pattern-sinks: + - patterns: + - pattern-inside: | + $U = new java.net.URI($VAR) + - pattern-either: + - pattern-inside: |- + new java.io.File($U) + - pattern-inside: |- + java.nio.file.Paths.get($U) + - pattern: "$VAR" + - patterns: + - pattern-inside: |- + new java.io.RandomAccessFile($INPUT,...) + - pattern: "$INPUT" + - pattern: "new java.io.FileReader(...)" + - pattern: "new javax.activation.FileDataSource(...)" + - pattern: "new java.io.FileInputStream(...)" + - pattern: "new java.io.File(...)" + - pattern: "java.nio.file.Paths.get(...)" + - pattern: "java.io.File.createTempFile(...)" + - pattern: "java.io.File.createTempDirectory(...)" + - pattern: "java.nio.file.Files.createTempFile(...)" + - pattern: "java.nio.file.Files.createTempDirectory(...)" + - patterns: + - pattern-inside: |- + new java.io.FileWriter($PATH, ...) + - pattern: "$PATH" + - patterns: + - pattern-inside: |- + new java.io.FileOutputStream($PATH, ...) + - pattern: "$PATH" + pattern-sources: + - pattern: "($REQ: HttpServletRequest ).getParameter(...)" + severity: "WARNING" diff --git a/PR_6_scala/scala/inject/rule-SpotbugsPathTraversalRelative.yml b/PR_6_scala/scala/inject/rule-SpotbugsPathTraversalRelative.yml new file mode 100644 index 0000000..2653ec5 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-SpotbugsPathTraversalRelative.yml @@ -0,0 +1,67 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-SpotbugsPathTraversalRelative" + languages: + - "scala" + message: | + "The software uses an HTTP request parameter to construct a pathname that should be within a + restricted directory, but it does not properly neutralize sequences such as ".." that can + resolve to a location that is outside of that directory. See + http://cwe.mitre.org/data/definitions/23.html for more information." + metadata: + owasp: + - "A5:2017-Broken Access Control" + - "A01:2021-Broken Access Control" + category: "security" + cwe: "CWE-22" + shortDescription: "Improper limitation of a pathname to a restricted directory + ('Path Traversal')" + technology: + - "scala" + security-severity: "MEDIUM" + mode: "taint" + pattern-sanitizers: + - pattern: "org.apache.commons.io.FilenameUtils.getName(...)" + pattern-sinks: + - patterns: + - pattern-inside: | + $U = new java.net.URI($VAR) + - pattern-either: + - pattern-inside: |- + new java.io.File($U) + - pattern-inside: |- + java.nio.file.Paths.get($U) + - pattern: "$VAR" + - patterns: + - pattern-inside: |- + new java.io.RandomAccessFile($INPUT,...) + - pattern: "$INPUT" + - pattern: "new java.io.FileReader(...)" + - pattern: "new javax.activation.FileDataSource(...)" + - pattern: "new java.io.FileInputStream(...)" + - pattern: "new java.io.File(...)" + - pattern: "java.nio.file.Paths.get(...)" + - pattern: "java.io.File.createTempFile(...)" + - pattern: "java.io.File.createTempDirectory(...)" + - pattern: "java.nio.file.Files.createTempFile(...)" + - pattern: "java.nio.file.Files.createTempDirectory(...)" + - patterns: + - pattern-inside: |- + new java.io.FileWriter($PATH, ...) + - pattern: "$PATH" + - patterns: + - pattern-inside: |- + new java.io.FileOutputStream($PATH, ...) + - pattern: "$PATH" + pattern-sources: + - patterns: + - pattern-inside: | + $P = ($REQ: HttpServletRequest ).getParameter(...); + ... + - pattern-either: + - pattern: "$P + ..." + - pattern: "... + $P" + severity: "WARNING" diff --git a/PR_6_scala/scala/inject/rule-SqlInjection.scala b/PR_6_scala/scala/inject/rule-SqlInjection.scala new file mode 100644 index 0000000..1c8ca14 --- /dev/null +++ b/PR_6_scala/scala/inject/rule-SqlInjection.scala @@ -0,0 +1,130 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package inject + +import org.hibernate.Session +import javax.jdo.Extent +import javax.jdo.JDOHelper +import javax.jdo.PersistenceManager +import javax.jdo.PersistenceManagerFactory +import javax.persistence.criteria.CriteriaBuilder +import javax.persistence.criteria.CriteriaQuery +import java.util +import org.springframework.jdbc.core.JdbcTemplate + + +object SqlInjection { + private val CLIENT_FIELDS = "client_id, client_secret, resource_ids, scope, " + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, " + "refresh_token_validity, additional_information, autoapprove" + private val DEFAULT_INSERT_STATEMENT = "insert into oauth_client_details (" + CLIENT_FIELDS + ")" + "values (?,?,?,?,?,?,?,?,?,?,?)" + private val pmfInstance = JDOHelper.getPersistenceManagerFactory("transactions-optional") + + def getPM = pmfInstance.getPersistenceManager +} + +class SqlInjection { + private val jdbcTemplate: org.springframework.jdbc.core.JdbcTemplate = null + + class UserEntity { + private var id = null + private var test = null + + def getTest = test + + def setTest(test: Nothing): Unit = { + this.test = test + } + + def getId = id + + def setId(id: Nothing): Unit = { + this.id = id + } + } + + def testJdoQueries(input: String): Unit = { + val pm: javax.jdo.PersistenceManager = SqlInjection.getPM + // {fact rule=sql-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-SqlInjection + pm.newQuery("select * from Users where name = " + input) + // {/fact} + + + // {fact rule=sql-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-SqlInjection + pm.newQuery("sql", "select * from Products where name = " + input) + // {/fact} + + + + // Test for false positive + pm.newQuery("select * from Config") + val query = "select * from Config" + pm.newQuery(query) + pm.newQuery("sql", query) + } + + def testJdoQueriesAdditionalMethodSig(input: String): Unit = { + val pm: javax.jdo.PersistenceManager = SqlInjection.getPM + pm.newQuery(classOf[SqlInjection#UserEntity], "id == 1") + // {fact rule=sql-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-SqlInjection + pm.newQuery(classOf[SqlInjection#UserEntity], "id == " + input) + // {/fact} + + + + // {fact rule=sql-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-SqlInjection + pm.newQuery(null.asInstanceOf[String], "id == " + input) + // {/fact} + + + pm.newQuery(null.asInstanceOf[String], "id == 1") + } + + def testHibernate(input: String): Unit = { + val session: Session = null + val cb = session.getCriteriaBuilder + val query:CriteriaQuery[Object] = null + // should not be reported + session.createQuery(query) + + + // {fact rule=sql-injection@v1.0 defects=1} + // should be reported + // ruleid: scala_inject_rule-SqlInjection + session.createQuery(input) + // {/fact} + + + + val cq = cb.createQuery(classOf[String]) + } + + def good(clientDetails: Nothing): Unit = { + val statementUsingConstants = "insert into oauth_client_details (" + SqlInjection.CLIENT_FIELDS + ")" + "values (?,?,?,?,?,?,?,?,?,?,?)" + jdbcTemplate.update(statementUsingConstants, clientDetails) + } + + def good2(clientDetails: Nothing): Unit = { + + // {fact rule=sql-injection@v1.0 defects=1} + // ruleid: scala_inject_rule-SqlInjection + jdbcTemplate.update(SqlInjection.DEFAULT_INSERT_STATEMENT, clientDetails) + // {/fact} + + + } + // {fact rule=sql-injection@v1.0 defects=1} + def bad(clientDetails: Nothing): Unit = { + val stmtUsingFuncParam = "test" + clientDetails + "test" + jdbcTemplate.update(stmtUsingFuncParam, clientDetails) + } + // {/fact} + + // {fact rule=sql-injection@v1.0 defects=1} + def badInline(clientDetails: Nothing): Unit = { + // ruleid: scala_inject_rule-SqlInjection + jdbcTemplate.update("test" + clientDetails + "test", clientDetails) + } + // {/fact} +} diff --git a/PR_6_scala/scala/inject/rule-SqlInjection.yml b/PR_6_scala/scala/inject/rule-SqlInjection.yml new file mode 100644 index 0000000..d4829ee --- /dev/null +++ b/PR_6_scala/scala/inject/rule-SqlInjection.yml @@ -0,0 +1,256 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_inject_rule-SqlInjection" + languages: + - "scala" + message: | + The input values included in SQL queries need to be passed in safely. Bind + variables in prepared statements can be used to easily mitigate the risk of + SQL injection. + metadata: + category: "security" + cwe: "CWE-89" + owasp: "A1:2017-Injection" + shortDescription: "Improper Neutralization of Special Elements used in an SQL + Command ('SQL Injection')" + security-severity: "CRITICAL" + patterns: + - pattern-not-inside: | + $ARG = ... + ... + - pattern-not-inside: | + object $CLAZZ { + ... + $ARG = ... + ... + } + - pattern-not-inside: | + class $CLAZZ { + ... + $ARG = ... + ... + } + - pattern-either: + - patterns: + - pattern: "($PM:javax.jdo.PersistenceManager).newQuery(<...$ARG...>)" + - pattern-not: "($PM:javax.jdo.PersistenceManager).newQuery(\"...\")" + - patterns: + - pattern: "($PM:javax.jdo.PersistenceManager).newQuery(..., <...$ARG...>)" + - pattern-not: "($PM:javax.jdo.PersistenceManager).newQuery(..., \"...\")" + - patterns: + - pattern: "($Q: javax.jdo.Query).setFilter(<...$ARG...>)" + - pattern-not: "($Q: javax.jdo.Query).setFilter(\"...\")" + - patterns: + - pattern: "($Q: javax.jdo.Query).setGrouping(<...$ARG...>)" + - pattern-not: "($Q: javax.jdo.Query).setGrouping(\"...\")" + - patterns: + - pattern: "($Q: javax.jdo.Query).setGrouping(<...$ARG...>)" + - pattern-not: "($Q: javax.jdo.Query).setGrouping(\"...\")" + - patterns: + - pattern: "($H: org.hibernate.criterion.Restrictions).sqlRestriction(<...$ARG...>, + ...)" + - pattern-not: "($H: org.hibernate.criterion.Restrictions).sqlRestriction(\"...\", + ...)" + - patterns: + - pattern: "($S: org.hibernate.Session).createQuery(<...$ARG...>, ...)" + - pattern-not: "($S: org.hibernate.Session).createQuery(\"...\", ...)" + - patterns: + - pattern: "($S: org.hibernate.Session).createSQLQuery(<...$ARG...>, ...)" + - pattern-not: "($S: org.hibernate.Session).createSQLQuery(\"...\", ...)" + - patterns: + - pattern: "($S: java.sql.Statement).executeQuery(<...$ARG...>, ...)" + - pattern-not: "($S: java.sql.Statement).createSQLQuery(\"...\", ...)" + - patterns: + - pattern: "($S: java.sql.Statement).execute(<...$ARG...>, ...)" + - pattern-not: "($S: java.sql.Statement).execute(\"...\", ...)" + - patterns: + - pattern: "($S: java.sql.Statement).executeUpdate(<...$ARG...>, ...)" + - pattern-not: "($S: java.sql.Statement).executeUpdate(\"...\", ...)" + - patterns: + - pattern: "($S: java.sql.Statement).executeLargeUpdate(<...$ARG...>, ...)" + - pattern-not: "($S: java.sql.Statement).executeLargeUpdate(\"...\", ...)" + - patterns: + - pattern: "($S: java.sql.Statement).addBatch(<...$ARG...>, ...)" + - pattern-not: "($S: java.sql.Statement).addBatch(\"...\", ...)" + - patterns: + - pattern: "($S: java.sql.PreparedStatement).executeQuery(<...$ARG...>, ...)" + - pattern-not: "($S: java.sql.PreparedStatement).executeQuery(\"...\", ...)" + - patterns: + - pattern: "($S: java.sql.PreparedStatement).execute(<...$ARG...>, ...)" + - pattern-not: "($S: java.sql.PreparedStatement).execute(\"...\", ...)" + - patterns: + - pattern: "($S: java.sql.PreparedStatement).executeUpdate(<...$ARG...>, ...)" + - pattern-not: "($S: java.sql.PreparedStatement).executeUpdate(\"...\", ...)" + - patterns: + - pattern: "($S: java.sql.PreparedStatement).executeLargeUpdate(<...$ARG...>, + ...)" + - pattern-not: "($S: java.sql.PreparedStatement).executeLargeUpdate(\"...\", + ...)" + - patterns: + - pattern: "($S: java.sql.PreparedStatement).addBatch(<...$ARG...>, ...)" + - pattern-not: "($S: java.sql.PreparedStatement).addBatch(\"...\", ...)" + - patterns: + - pattern: "($S: java.sql.Connection).prepareCall(<...$ARG...>, ...)" + - pattern-not: "($S: java.sql.Connection).prepareCall(\"...\", ...)" + - patterns: + - pattern: "($S: java.sql.Connection).prepareStatement(<...$ARG...>, ...)" + - pattern-not: "($S: java.sql.Connection).prepareStatement(\"...\", ...)" + - patterns: + - pattern: "($S: java.sql.Connection).nativeSQL(<...$ARG...>, ...)" + - pattern-not: "($S: java.sql.Connection).nativeSQL(\"...\", ...)" + - patterns: + - pattern: "new org.springframework.jdbc.core.PreparedStatementCreatorFactory(<...$ARG...>, + ...)" + - pattern-not: "new org.springframework.jdbc.core.PreparedStatementCreatorFactory(\"...\", + ...)" + - patterns: + - pattern: "(org.springframework.jdbc.core.PreparedStatementCreatorFactory $F).newPreparedStatementCreator(<...$ARG...>, + ...)" + - pattern-not: "(org.springframework.jdbc.core.PreparedStatementCreatorFactory + $F).newPreparedStatementCreator(\"...\", ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcOperations).batchUpdate(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcOperations).batchUpdate(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcOperations).execute(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcOperations).execute(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcOperations).query(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcOperations).query(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcOperations).queryForList(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcOperations).queryForList(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcOperations).queryForMap(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcOperations).queryForMap(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcOperations).queryForObject(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcOperations).queryForObject(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcOperations).queryForObject(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcOperations).queryForObject(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcOperations).queryForRowSet(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcOperations).queryForRowSet(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcOperations).queryForInt(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcOperations).queryForInt(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcOperations).queryForLong(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcOperations).queryForLong(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcOperations).update(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcOperations).update(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcTemplate).batchUpdate(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcTemplate).batchUpdate(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcTemplate).execute(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcTemplate).execute(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcTemplate).query(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcTemplate).query(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcTemplate).queryForList(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcTemplate).queryForList(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcTemplate).queryForMap(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcTemplate).queryForMap(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcTemplate).queryForObject(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcTemplate).queryForObject(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcTemplate).queryForRowSet(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcTemplate).queryForRowSet(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcTemplate).queryForInt(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcTemplate).queryForInt(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcTemplate).queryForLong(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcTemplate).queryForLong(\"...\", + ...)" + - patterns: + - pattern: "($O: org.springframework.jdbc.core.JdbcTemplate).update(<...$ARG...>, + ...)" + - pattern-not: "($O: org.springframework.jdbc.core.JdbcTemplate).update(\"...\", + ...)" + - patterns: + - pattern: "($O: io.vertx.sqlclient.SqlClient).query(<...$ARG...>, ...)" + - pattern-not: "($O: io.vertx.sqlclient.SqlClient).query(\"...\", ...)" + - patterns: + - pattern: "($O: io.vertx.sqlclient.SqlClient).preparedQuery(<...$ARG...>, ...)" + - pattern-not: "($O: io.vertx.sqlclient.SqlClient).preparedQuery(\"...\", ...)" + - patterns: + - pattern: "($O: io.vertx.sqlclient.SqlConnection).prepare(<...$ARG...>, ...)" + - pattern-not: "($O: io.vertx.sqlclient.SqlConnection).prepare(\"...\", ...)" + - patterns: + - pattern: "($O: org.apache.turbine.om.peer.BasePeer).executeQuery(<...$ARG...>, + ...)" + - pattern-not: "($O: org.apache.turbine.om.peer.BasePeer).executeQuery(\"...\", + ...)" + - patterns: + - pattern: "($O: org.apache.torque.util.BasePeer).executeQuery(<...$ARG...>, + ...)" + - pattern-not: "($O: org.apache.torque.util.BasePeer).executeQuery(\"...\", + ...)" + - patterns: + - pattern: "($O: javax.persistence.EntityManager).createQuery(<...$ARG...>, + ...)" + - pattern-not: "($O: javax.persistence.EntityManager).createQuery(\"...\", ...)" + - patterns: + - pattern: "($O: javax.persistence.EntityManager).createNativeQuery(<...$ARG...>, + ...)" + - pattern-not: "($O: javax.persistence.EntityManager).createNativeQuery(\"...\", + ...)" + - patterns: + - pattern: "anorm.SQL(<...$ARG...>)" + - pattern-not: "anorm.SQL(\"...\")" + - patterns: + - pattern-inside: | + import anorm._ + ... + - pattern: "SQL(<...$ARG...>)" + - pattern-not: "SQL(\"...\")" + severity: "ERROR" diff --git a/PR_6_scala/scala/ldap/rule-AnonymousLDAP.scala b/PR_6_scala/scala/ldap/rule-AnonymousLDAP.scala new file mode 100644 index 0000000..1d0c078 --- /dev/null +++ b/PR_6_scala/scala/ldap/rule-AnonymousLDAP.scala @@ -0,0 +1,39 @@ +// License: LGPL-3.0 License (c) find-sec-bugs +package ldap + +import java.util._ +import javax.naming.Context +import javax.naming.directory.DirContext +import javax.naming.directory.InitialDirContext + +object AnonymousLDAP { + private val ldapURI = "ldaps://ldap.server.com/dc=ldap,dc=server,dc=com" + private val contextFactory = "com.sun.jndi.ldap.LdapCtxFactory" + + @throws[Exception] + private def ldapContext(env: Hashtable[String, String]) = { + env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory) + env.put(Context.PROVIDER_URL, ldapURI) + // {fact rule=improper-input-validation@v1.0 defects=1} + // ruleid: scala_ldap_rule-AnonymousLDAP + env.put(Context.SECURITY_AUTHENTICATION, "none") + // {/fact} + val ctx = new InitialDirContext(env) + ctx + } + + @throws[Exception] + def testBind(dn: String, password: String): Boolean = { + val env = new Hashtable[String, String] + env.put(Context.SECURITY_AUTHENTICATION, "simple") //false positive + + env.put(Context.SECURITY_PRINCIPAL, dn) + env.put(Context.SECURITY_CREDENTIALS, password) + try ldapContext(env) + catch { + case e: javax.naming.AuthenticationException => + return false + } + true + } +} diff --git a/PR_6_scala/scala/ldap/rule-AnonymousLDAP.yml b/PR_6_scala/scala/ldap/rule-AnonymousLDAP.yml new file mode 100644 index 0000000..c8e5b49 --- /dev/null +++ b/PR_6_scala/scala/ldap/rule-AnonymousLDAP.yml @@ -0,0 +1,23 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_ldap_rule-AnonymousLDAP" + languages: + - "scala" + message: | + Without proper access control, executing an LDAP statement that contains a + user-controlled value can allow an attacker to abuse poorly configured LDAP + context + metadata: + category: "security" + cwe: "CWE-20" + shortDescription: "Improper Input Validation" + security-severity: "MEDIUM" + patterns: + - pattern-inside: | + import javax.naming.Context; + ... + - pattern: "$ENV.put(Context.SECURITY_AUTHENTICATION, \"none\");" + severity: "WARNING" diff --git a/PR_6_scala/scala/ldap/rule-EntryPoisoning.scala b/PR_6_scala/scala/ldap/rule-EntryPoisoning.scala new file mode 100644 index 0000000..5e019d4 --- /dev/null +++ b/PR_6_scala/scala/ldap/rule-EntryPoisoning.scala @@ -0,0 +1,38 @@ +// License: MIT (c) GitLab Inc. +package ldap + +import javax.naming.directory.SearchControls + +class EntryPoisoning { + private val scope = 0 + private val countLimit = 0 + private val timeLimit = 0 + private val attributes = null + private val deref = false + + def unsafe1(): Unit = { + // {fact rule=improper-input-validation@v1.0 defects=1} + // ruleid: scala_ldap_rule-EntryPoisoning + new SearchControls(scope, countLimit, timeLimit, attributes, true, //!! It will flag line 14 ... the beginning of the call + deref) + // {/fact} + } + + def unsafe2(): Unit = { + val ctrl = new SearchControls() + ctrl.setReturningObjFlag(true) //!! + + } + + // {fact rule=improper-input-validation@v1.0 defects=0} + def safe1(): Unit = { + new SearchControls(scope, countLimit, timeLimit, attributes, false, //OK + deref) + } + // {/fact} + + def safe2(): Unit = { + val ctrl = new SearchControls() + ctrl.setReturningObjFlag(false) + } +} diff --git a/PR_6_scala/scala/ldap/rule-EntryPoisoning.yml b/PR_6_scala/scala/ldap/rule-EntryPoisoning.yml new file mode 100644 index 0000000..f57d396 --- /dev/null +++ b/PR_6_scala/scala/ldap/rule-EntryPoisoning.yml @@ -0,0 +1,21 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_ldap_rule-EntryPoisoning" + languages: + - "scala" + message: | + Without proper access control, executing an LDAP statement that contains a + user-controlled value can allow an attacker to abuse poorly configured LDAP + context + metadata: + category: "security" + cwe: "CWE-20" + shortDescription: "Improper Input Validation" + security-severity: "CRITICAL" + patterns: + - pattern: "new javax.naming.directory.SearchControls($SCOPE, $CLIMIT, $TLIMIT, + $ATTR, true, $DEREF)" + severity: "ERROR" diff --git a/PR_6_scala/scala/password/rule-ConstantDBPassword.yml b/PR_6_scala/scala/password/rule-ConstantDBPassword.yml new file mode 100644 index 0000000..03177a9 --- /dev/null +++ b/PR_6_scala/scala/password/rule-ConstantDBPassword.yml @@ -0,0 +1,21 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_password_rule-ConstantDBPassword" + languages: + - "scala" + message: | + This code creates a database connect using a hardcoded, constant password. Anyone with access + to either the source code or the compiled code can easily learn the password. + metadata: + category: "security" + cwe: "CWE-259" + shortDescription: "Use of Hard-coded Password" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern: "java.sql.DriverManager.getConnection($URI, $USR, \"...\");" + severity: "WARNING" diff --git a/PR_6_scala/scala/password/rule-EmptyDBPassword.yml b/PR_6_scala/scala/password/rule-EmptyDBPassword.yml new file mode 100644 index 0000000..6852f15 --- /dev/null +++ b/PR_6_scala/scala/password/rule-EmptyDBPassword.yml @@ -0,0 +1,21 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_password_rule-EmptyDBPassword" + languages: + - "scala" + message: | + This code creates a database connect using a blank or empty password. This indicates that the + database is not protected by a password. + metadata: + category: "security" + cwe: "CWE-259" + shortDescription: "Use of Hard-coded Password" + technology: + - "scala" + security-severity: "MEDIUM" + patterns: + - pattern: "java.sql.DriverManager.getConnection($URI, $USR, \"\");" + severity: "WARNING" diff --git a/PR_6_scala/scala/password/rule-HardcodeKey.yml b/PR_6_scala/scala/password/rule-HardcodeKey.yml new file mode 100644 index 0000000..e534df4 --- /dev/null +++ b/PR_6_scala/scala/password/rule-HardcodeKey.yml @@ -0,0 +1,66 @@ +# yamllint disable +# License: MIT (c) GitLab Inc. +# yamllint enable +--- +rules: +- id: "scala_password_rule-HardcodeKey" + languages: + - "scala" + message: | + Cryptographic keys should not be kept in the source code. The source code can be widely shared + in an enterprise environment, and is certainly shared in open source. To be managed safely, + passwords and secret keys should be stored in separate configuration files or keystores. + metadata: + category: "security" + cwe: "CWE-321" + shortDescription: "Use of Hard-coded Cryptographic Key" + technology: + - "scala" + security-severity: "CRITICAL" + pattern-either: + - patterns: + - pattern-not-inside: | + def $FUNC(...,$KEY_BYTES: Array[Byte], ...): $TYPE = { + ... + } + - pattern-inside: | + $KEY = Array[Byte](...) + ... + - pattern-either: + - pattern: "new DESKeySpec($KEY)" + - pattern: "new DESedeKeySpec($KEY)" + - pattern: "new KerberosKey(..., $KEY,...)" + - pattern: "new SecretKeySpec($KEY, ...)" + - pattern: "new X509EncodedKeySpec($KEY)" + - pattern: "new PKCS8EncodedKeySpec($KEY)" + - pattern: "new KeyRep(..., $KEY)" + - pattern: "new KerberosTicket(...,$KEY,...)" + - patterns: + - pattern-inside: | + $KEY = ... .getBytes(...) + ... + - pattern-either: + - pattern: "new DESKeySpec($KEY)" + - pattern: "new DESedeKeySpec($KEY)" + - pattern: "new KerberosKey(..., $KEY,...)" + - pattern: "new SecretKeySpec($KEY, ...)" + - pattern: "new X509EncodedKeySpec($KEY)" + - pattern: "new PKCS8EncodedKeySpec($KEY)" + - pattern: "new KeyRep(..., $KEY)" + - pattern: "new KerberosTicket(...,$KEY,...)" + - patterns: + - pattern-not-inside: | + def $FUNC(..., $PRIVATE_KEY: BigInteger, ...): $TYPE = { + ... + } + - pattern-either: + - pattern: "new DSAPrivateKeySpec($PRIVATE_KEY: BigInteger, ...)" + - pattern: "new DSAPublicKeySpec($PRIVATE_KEY: BigInteger, ...)" + - pattern: "new DHPrivateKeySpec($PRIVATE_KEY: BigInteger, ...)" + - pattern: "new DHPublicKeySpec($PRIVATE_KEY: BigInteger, ...)" + - pattern: "new ECPrivateKeySpec($PRIVATE_KEY: BigInteger, ...)" + - pattern: "new RSAPrivateKeySpec($PRIVATE_KEY: BigInteger, ...)" + - pattern: "new RSAMultiPrimePrivateCrtKeySpec($PRIVATE_KEY: BigInteger, ...)" + - pattern: "new RSAPrivateCrtKeySpec($PRIVATE_KEY: BigInteger, ...)" + - pattern: "new RSAPublicKeySpec($PRIVATE_KEY: BigInteger, ...)" + severity: "ERROR"