Date: Mon, 1 Mar 2021 22:06:52 +0000
Subject: [PATCH 0008/1429] Query to check sensitive cookies without the
HttpOnly flag set
---
.../CWE-1004/SensitiveCookieNotHttpOnly.java | 44 +++
.../CWE-1004/SensitiveCookieNotHttpOnly.qhelp | 27 ++
.../CWE-1004/SensitiveCookieNotHttpOnly.ql | 194 ++++++++++
.../SensitiveCookieNotHttpOnly.expected | 13 +
.../CWE-1004/SensitiveCookieNotHttpOnly.java | 57 +++
.../CWE-1004/SensitiveCookieNotHttpOnly.qlref | 1 +
.../query-tests/security/CWE-1004/options | 1 +
.../javax/ws/rs/core/Cookie.java | 187 +++++++++
.../javax/ws/rs/core/NewCookie.java | 359 ++++++++++++++++++
.../javax/servlet/http/Cookie.java | 33 ++
.../servlet/http/HttpServletResponse.java | 6 +
11 files changed, 922 insertions(+)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.java
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.qlref
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-1004/options
create mode 100644 java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java
create mode 100644 java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.java b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.java
new file mode 100644
index 00000000000..48d80707ff8
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.java
@@ -0,0 +1,44 @@
+class SensitiveCookieNotHttpOnly {
+ // GOOD - Create a sensitive cookie with the `HttpOnly` flag set.
+ public void addCookie(String jwt_token, HttpServletRequest request, HttpServletResponse response) {
+ Cookie jwtCookie =new Cookie("jwt_token", jwt_token);
+ jwtCookie.setPath("/");
+ jwtCookie.setMaxAge(3600*24*7);
+ jwtCookie.setHttpOnly(true);
+ response.addCookie(jwtCookie);
+ }
+
+ // BAD - Create a sensitive cookie without the `HttpOnly` flag set.
+ public void addCookie2(String jwt_token, String userId, HttpServletRequest request, HttpServletResponse response) {
+ Cookie jwtCookie =new Cookie("jwt_token", jwt_token);
+ jwtCookie.setPath("/");
+ jwtCookie.setMaxAge(3600*24*7);
+ response.addCookie(jwtCookie);
+ }
+
+ // GOOD - Set a sensitive cookie header with the `HttpOnly` flag set.
+ public void addCookie3(String authId, HttpServletRequest request, HttpServletResponse response) {
+ response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure");
+ }
+
+ // BAD - Set a sensitive cookie header without the `HttpOnly` flag set.
+ public void addCookie4(String authId, HttpServletRequest request, HttpServletResponse response) {
+ response.addHeader("Set-Cookie", "token=" +authId + ";Secure");
+ }
+
+ // GOOD - Set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` with the `HttpOnly` flag set through string concatenation.
+ public void addCookie5(String accessKey, HttpServletRequest request, HttpServletResponse response) {
+ response.setHeader("Set-Cookie", new NewCookie("session-access-key", accessKey, "/", null, null, 0, true) + ";HttpOnly");
+ }
+
+ // BAD - Set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` without the `HttpOnly` flag set.
+ public void addCookie6(String accessKey, HttpServletRequest request, HttpServletResponse response) {
+ response.setHeader("Set-Cookie", new NewCookie("session-access-key", accessKey, "/", null, null, 0, true).toString());
+ }
+
+ // GOOD - Set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` with the `HttpOnly` flag set through the constructor.
+ public void addCookie7(String accessKey, HttpServletRequest request, HttpServletResponse response) {
+ NewCookie accessKeyCookie = new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true);
+ response.setHeader("Set-Cookie", accessKeyCookie.toString());
+ }
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp
new file mode 100644
index 00000000000..880ed767be9
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp
@@ -0,0 +1,27 @@
+
+
+
+
+ Cross-Site Scripting (XSS) is categorized as one of the OWASP Top 10 Security Vulnerabilities. The HttpOnly flag directs compatible browsers to prevent client-side script from accessing cookies. Including the HttpOnly flag in the Set-Cookie HTTP response header for a sensitive cookie helps mitigate the risk associated with XSS where an attacker's script code attempts to read the contents of a cookie and exfiltrate information obtained.
+
+
+
+ Use the HttpOnly flag when generating a cookie containing sensitive information to help mitigate the risk of client side script accessing the protected cookie.
+
+
+
+ The following example shows two ways of generating sensitive cookies. In the 'BAD' cases, the HttpOnly flag is not set. In the 'GOOD' cases, the HttpOnly flag is set.
+
+
+
+
+
+ PortSwigger:
+ Cookie without HttpOnly flag set
+
+
+ OWASP:
+ HttpOnly
+
+
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
new file mode 100644
index 00000000000..bf4e60134c9
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -0,0 +1,194 @@
+/**
+ * @name Sensitive cookies without the HttpOnly response header set
+ * @description Sensitive cookies without 'HttpOnly' leaves session cookies vulnerable to an XSS attack.
+ * @kind path-problem
+ * @id java/sensitive-cookie-not-httponly
+ * @tags security
+ * external/cwe/cwe-1004
+ */
+
+import java
+import semmle.code.java.frameworks.Servlets
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.dataflow.TaintTracking
+import DataFlow::PathGraph
+
+/** Gets a regular expression for matching common names of sensitive cookies. */
+string getSensitiveCookieNameRegex() { result = "(?i).*(auth|session|token|key|credential).*" }
+
+/** Holds if a string is concatenated with the name of a sensitive cookie. */
+predicate isSensitiveCookieNameExpr(Expr expr) {
+ expr.(StringLiteral)
+ .getRepresentedString()
+ .toLowerCase()
+ .regexpMatch(getSensitiveCookieNameRegex()) or
+ isSensitiveCookieNameExpr(expr.(AddExpr).getAnOperand())
+}
+
+/** Holds if a string is concatenated with the `HttpOnly` flag. */
+predicate hasHttpOnlyExpr(Expr expr) {
+ expr.(StringLiteral).getRepresentedString().toLowerCase().matches("%httponly%") or
+ hasHttpOnlyExpr(expr.(AddExpr).getAnOperand())
+}
+
+/** The method call `Set-Cookie` of `addHeader` or `setHeader`. */
+class SetCookieMethodAccess extends MethodAccess {
+ SetCookieMethodAccess() {
+ (
+ this.getMethod() instanceof ResponseAddHeaderMethod or
+ this.getMethod() instanceof ResponseSetHeaderMethod
+ ) and
+ this.getArgument(0).(StringLiteral).getRepresentedString().toLowerCase() = "set-cookie"
+ }
+}
+
+/** Sensitive cookie name used in a `Cookie` constructor or a `Set-Cookie` call. */
+class SensitiveCookieNameExpr extends Expr {
+ SensitiveCookieNameExpr() {
+ isSensitiveCookieNameExpr(this) and
+ (
+ exists(
+ ClassInstanceExpr cie // new Cookie("jwt_token", token)
+ |
+ (
+ cie.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") or
+ cie.getConstructor()
+ .getDeclaringType()
+ .getASupertype*()
+ .hasQualifiedName("javax.ws.rs.core", "Cookie") or
+ cie.getConstructor()
+ .getDeclaringType()
+ .getASupertype*()
+ .hasQualifiedName("jakarta.ws.rs.core", "Cookie")
+ ) and
+ DataFlow::localExprFlow(this, cie.getArgument(0))
+ )
+ or
+ exists(
+ SetCookieMethodAccess ma // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
+ |
+ DataFlow::localExprFlow(this, ma.getArgument(1))
+ )
+ )
+ }
+}
+
+/** Sink of adding a cookie to the HTTP response. */
+class CookieResponseSink extends DataFlow::ExprNode {
+ CookieResponseSink() {
+ exists(MethodAccess ma |
+ (
+ ma.getMethod() instanceof ResponseAddCookieMethod or
+ ma instanceof SetCookieMethodAccess
+ ) and
+ ma.getAnArgument() = this.getExpr()
+ )
+ }
+}
+
+/** Holds if the `node` is a method call of `setHttpOnly(true)` on a cookie. */
+predicate setHttpOnlyMethodAccess(DataFlow::Node node) {
+ exists(
+ MethodAccess addCookie, Variable cookie, MethodAccess m // jwtCookie.setHttpOnly(true)
+ |
+ addCookie.getMethod() instanceof ResponseAddCookieMethod and
+ addCookie.getArgument(0) = cookie.getAnAccess() and
+ m.getMethod().getName() = "setHttpOnly" and
+ m.getArgument(0).(BooleanLiteral).getBooleanValue() = true and
+ m.getQualifier() = cookie.getAnAccess() and
+ node.asExpr() = cookie.getAnAccess()
+ )
+}
+
+/** Holds if the `node` is a method call of `Set-Cookie` header with the `HttpOnly` flag whose cookie name is sensitive. */
+predicate setHttpOnlyInSetCookie(DataFlow::Node node) {
+ exists(SetCookieMethodAccess sa |
+ hasHttpOnlyExpr(node.asExpr()) and
+ DataFlow::localExprFlow(node.asExpr(), sa.getArgument(1))
+ )
+}
+
+/** Holds if the `node` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
+predicate setHttpOnlyInNewCookie(DataFlow::Node node) {
+ exists(ClassInstanceExpr cie |
+ cie.getConstructor().getDeclaringType().hasName("NewCookie") and
+ DataFlow::localExprFlow(node.asExpr(), cie.getArgument(0)) and
+ (
+ cie.getNumArgument() = 6 and cie.getArgument(5).(BooleanLiteral).getBooleanValue() = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
+ or
+ cie.getNumArgument() = 8 and
+ cie.getArgument(6).getType() instanceof BooleanType and
+ cie.getArgument(7).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
+ or
+ cie.getNumArgument() = 10 and cie.getArgument(9).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
+ )
+ )
+}
+
+/**
+ * Holds if the node is a test method indicated by:
+ * a) in a test directory such as `src/test/java`
+ * b) in a test package whose name has the word `test`
+ * c) in a test class whose name has the word `test`
+ * d) in a test class implementing a test framework such as JUnit or TestNG
+ */
+predicate isTestMethod(DataFlow::Node node) {
+ exists(MethodAccess ma, Method m |
+ node.asExpr() = ma.getAnArgument() and
+ m = ma.getEnclosingCallable() and
+ (
+ m.getDeclaringType().getName().toLowerCase().matches("%test%") or // Simple check to exclude test classes to reduce FPs
+ m.getDeclaringType().getPackage().getName().toLowerCase().matches("%test%") or // Simple check to exclude classes in test packages to reduce FPs
+ exists(m.getLocation().getFile().getAbsolutePath().indexOf("/src/test/java")) or // Match test directory structure of build tools like maven
+ m instanceof TestMethod // Test method of a test case implementing a test framework such as JUnit or TestNG
+ )
+ )
+}
+
+/** A taint configuration tracking flow from a sensitive cookie without HttpOnly flag set to its HTTP response. */
+class MissingHttpOnlyConfiguration extends TaintTracking::Configuration {
+ MissingHttpOnlyConfiguration() { this = "MissingHttpOnlyConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof SensitiveCookieNameExpr
+ }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof CookieResponseSink }
+
+ override predicate isSanitizer(DataFlow::Node node) {
+ // cookie.setHttpOnly(true)
+ setHttpOnlyMethodAccess(node)
+ or
+ // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
+ setHttpOnlyInSetCookie(node)
+ or
+ // new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)
+ setHttpOnlyInNewCookie(node)
+ or
+ // Test class or method
+ isTestMethod(node)
+ }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ exists(
+ ClassInstanceExpr cie // `NewCookie` constructor
+ |
+ cie.getAnArgument() = pred.asExpr() and
+ cie = succ.asExpr() and
+ cie.getConstructor().getDeclaringType().hasName("NewCookie")
+ )
+ or
+ exists(
+ MethodAccess ma // `toString` call on a cookie object
+ |
+ ma.getQualifier() = pred.asExpr() and
+ ma.getMethod().hasName("toString") and
+ ma = succ.asExpr()
+ )
+ }
+}
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, MissingHttpOnlyConfiguration c
+where c.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "$@ doesn't have the HttpOnly flag set.", source.getNode(),
+ "This sensitive cookie"
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
new file mode 100644
index 00000000000..84d6be3863a
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
@@ -0,0 +1,13 @@
+edges
+| SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie |
+| SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) |
+nodes
+| SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | semmle.label | "jwt_token" : String |
+| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | semmle.label | jwtCookie |
+| SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | semmle.label | ... + ... |
+| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | semmle.label | toString(...) |
+| SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | semmle.label | "session-access-key" : String |
+#select
+| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" | This sensitive cookie |
+| SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | This sensitive cookie |
+| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" | This sensitive cookie |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
new file mode 100644
index 00000000000..5e4f349f7c8
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
@@ -0,0 +1,57 @@
+import java.io.IOException;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.ServletException;
+
+import javax.ws.rs.core.NewCookie;
+
+class SensitiveCookieNotHttpOnly {
+ // GOOD - Tests adding a sensitive cookie with the `HttpOnly` flag set.
+ public void addCookie(String jwt_token, HttpServletRequest request, HttpServletResponse response) {
+ Cookie jwtCookie =new Cookie("jwt_token", jwt_token);
+ jwtCookie.setPath("/");
+ jwtCookie.setMaxAge(3600*24*7);
+ jwtCookie.setHttpOnly(true);
+ response.addCookie(jwtCookie);
+ }
+
+ // BAD - Tests adding a sensitive cookie without the `HttpOnly` flag set.
+ public void addCookie2(String jwt_token, String userId, HttpServletRequest request, HttpServletResponse response) {
+ Cookie jwtCookie =new Cookie("jwt_token", jwt_token);
+ Cookie userIdCookie =new Cookie("user_id", userId.toString());
+ jwtCookie.setPath("/");
+ userIdCookie.setPath("/");
+ jwtCookie.setMaxAge(3600*24*7);
+ userIdCookie.setMaxAge(3600*24*7);
+ response.addCookie(jwtCookie);
+ response.addCookie(userIdCookie);
+ }
+
+ // GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set.
+ public void addCookie3(String authId, HttpServletRequest request, HttpServletResponse response) {
+ response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure");
+ }
+
+ // BAD - Tests set a sensitive cookie header without the `HttpOnly` flag set.
+ public void addCookie4(String authId, HttpServletRequest request, HttpServletResponse response) {
+ response.addHeader("Set-Cookie", "token=" +authId + ";Secure");
+ }
+
+ // GOOD - Tests set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` with the `HttpOnly` flag set through string concatenation.
+ public void addCookie5(String accessKey, HttpServletRequest request, HttpServletResponse response) {
+ response.setHeader("Set-Cookie", new NewCookie("session-access-key", accessKey, "/", null, null, 0, true) + ";HttpOnly");
+ }
+
+ // BAD - Tests set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` without the `HttpOnly` flag set.
+ public void addCookie6(String accessKey, HttpServletRequest request, HttpServletResponse response) {
+ response.setHeader("Set-Cookie", new NewCookie("session-access-key", accessKey, "/", null, null, 0, true).toString());
+ }
+
+ // GOOD - Tests set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` with the `HttpOnly` flag set through the constructor.
+ public void addCookie7(String accessKey, HttpServletRequest request, HttpServletResponse response) {
+ NewCookie accessKeyCookie = new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true);
+ response.setHeader("Set-Cookie", accessKeyCookie.toString());
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.qlref b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.qlref
new file mode 100644
index 00000000000..cc2baaf6f7b
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/options b/java/ql/test/experimental/query-tests/security/CWE-1004/options
new file mode 100644
index 00000000000..7f2b253fb20
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/options
@@ -0,0 +1 @@
+// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/jsr311-api-1.1.1
\ No newline at end of file
diff --git a/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java
new file mode 100644
index 00000000000..4e4c7585c35
--- /dev/null
+++ b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java
@@ -0,0 +1,187 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 2010-2015 Oracle and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * http://glassfish.java.net/public/CDDL+GPL_1_1.html
+ * or packager/legal/LICENSE.txt. See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at packager/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * Oracle designates this particular file as subject to the "Classpath"
+ * exception as provided by Oracle in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+package javax.ws.rs.core;
+
+/**
+ * Represents the value of a HTTP cookie, transferred in a request.
+ * RFC 2109 specifies the legal characters for name,
+ * value, path and domain. The default version of 1 corresponds to RFC 2109.
+ *
+ * @author Paul Sandoz
+ * @author Marc Hadley
+ * @see IETF RFC 2109
+ * @since 1.0
+ */
+public class Cookie {
+ /**
+ * Cookies using the default version correspond to RFC 2109.
+ */
+ public static final int DEFAULT_VERSION = 1;
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @param path the URI path for which the cookie is valid.
+ * @param domain the host domain for which the cookie is valid.
+ * @param version the version of the specification to which the cookie complies.
+ * @throws IllegalArgumentException if name is {@code null}.
+ */
+ public Cookie(final String name, final String value, final String path, final String domain, final int version)
+ throws IllegalArgumentException {
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @param path the URI path for which the cookie is valid.
+ * @param domain the host domain for which the cookie is valid.
+ * @throws IllegalArgumentException if name is {@code null}.
+ */
+ public Cookie(final String name, final String value, final String path, final String domain)
+ throws IllegalArgumentException {
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @throws IllegalArgumentException if name is {@code null}.
+ */
+ public Cookie(final String name, final String value)
+ throws IllegalArgumentException {
+ }
+
+ /**
+ * Creates a new instance of {@code Cookie} by parsing the supplied string.
+ *
+ * @param value the cookie string.
+ * @return the newly created {@code Cookie}.
+ * @throws IllegalArgumentException if the supplied string cannot be parsed
+ * or is {@code null}.
+ */
+ public static Cookie valueOf(final String value) {
+ return null;
+ }
+
+ /**
+ * Get the name of the cookie.
+ *
+ * @return the cookie name.
+ */
+ public String getName() {
+ return null;
+ }
+
+ /**
+ * Get the value of the cookie.
+ *
+ * @return the cookie value.
+ */
+ public String getValue() {
+ return null;
+ }
+
+ /**
+ * Get the version of the cookie.
+ *
+ * @return the cookie version.
+ */
+ public int getVersion() {
+ return -1;
+ }
+
+ /**
+ * Get the domain of the cookie.
+ *
+ * @return the cookie domain.
+ */
+ public String getDomain() {
+ return null;
+ }
+
+ /**
+ * Get the path of the cookie.
+ *
+ * @return the cookie path.
+ */
+ public String getPath() {
+ return null;
+ }
+
+ /**
+ * Convert the cookie to a string suitable for use as the value of the
+ * corresponding HTTP header.
+ *
+ * @return a stringified cookie.
+ */
+ @Override
+ public String toString() {
+ return null;
+ }
+
+ /**
+ * Generate a hash code by hashing all of the cookies properties.
+ *
+ * @return the cookie hash code.
+ */
+ @Override
+ public int hashCode() {
+ return -1;
+ }
+
+ /**
+ * Compare for equality.
+ *
+ * @param obj the object to compare to.
+ * @return {@code true}, if the object is a {@code Cookie} with the same
+ * value for all properties, {@code false} otherwise.
+ */
+ @SuppressWarnings({"StringEquality", "RedundantIfStatement"})
+ @Override
+ public boolean equals(final Object obj) {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java
new file mode 100644
index 00000000000..43a4899e0ca
--- /dev/null
+++ b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java
@@ -0,0 +1,359 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 2010-2015 Oracle and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * http://glassfish.java.net/public/CDDL+GPL_1_1.html
+ * or packager/legal/LICENSE.txt. See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at packager/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * Oracle designates this particular file as subject to the "Classpath"
+ * exception as provided by Oracle in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+package javax.ws.rs.core;
+
+import java.util.Date;
+
+/**
+ * Used to create a new HTTP cookie, transferred in a response.
+ *
+ * @author Paul Sandoz
+ * @author Marc Hadley
+ * @see IETF RFC 2109
+ * @since 1.0
+ */
+public class NewCookie extends Cookie {
+
+ /**
+ * Specifies that the cookie expires with the current application/browser session.
+ */
+ public static final int DEFAULT_MAX_AGE = -1;
+
+ private final String comment;
+ private final int maxAge;
+ private final Date expiry;
+ private final boolean secure;
+ private final boolean httpOnly;
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @throws IllegalArgumentException if name is {@code null}.
+ */
+ public NewCookie(String name, String value) {
+ this(name, value, null, null, DEFAULT_VERSION, null, DEFAULT_MAX_AGE, null, false, false);
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @param path the URI path for which the cookie is valid.
+ * @param domain the host domain for which the cookie is valid.
+ * @param comment the comment.
+ * @param maxAge the maximum age of the cookie in seconds.
+ * @param secure specifies whether the cookie will only be sent over a secure connection.
+ * @throws IllegalArgumentException if name is {@code null}.
+ */
+ public NewCookie(String name,
+ String value,
+ String path,
+ String domain,
+ String comment,
+ int maxAge,
+ boolean secure) {
+ this(name, value, path, domain, DEFAULT_VERSION, comment, maxAge, null, secure, false);
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @param path the URI path for which the cookie is valid.
+ * @param domain the host domain for which the cookie is valid.
+ * @param comment the comment.
+ * @param maxAge the maximum age of the cookie in seconds.
+ * @param secure specifies whether the cookie will only be sent over a secure connection.
+ * @param httpOnly if {@code true} make the cookie HTTP only, i.e. only visible as part of an HTTP request.
+ * @throws IllegalArgumentException if name is {@code null}.
+ * @since 2.0
+ */
+ public NewCookie(String name,
+ String value,
+ String path,
+ String domain,
+ String comment,
+ int maxAge,
+ boolean secure,
+ boolean httpOnly) {
+ this(name, value, path, domain, DEFAULT_VERSION, comment, maxAge, null, secure, httpOnly);
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie
+ * @param value the value of the cookie
+ * @param path the URI path for which the cookie is valid
+ * @param domain the host domain for which the cookie is valid
+ * @param version the version of the specification to which the cookie complies
+ * @param comment the comment
+ * @param maxAge the maximum age of the cookie in seconds
+ * @param secure specifies whether the cookie will only be sent over a secure connection
+ * @throws IllegalArgumentException if name is {@code null}.
+ */
+ public NewCookie(String name,
+ String value,
+ String path,
+ String domain,
+ int version,
+ String comment,
+ int maxAge,
+ boolean secure) {
+ this(name, value, path, domain, version, comment, maxAge, null, secure, false);
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie
+ * @param value the value of the cookie
+ * @param path the URI path for which the cookie is valid
+ * @param domain the host domain for which the cookie is valid
+ * @param version the version of the specification to which the cookie complies
+ * @param comment the comment
+ * @param maxAge the maximum age of the cookie in seconds
+ * @param expiry the cookie expiry date.
+ * @param secure specifies whether the cookie will only be sent over a secure connection
+ * @param httpOnly if {@code true} make the cookie HTTP only, i.e. only visible as part of an HTTP request.
+ * @throws IllegalArgumentException if name is {@code null}.
+ * @since 2.0
+ */
+ public NewCookie(String name,
+ String value,
+ String path,
+ String domain,
+ int version,
+ String comment,
+ int maxAge,
+ Date expiry,
+ boolean secure,
+ boolean httpOnly) {
+ super(name, value, path, domain, version);
+ this.comment = comment;
+ this.maxAge = maxAge;
+ this.expiry = expiry;
+ this.secure = secure;
+ this.httpOnly = httpOnly;
+ }
+
+ /**
+ * Create a new instance copying the information in the supplied cookie.
+ *
+ * @param cookie the cookie to clone.
+ * @throws IllegalArgumentException if cookie is {@code null}.
+ */
+ public NewCookie(Cookie cookie) {
+ this(cookie, null, DEFAULT_MAX_AGE, null, false, false);
+ }
+
+ /**
+ * Create a new instance supplementing the information in the supplied cookie.
+ *
+ * @param cookie the cookie to clone.
+ * @param comment the comment.
+ * @param maxAge the maximum age of the cookie in seconds.
+ * @param secure specifies whether the cookie will only be sent over a secure connection.
+ * @throws IllegalArgumentException if cookie is {@code null}.
+ */
+ public NewCookie(Cookie cookie, String comment, int maxAge, boolean secure) {
+ this(cookie, comment, maxAge, null, secure, false);
+ }
+
+ /**
+ * Create a new instance supplementing the information in the supplied cookie.
+ *
+ * @param cookie the cookie to clone.
+ * @param comment the comment.
+ * @param maxAge the maximum age of the cookie in seconds.
+ * @param expiry the cookie expiry date.
+ * @param secure specifies whether the cookie will only be sent over a secure connection.
+ * @param httpOnly if {@code true} make the cookie HTTP only, i.e. only visible as part of an HTTP request.
+ * @throws IllegalArgumentException if cookie is {@code null}.
+ * @since 2.0
+ */
+ public NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly) {
+ super(cookie == null ? null : cookie.getName(),
+ cookie == null ? null : cookie.getValue(),
+ cookie == null ? null : cookie.getPath(),
+ cookie == null ? null : cookie.getDomain(),
+ cookie == null ? Cookie.DEFAULT_VERSION : cookie.getVersion());
+ this.comment = comment;
+ this.maxAge = maxAge;
+ this.expiry = expiry;
+ this.secure = secure;
+ this.httpOnly = httpOnly;
+ }
+
+ /**
+ * Creates a new instance of NewCookie by parsing the supplied string.
+ *
+ * @param value the cookie string.
+ * @return the newly created {@code NewCookie}.
+ * @throws IllegalArgumentException if the supplied string cannot be parsed
+ * or is {@code null}.
+ */
+ public static NewCookie valueOf(String value) {
+ return null;
+ }
+
+ /**
+ * Get the comment associated with the cookie.
+ *
+ * @return the comment or null if none set
+ */
+ public String getComment() {
+ return null;
+ }
+
+ /**
+ * Get the maximum age of the the cookie in seconds. Cookies older than
+ * the maximum age are discarded. A cookie can be unset by sending a new
+ * cookie with maximum age of 0 since it will overwrite any existing cookie
+ * and then be immediately discarded. The default value of {@code -1} indicates
+ * that the cookie will be discarded at the end of the browser/application session.
+ *
+ * Note that it is recommended to use {@code Max-Age} to control cookie
+ * expiration, however some browsers do not understand {@code Max-Age}, in which case
+ * setting {@link #getExpiry()} Expires} parameter may be necessary.
+ *
+ *
+ * @return the maximum age in seconds.
+ * @see #getExpiry()
+ */
+ public int getMaxAge() {
+ return -1;
+ }
+
+ /**
+ * Get the cookie expiry date. Cookies whose expiry date has passed are discarded.
+ * A cookie can be unset by setting a new cookie with an expiry date in the past,
+ * typically the lowest possible date that can be set.
+ *
+ * Note that it is recommended to use {@link #getMaxAge() Max-Age} to control cookie
+ * expiration, however some browsers do not understand {@code Max-Age}, in which case
+ * setting {@code Expires} parameter may be necessary.
+ *
+ *
+ * @return cookie expiry date or {@code null} if no expiry date was set.
+ * @see #getMaxAge()
+ * @since 2.0
+ */
+ public Date getExpiry() {
+ return null;
+ }
+
+ /**
+ * Whether the cookie will only be sent over a secure connection. Defaults
+ * to {@code false}.
+ *
+ * @return {@code true} if the cookie will only be sent over a secure connection,
+ * {@code false} otherwise.
+ */
+ public boolean isSecure() {
+ return false;
+ }
+
+ /**
+ * Returns {@code true} if this cookie contains the {@code HttpOnly} attribute.
+ * This means that the cookie should not be accessible to scripting engines,
+ * like javascript.
+ *
+ * @return {@code true} if this cookie should be considered http only, {@code false}
+ * otherwise.
+ * @since 2.0
+ */
+ public boolean isHttpOnly() {
+ return false;
+ }
+
+ /**
+ * Obtain a new instance of a {@link Cookie} with the same name, value, path,
+ * domain and version as this {@code NewCookie}. This method can be used to
+ * obtain an object that can be compared for equality with another {@code Cookie};
+ * since a {@code Cookie} will never compare equal to a {@code NewCookie}.
+ *
+ * @return a {@link Cookie}
+ */
+ public Cookie toCookie() {
+ return null;
+ }
+
+ /**
+ * Convert the cookie to a string suitable for use as the value of the
+ * corresponding HTTP header.
+ *
+ * @return a stringified cookie.
+ */
+ @Override
+ public String toString() {
+ return null;
+ }
+
+ /**
+ * Generate a hash code by hashing all of the properties.
+ *
+ * @return the hash code.
+ */
+ @Override
+ public int hashCode() {
+ return -1;
+ }
+
+ /**
+ * Compare for equality. Use {@link #toCookie()} to compare a
+ * {@code NewCookie} to a {@code Cookie} considering only the common
+ * properties.
+ *
+ * @param obj the object to compare to
+ * @return true if the object is a {@code NewCookie} with the same value for
+ * all properties, false otherwise.
+ */
+ @SuppressWarnings({"StringEquality", "RedundantIfStatement"})
+ @Override
+ public boolean equals(Object obj) {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/Cookie.java b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/Cookie.java
index d8348e13e2e..a93fec853e0 100644
--- a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/Cookie.java
+++ b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/Cookie.java
@@ -32,6 +32,7 @@ public class Cookie implements Cloneable {
private String path; // ;Path=VALUE ... URLs that see the cookie
private boolean secure; // ;Secure ... e.g. use SSL
private int version = 0; // ;Version=1 ... means RFC 2109++ style
+ private boolean isHttpOnly = false;
public Cookie(String name, String value) {
this.name = name;
@@ -81,4 +82,36 @@ public class Cookie implements Cloneable {
}
public void setVersion(int v) {
}
+
+ /**
+ * Marks or unmarks this Cookie as HttpOnly.
+ *
+ * If isHttpOnly is set to true, this cookie is
+ * marked as HttpOnly, by adding the HttpOnly attribute
+ * to it.
+ *
+ *
HttpOnly cookies are not supposed to be exposed to
+ * client-side scripting code, and may therefore help mitigate certain
+ * kinds of cross-site scripting attacks.
+ *
+ * @param isHttpOnly true if this cookie is to be marked as
+ * HttpOnly, false otherwise
+ *
+ * @since Servlet 3.0
+ */
+ public void setHttpOnly(boolean isHttpOnly) {
+ this.isHttpOnly = isHttpOnly;
+ }
+
+ /**
+ * Checks whether this Cookie has been marked as HttpOnly.
+ *
+ * @return true if this Cookie has been marked as HttpOnly,
+ * false otherwise
+ *
+ * @since Servlet 3.0
+ */
+ public boolean isHttpOnly() {
+ return isHttpOnly;
+ }
}
diff --git a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java
index 2971e023390..162ac0db3cc 100644
--- a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java
+++ b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java
@@ -24,6 +24,7 @@
package javax.servlet.http;
import java.io.IOException;
+import java.util.Collection;
import javax.servlet.ServletResponse;
public interface HttpServletResponse extends ServletResponse {
@@ -44,6 +45,11 @@ public interface HttpServletResponse extends ServletResponse {
public void addIntHeader(String name, int value);
public void setStatus(int sc);
public void setStatus(int sc, String sm);
+ public int getStatus();
+ public String getHeader(String name);
+ public Collection getHeaders(String name);
+ public Collection getHeaderNames();
+
public static final int SC_CONTINUE = 100;
public static final int SC_SWITCHING_PROTOCOLS = 101;
From b366ffa69e98e21ac8998bb08a8815fc4cae6b4f Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Wed, 3 Mar 2021 13:38:18 +0000
Subject: [PATCH 0009/1429] Revamp source of the query
---
.../CWE-1004/SensitiveCookieNotHttpOnly.qhelp | 2 +-
.../CWE-1004/SensitiveCookieNotHttpOnly.ql | 116 +++++++++---------
.../SensitiveCookieNotHttpOnly.expected | 12 +-
.../javax/ws/rs/core/Cookie.java | 59 +++------
.../javax/ws/rs/core/NewCookie.java | 36 ------
5 files changed, 82 insertions(+), 143 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp
index 880ed767be9..ee3e8a4181a 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp
@@ -2,7 +2,7 @@
- Cross-Site Scripting (XSS) is categorized as one of the OWASP Top 10 Security Vulnerabilities. The HttpOnly flag directs compatible browsers to prevent client-side script from accessing cookies. Including the HttpOnly flag in the Set-Cookie HTTP response header for a sensitive cookie helps mitigate the risk associated with XSS where an attacker's script code attempts to read the contents of a cookie and exfiltrate information obtained.
+ Cross-Site Scripting (XSS) is categorized as one of the OWASP Top 10 Security Vulnerabilities. The HttpOnly flag directs compatible browsers to prevent client-side script from accessing cookies. Including the HttpOnly flag in the Set-Cookie HTTP response header for a sensitive cookie helps mitigate the risk associated with XSS where an attacker's script code attempts to read the contents of a cookie and exfiltrate information obtained.
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index bf4e60134c9..842e8b7eabb 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -9,7 +9,6 @@
import java
import semmle.code.java.frameworks.Servlets
-import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import DataFlow::PathGraph
@@ -18,8 +17,8 @@ string getSensitiveCookieNameRegex() { result = "(?i).*(auth|session|token|key|c
/** Holds if a string is concatenated with the name of a sensitive cookie. */
predicate isSensitiveCookieNameExpr(Expr expr) {
- expr.(StringLiteral)
- .getRepresentedString()
+ expr.(CompileTimeConstantExpr)
+ .getStringValue()
.toLowerCase()
.regexpMatch(getSensitiveCookieNameRegex()) or
isSensitiveCookieNameExpr(expr.(AddExpr).getAnOperand())
@@ -27,7 +26,7 @@ predicate isSensitiveCookieNameExpr(Expr expr) {
/** Holds if a string is concatenated with the `HttpOnly` flag. */
predicate hasHttpOnlyExpr(Expr expr) {
- expr.(StringLiteral).getRepresentedString().toLowerCase().matches("%httponly%") or
+ expr.(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%") or
hasHttpOnlyExpr(expr.(AddExpr).getAnOperand())
}
@@ -38,37 +37,34 @@ class SetCookieMethodAccess extends MethodAccess {
this.getMethod() instanceof ResponseAddHeaderMethod or
this.getMethod() instanceof ResponseSetHeaderMethod
) and
- this.getArgument(0).(StringLiteral).getRepresentedString().toLowerCase() = "set-cookie"
+ this.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() = "set-cookie"
}
}
/** Sensitive cookie name used in a `Cookie` constructor or a `Set-Cookie` call. */
class SensitiveCookieNameExpr extends Expr {
SensitiveCookieNameExpr() {
- isSensitiveCookieNameExpr(this) and
- (
- exists(
- ClassInstanceExpr cie // new Cookie("jwt_token", token)
- |
- (
- cie.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") or
- cie.getConstructor()
- .getDeclaringType()
- .getASupertype*()
- .hasQualifiedName("javax.ws.rs.core", "Cookie") or
- cie.getConstructor()
- .getDeclaringType()
- .getASupertype*()
- .hasQualifiedName("jakarta.ws.rs.core", "Cookie")
- ) and
- DataFlow::localExprFlow(this, cie.getArgument(0))
- )
- or
- exists(
- SetCookieMethodAccess ma // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
- |
- DataFlow::localExprFlow(this, ma.getArgument(1))
- )
+ exists(
+ ClassInstanceExpr cie, Expr e // new Cookie("jwt_token", token)
+ |
+ (
+ cie.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") or
+ cie.getConstructor()
+ .getDeclaringType()
+ .getASupertype*()
+ .hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "Cookie")
+ ) and
+ this = cie and
+ isSensitiveCookieNameExpr(e) and
+ DataFlow::localExprFlow(e, cie.getArgument(0))
+ )
+ or
+ exists(
+ SetCookieMethodAccess ma, Expr e // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
+ |
+ this = ma.getArgument(1) and
+ isSensitiveCookieNameExpr(e) and
+ DataFlow::localExprFlow(e, ma.getArgument(1))
)
}
}
@@ -78,15 +74,20 @@ class CookieResponseSink extends DataFlow::ExprNode {
CookieResponseSink() {
exists(MethodAccess ma |
(
- ma.getMethod() instanceof ResponseAddCookieMethod or
- ma instanceof SetCookieMethodAccess
- ) and
- ma.getAnArgument() = this.getExpr()
+ ma.getMethod() instanceof ResponseAddCookieMethod and
+ this.getExpr() = ma.getArgument(0)
+ or
+ ma instanceof SetCookieMethodAccess and
+ this.getExpr() = ma.getArgument(1)
+ )
)
}
}
-/** Holds if the `node` is a method call of `setHttpOnly(true)` on a cookie. */
+/**
+ * Holds if `node` is an access to a variable which has `setHttpOnly(true)` called on it and is also
+ * the first argument to a call to the method `addCookie` of `javax.servlet.http.HttpServletResponse`.
+ */
predicate setHttpOnlyMethodAccess(DataFlow::Node node) {
exists(
MethodAccess addCookie, Variable cookie, MethodAccess m // jwtCookie.setHttpOnly(true)
@@ -100,7 +101,10 @@ predicate setHttpOnlyMethodAccess(DataFlow::Node node) {
)
}
-/** Holds if the `node` is a method call of `Set-Cookie` header with the `HttpOnly` flag whose cookie name is sensitive. */
+/**
+ * Holds if `node` is a string that contains `httponly` and which flows to the second argument
+ * of a method to set a cookie.
+ */
predicate setHttpOnlyInSetCookie(DataFlow::Node node) {
exists(SetCookieMethodAccess sa |
hasHttpOnlyExpr(node.asExpr()) and
@@ -108,20 +112,17 @@ predicate setHttpOnlyInSetCookie(DataFlow::Node node) {
)
}
-/** Holds if the `node` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
-predicate setHttpOnlyInNewCookie(DataFlow::Node node) {
- exists(ClassInstanceExpr cie |
- cie.getConstructor().getDeclaringType().hasName("NewCookie") and
- DataFlow::localExprFlow(node.asExpr(), cie.getArgument(0)) and
- (
- cie.getNumArgument() = 6 and cie.getArgument(5).(BooleanLiteral).getBooleanValue() = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
- or
- cie.getNumArgument() = 8 and
- cie.getArgument(6).getType() instanceof BooleanType and
- cie.getArgument(7).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
- or
- cie.getNumArgument() = 10 and cie.getArgument(9).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
- )
+/** Holds if `cie` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
+predicate setHttpOnlyInNewCookie(ClassInstanceExpr cie) {
+ cie.getConstructedType().hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "NewCookie") and
+ (
+ cie.getNumArgument() = 6 and cie.getArgument(5).(BooleanLiteral).getBooleanValue() = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
+ or
+ cie.getNumArgument() = 8 and
+ cie.getArgument(6).getType() instanceof BooleanType and
+ cie.getArgument(7).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
+ or
+ cie.getNumArgument() = 10 and cie.getArgument(9).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
)
}
@@ -145,7 +146,10 @@ predicate isTestMethod(DataFlow::Node node) {
)
}
-/** A taint configuration tracking flow from a sensitive cookie without HttpOnly flag set to its HTTP response. */
+/**
+ * A taint configuration tracking flow from a sensitive cookie without the `HttpOnly` flag
+ * set to its HTTP response.
+ */
class MissingHttpOnlyConfiguration extends TaintTracking::Configuration {
MissingHttpOnlyConfiguration() { this = "MissingHttpOnlyConfiguration" }
@@ -159,25 +163,17 @@ class MissingHttpOnlyConfiguration extends TaintTracking::Configuration {
// cookie.setHttpOnly(true)
setHttpOnlyMethodAccess(node)
or
- // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
+ // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
setHttpOnlyInSetCookie(node)
or
// new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)
- setHttpOnlyInNewCookie(node)
+ setHttpOnlyInNewCookie(node.asExpr())
or
// Test class or method
isTestMethod(node)
}
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
- exists(
- ClassInstanceExpr cie // `NewCookie` constructor
- |
- cie.getAnArgument() = pred.asExpr() and
- cie = succ.asExpr() and
- cie.getConstructor().getDeclaringType().hasName("NewCookie")
- )
- or
exists(
MethodAccess ma // `toString` call on a cookie object
|
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
index 84d6be3863a..e2d05a9b24d 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
@@ -1,13 +1,13 @@
edges
-| SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie |
-| SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) |
+| SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie |
+| SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) : NewCookie | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) |
nodes
-| SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | semmle.label | "jwt_token" : String |
+| SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | semmle.label | new Cookie(...) : Cookie |
| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | semmle.label | jwtCookie |
| SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | semmle.label | ... + ... |
+| SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) : NewCookie | semmle.label | new NewCookie(...) : NewCookie |
| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | semmle.label | toString(...) |
-| SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | semmle.label | "session-access-key" : String |
#select
-| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" | This sensitive cookie |
+| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | This sensitive cookie |
-| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" | This sensitive cookie |
+| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) : NewCookie | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) | This sensitive cookie |
diff --git a/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java
index 4e4c7585c35..f810600a766 100644
--- a/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java
+++ b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java
@@ -51,10 +51,16 @@ package javax.ws.rs.core;
* @since 1.0
*/
public class Cookie {
+
/**
* Cookies using the default version correspond to RFC 2109.
*/
public static final int DEFAULT_VERSION = 1;
+ private final String name;
+ private final String value;
+ private final int version;
+ private final String path;
+ private final String domain;
/**
* Create a new instance.
@@ -68,6 +74,11 @@ public class Cookie {
*/
public Cookie(final String name, final String value, final String path, final String domain, final int version)
throws IllegalArgumentException {
+ this.name = name;
+ this.value = value;
+ this.version = version;
+ this.domain = domain;
+ this.path = path;
}
/**
@@ -81,6 +92,7 @@ public class Cookie {
*/
public Cookie(final String name, final String value, final String path, final String domain)
throws IllegalArgumentException {
+ this(name, value, path, domain, DEFAULT_VERSION);
}
/**
@@ -92,6 +104,7 @@ public class Cookie {
*/
public Cookie(final String name, final String value)
throws IllegalArgumentException {
+ this(name, value, null, null);
}
/**
@@ -112,7 +125,7 @@ public class Cookie {
* @return the cookie name.
*/
public String getName() {
- return null;
+ return name;
}
/**
@@ -121,7 +134,7 @@ public class Cookie {
* @return the cookie value.
*/
public String getValue() {
- return null;
+ return value;
}
/**
@@ -130,7 +143,7 @@ public class Cookie {
* @return the cookie version.
*/
public int getVersion() {
- return -1;
+ return version;
}
/**
@@ -139,7 +152,7 @@ public class Cookie {
* @return the cookie domain.
*/
public String getDomain() {
- return null;
+ return domain;
}
/**
@@ -148,40 +161,6 @@ public class Cookie {
* @return the cookie path.
*/
public String getPath() {
- return null;
+ return path;
}
-
- /**
- * Convert the cookie to a string suitable for use as the value of the
- * corresponding HTTP header.
- *
- * @return a stringified cookie.
- */
- @Override
- public String toString() {
- return null;
- }
-
- /**
- * Generate a hash code by hashing all of the cookies properties.
- *
- * @return the cookie hash code.
- */
- @Override
- public int hashCode() {
- return -1;
- }
-
- /**
- * Compare for equality.
- *
- * @param obj the object to compare to.
- * @return {@code true}, if the object is a {@code Cookie} with the same
- * value for all properties, {@code false} otherwise.
- */
- @SuppressWarnings({"StringEquality", "RedundantIfStatement"})
- @Override
- public boolean equals(final Object obj) {
- return true;
- }
-}
\ No newline at end of file
+}
diff --git a/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java
index 43a4899e0ca..7f2e3ec0535 100644
--- a/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java
+++ b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java
@@ -320,40 +320,4 @@ public class NewCookie extends Cookie {
public Cookie toCookie() {
return null;
}
-
- /**
- * Convert the cookie to a string suitable for use as the value of the
- * corresponding HTTP header.
- *
- * @return a stringified cookie.
- */
- @Override
- public String toString() {
- return null;
- }
-
- /**
- * Generate a hash code by hashing all of the properties.
- *
- * @return the hash code.
- */
- @Override
- public int hashCode() {
- return -1;
- }
-
- /**
- * Compare for equality. Use {@link #toCookie()} to compare a
- * {@code NewCookie} to a {@code Cookie} considering only the common
- * properties.
- *
- * @param obj the object to compare to
- * @return true if the object is a {@code NewCookie} with the same value for
- * all properties, false otherwise.
- */
- @SuppressWarnings({"StringEquality", "RedundantIfStatement"})
- @Override
- public boolean equals(Object obj) {
- return true;
- }
}
\ No newline at end of file
From 1b1c3f953b9ebb828e161e4b36d67075a7bca510 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Wed, 3 Mar 2021 13:54:26 +0000
Subject: [PATCH 0010/1429] Remove localflow from the source
---
.../CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index 842e8b7eabb..258b2545b27 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -45,7 +45,7 @@ class SetCookieMethodAccess extends MethodAccess {
class SensitiveCookieNameExpr extends Expr {
SensitiveCookieNameExpr() {
exists(
- ClassInstanceExpr cie, Expr e // new Cookie("jwt_token", token)
+ ClassInstanceExpr cie // new Cookie("jwt_token", token)
|
(
cie.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") or
@@ -55,16 +55,14 @@ class SensitiveCookieNameExpr extends Expr {
.hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "Cookie")
) and
this = cie and
- isSensitiveCookieNameExpr(e) and
- DataFlow::localExprFlow(e, cie.getArgument(0))
+ isSensitiveCookieNameExpr(cie.getArgument(0))
)
or
exists(
- SetCookieMethodAccess ma, Expr e // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
+ SetCookieMethodAccess ma // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
|
this = ma.getArgument(1) and
- isSensitiveCookieNameExpr(e) and
- DataFlow::localExprFlow(e, ma.getArgument(1))
+ isSensitiveCookieNameExpr(this)
)
}
}
From 502cf38fccef4c0b2a1120f2fe175571ec1b9cb5 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Wed, 3 Mar 2021 14:07:43 +0000
Subject: [PATCH 0011/1429] Use concise API
---
.../Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index 258b2545b27..d9104cbad97 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -48,9 +48,8 @@ class SensitiveCookieNameExpr extends Expr {
ClassInstanceExpr cie // new Cookie("jwt_token", token)
|
(
- cie.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") or
- cie.getConstructor()
- .getDeclaringType()
+ cie.getConstructedType().hasQualifiedName("javax.servlet.http", "Cookie") or
+ cie.getConstructedType()
.getASupertype*()
.hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "Cookie")
) and
From 86dde6eab16f9f28cc6bb68c8b2ed7fa8c40e3bd Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 17 Feb 2021 11:34:05 +0100
Subject: [PATCH 0012/1429] Python: start of port
---
.../src/Security/CWE-327/InsecureProtocol.ql | 299 +++++++++++++++++-
.../examples/secure_default_protocol.py | 13 +
.../CWE-327/examples/secure_protocol.py | 11 +
3 files changed, 307 insertions(+), 16 deletions(-)
create mode 100644 python/ql/src/Security/CWE-327/examples/secure_default_protocol.py
create mode 100644 python/ql/src/Security/CWE-327/examples/secure_protocol.py
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index d1ae714b6be..6f2feedce22 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -10,7 +10,270 @@
*/
import python
+import semmle.python.ApiGraphs
+// The idea is to track flow from the creation of an insecure context to a use
+// such as `wrap_socket`. There should be a data-flow path for each insecure version
+// and each path should have a version specific sanitizer. This will allow fluent api
+// style code to block the paths one by one.
+//
+// class InsecureContextCreation extends DataFlow::CfgNode {
+// override CallNode node;
+// InsecureContextCreation() {
+// this = API::moduleImport("ssl").getMember("SSLContext").getACall() and
+// insecure_version().asCfgNode() in [node.getArg(0), node.getArgByName("protocol")]
+// }
+// }
+// class InsecureSSLContextCreation extends DataFlow::CfgNode {
+// override CallNode node;
+// InsecureSSLContextCreation() {
+// this = API::moduleImport("ssl").getMember("create_default_context").getACall()
+// or
+// this = API::moduleImport("ssl").getMember("SSLContext").getACall() and
+// API::moduleImport("ssl").getMember("PROTOCOL_TLS").getAUse().asCfgNode() in [
+// node.getArg(0), node.getArgByName("protocol")
+// ]
+// }
+// }
+abstract class ContextCreation extends DataFlow::CfgNode {
+ abstract DataFlow::CfgNode getProtocol();
+}
+
+class SSLContextCreation extends ContextCreation {
+ override CallNode node;
+
+ SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
+
+ override DataFlow::CfgNode getProtocol() {
+ result.getNode() in [node.getArg(0), node.getArgByName("protocol")]
+ }
+}
+
+class PyOpenSSLContextCreation extends ContextCreation {
+ override CallNode node;
+
+ PyOpenSSLContextCreation() {
+ this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Context").getACall()
+ }
+
+ override DataFlow::CfgNode getProtocol() {
+ result.getNode() in [node.getArg(0), node.getArgByName("method")]
+ }
+}
+
+abstract class ConnectionCreation extends DataFlow::CfgNode {
+ abstract DataFlow::CfgNode getContext();
+}
+
+class WrapSocketCall extends ConnectionCreation {
+ override CallNode node;
+
+ WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() = node.getFunction().(AttrNode).getObject()
+ }
+}
+
+class ConnectionCall extends ConnectionCreation {
+ override CallNode node;
+
+ ConnectionCall() {
+ this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Connection").getACall()
+ }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() in [node.getArg(0), node.getArgByName("context")]
+ }
+}
+
+abstract class TlsLibrary extends string {
+ TlsLibrary() { this in ["ssl"] }
+
+ abstract string specific_insecure_version_name();
+
+ abstract string unspecific_version_name();
+
+ abstract API::Node version_constants();
+
+ DataFlow::Node insecure_version() {
+ result = version_constants().getMember(specific_insecure_version_name()).getAUse()
+ }
+
+ DataFlow::Node unspecific_version() {
+ result = version_constants().getMember(unspecific_version_name()).getAUse()
+ }
+
+ abstract DataFlow::CfgNode default_context_creation();
+
+ abstract ContextCreation specific_context_creation();
+
+ ContextCreation insecure_context_creation() {
+ result = specific_context_creation() and
+ result.getProtocol() = insecure_version()
+ }
+
+ DataFlow::CfgNode unspecific_context_creation() {
+ result = default_context_creation()
+ or
+ result = specific_context_creation() and
+ result.(ContextCreation).getProtocol() = unspecific_version()
+ }
+
+ abstract ConnectionCreation connection_creation();
+}
+
+class Ssl extends TlsLibrary {
+ Ssl() { this = "ssl" }
+
+ override string specific_insecure_version_name() {
+ result in [
+ "PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_TLSv1", "PROTOCOL_TLSv1_1"
+ ]
+ }
+
+ override string unspecific_version_name() { result = "PROTOCOL_TLS" }
+
+ override API::Node version_constants() { result = API::moduleImport("ssl") }
+
+ override DataFlow::CfgNode default_context_creation() {
+ result = API::moduleImport("ssl").getMember("create_default_context").getACall()
+ }
+
+ override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
+
+ override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
+}
+
+class PyOpenSSL extends TlsLibrary {
+ PyOpenSSL() { this = "pyOpenSSL" }
+
+ override string specific_insecure_version_name() {
+ result in ["SSLv2_METHOD", "SSLv23_METHOD", "SSLv3_METHOD", "TLSv1_METHOD", "TLSv1_1_METHOD"]
+ }
+
+ override string unspecific_version_name() { result = "TLS_METHOD" }
+
+ override API::Node version_constants() {
+ result = API::moduleImport("pyOpenSSL").getMember("SSL")
+ }
+
+ override DataFlow::CfgNode default_context_creation() { none() }
+
+ override ContextCreation specific_context_creation() {
+ result instanceof PyOpenSSLContextCreation
+ }
+
+ override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
+}
+
+module ssl {
+ string insecure_version_name() {
+ result = "PROTOCOL_SSLv2" or
+ result = "PROTOCOL_SSLv3" or
+ result = "PROTOCOL_SSLv23" or
+ result = "PROTOCOL_TLSv1" or
+ result = "PROTOCOL_TLSv1_1"
+ }
+
+ DataFlow::Node insecure_version() {
+ result = API::moduleImport("ssl").getMember(insecure_version_name()).getAUse()
+ }
+}
+
+module pyOpenSSL {
+ string insecure_version_name() {
+ result = "SSLv2_METHOD" or
+ result = "SSLv23_METHOD" or
+ result = "SSLv3_METHOD" or
+ result = "TLSv1_METHOD" or
+ result = "TLSv1_1_METHOD"
+ }
+
+ DataFlow::Node insecure_version() {
+ result =
+ API::moduleImport("pyOpenSSL").getMember("SSL").getMember(insecure_version_name()).getAUse()
+ }
+}
+
+class InsecureContextConfiguration extends DataFlow::Configuration {
+ TlsLibrary library;
+
+ InsecureContextConfiguration() { this = library + ["AllowsTLSv1", "AllowsTLSv1_1"] }
+
+ override predicate isSource(DataFlow::Node source) {
+ source = library.unspecific_context_creation()
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink = library.connection_creation().getContext()
+ }
+
+ abstract string flag();
+
+ override predicate isBarrierOut(DataFlow::Node node) {
+ exists(AugAssign aa, AttrNode attr |
+ aa.getOperation().getOp() instanceof BitOr and
+ aa.getTarget() = attr.getNode() and
+ attr.getName() = "options" and
+ attr.getObject() = node.asCfgNode() and
+ aa.getValue() = API::moduleImport("ssl").getMember(flag()).getAUse().asExpr()
+ )
+ }
+}
+
+class AllowsTLSv1 extends InsecureContextConfiguration {
+ AllowsTLSv1() { this = library + "AllowsTLSv1" }
+
+ override string flag() { result = "OP_NO_TLSv1" }
+}
+
+class AllowsTLSv1_1 extends InsecureContextConfiguration {
+ AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" }
+
+ override string flag() { result = "OP_NO_TLSv1_2" }
+}
+
+predicate unsafe_connection_creation(DataFlow::Node node) {
+ exists(AllowsTLSv1 c | c.hasFlowTo(node)) or
+ exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) //or
+ // node = API::moduleImport("ssl").getMember("wrap_socket").getACall()
+}
+
+predicate unsafe_context_creation(DataFlow::Node node) {
+ exists(TlsLibrary l | l.insecure_context_creation() = node)
+}
+
+// class InsecureTLSContextConfiguration extends DataFlow::Configuration {
+// InsecureTLSContextConfiguration() { this in ["AllowsTLSv1", "AllowsTLSv1_1"] }
+// override predicate isSource(DataFlow::Node source) {
+// source instanceof InsecureSSLContextCreation
+// }
+// override predicate isSink(DataFlow::Node sink) { sink = any(WrapSocketCall c).getContext() }
+// abstract string flag();
+// override predicate isBarrierOut(DataFlow::Node node) {
+// exists(AugAssign aa, AttrNode attr |
+// aa.getOperation().getOp() instanceof BitOr and
+// aa.getTarget() = attr.getNode() and
+// attr.getName() = "options" and
+// attr.getObject() = node.asCfgNode() and
+// aa.getValue() = API::moduleImport("ssl").getMember(flag()).getAUse().asExpr()
+// )
+// }
+// }
+// class AllowsTLSv1 extends InsecureTLSContextConfiguration {
+// AllowsTLSv1() { this = "AllowsTLSv1" }
+// override string flag() { result = "OP_NO_TLSv1" }
+// }
+// class AllowsTLSv1_1 extends InsecureTLSContextConfiguration {
+// AllowsTLSv1_1() { this = "AllowsTLSv1_1" }
+// override string flag() { result = "OP_NO_TLSv1_1" }
+// }
+// predicate unsafe_wrap_socket_call(DataFlow::Node node) {
+// exists(AllowsTLSv1 c | c.hasFlowTo(node)) or
+// exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) or
+// node = API::moduleImport("ssl").getMember("wrap_socket").getACall()
+// }
private ModuleValue the_ssl_module() { result = Module::named("ssl") }
FunctionValue ssl_wrap_socket() { result = the_ssl_module().attr("wrap_socket") }
@@ -21,20 +284,24 @@ private ModuleValue the_pyOpenSSL_module() { result = Value::named("pyOpenSSL.SS
ClassValue the_pyOpenSSL_Context_class() { result = Value::named("pyOpenSSL.SSL.Context") }
-string insecure_version_name() {
- // For `pyOpenSSL.SSL`
- result = "SSLv2_METHOD" or
- result = "SSLv23_METHOD" or
- result = "SSLv3_METHOD" or
- result = "TLSv1_METHOD" or
- // For the `ssl` module
- result = "PROTOCOL_SSLv2" or
- result = "PROTOCOL_SSLv3" or
- result = "PROTOCOL_SSLv23" or
- result = "PROTOCOL_TLS" or
- result = "PROTOCOL_TLSv1"
-}
-
+// Since version 3.6, it is fine to call `ssl.SSLContext(protocol=PROTOCOL_TLS)`
+// if one also specifies either OP_NO_TLSv1 (introduced in 3.2)
+// or SSLContext.minimum_version other than TLSVersion.TLSv1 (introduced in 3.7)
+// See https://docs.python.org/3/library/ssl.html?highlight=ssl#ssl.SSLContext
+// and https://docs.python.org/3/library/ssl.html?highlight=ssl#protocol-versions
+// FP reported here: https://github.com/github/codeql/issues/2554
+// string insecure_version_name() {
+// // For `pyOpenSSL.SSL`
+// result = "SSLv2_METHOD" or
+// result = "SSLv23_METHOD" or
+// result = "SSLv3_METHOD" or
+// result = "TLSv1_METHOD" or
+// // For the `ssl` module
+// result = "PROTOCOL_SSLv2" or
+// result = "PROTOCOL_SSLv3" or
+// result = "PROTOCOL_SSLv23" or
+// result = "PROTOCOL_TLSv1"
+// }
/*
* A syntactic check for cases where points-to analysis cannot infer the presence of
* a protocol constant, e.g. if it has been removed in later versions of the `ssl`
@@ -71,7 +338,7 @@ predicate unsafe_ssl_wrap_socket_call(
named_argument = "protocol" and
method_name = "ssl.SSLContext"
) and
- insecure_version = insecure_version_name() and
+ insecure_version = ssl::insecure_version_name() and
(
call.getArgByName(named_argument).pointsTo(the_ssl_module().attr(insecure_version))
or
@@ -81,7 +348,7 @@ predicate unsafe_ssl_wrap_socket_call(
predicate unsafe_pyOpenSSL_Context_call(CallNode call, string insecure_version) {
call = the_pyOpenSSL_Context_class().getACall() and
- insecure_version = insecure_version_name() and
+ insecure_version = pyOpenSSL::insecure_version_name() and
call.getArg(0).pointsTo(the_pyOpenSSL_module().attr(insecure_version))
}
diff --git a/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py b/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py
new file mode 100644
index 00000000000..83c6dbbba0e
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py
@@ -0,0 +1,13 @@
+# taken from https://docs.python.org/3/library/ssl.html?highlight=ssl#ssl.SSLContext
+
+import socket
+import ssl
+
+hostname = 'www.python.org'
+context = ssl.create_default_context()
+context.options |= ssl.OP_NO_TLSv1 # This added by me
+context.options |= ssl.OP_NO_TLSv1_1 # This added by me
+
+with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
diff --git a/python/ql/src/Security/CWE-327/examples/secure_protocol.py b/python/ql/src/Security/CWE-327/examples/secure_protocol.py
new file mode 100644
index 00000000000..94b3557d017
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/examples/secure_protocol.py
@@ -0,0 +1,11 @@
+import socket
+import ssl
+
+hostname = 'www.python.org'
+context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+context.options |= ssl.OP_NO_TLSv1
+context.options |= ssl.OP_NO_TLSv1_1 # This added by me
+
+with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
From 72b37a5b1bb344dcdd9b4e2d1172c540df678408 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 17 Feb 2021 13:21:33 +0100
Subject: [PATCH 0013/1429] Python: factor out barrier
---
.../src/Security/CWE-327/InsecureProtocol.ql | 58 ++++++++++++++++---
1 file changed, 51 insertions(+), 7 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index 6f2feedce22..6b5b2e75d22 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -87,6 +87,46 @@ class ConnectionCall extends ConnectionCreation {
}
}
+class ProtocolRestriction extends DataFlow::CfgNode {
+ abstract DataFlow::CfgNode getContext();
+
+ abstract string getRestriction();
+}
+
+class OptionsAugOr extends ProtocolRestriction {
+ string restriction;
+
+ OptionsAugOr() {
+ exists(AugAssign aa, AttrNode attr |
+ aa.getOperation().getOp() instanceof BitOr and
+ aa.getTarget() = attr.getNode() and
+ attr.getName() = "options" and
+ attr.getObject() = node and
+ aa.getValue() = API::moduleImport("ssl").getMember(restriction).getAUse().asExpr()
+ )
+ }
+
+ override DataFlow::CfgNode getContext() { result = this }
+
+ override string getRestriction() { result = restriction }
+}
+
+class SetOptionsCall extends ProtocolRestriction {
+ override CallNode node;
+
+ SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() = node.getFunction().(AttrNode).getObject()
+ }
+
+ override string getRestriction() {
+ API::moduleImport("PyOpenSSL").getMember("SSL").getMember(result).getAUse().asCfgNode() in [
+ node.getArg(0), node.getArgByName("options")
+ ]
+ }
+}
+
abstract class TlsLibrary extends string {
TlsLibrary() { this in ["ssl"] }
@@ -121,6 +161,8 @@ abstract class TlsLibrary extends string {
}
abstract ConnectionCreation connection_creation();
+
+ abstract ProtocolRestriction protocol_restriction();
}
class Ssl extends TlsLibrary {
@@ -143,6 +185,8 @@ class Ssl extends TlsLibrary {
override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
+
+ override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr }
}
class PyOpenSSL extends TlsLibrary {
@@ -165,6 +209,8 @@ class PyOpenSSL extends TlsLibrary {
}
override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
+
+ override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr }
}
module ssl {
@@ -212,12 +258,10 @@ class InsecureContextConfiguration extends DataFlow::Configuration {
abstract string flag();
override predicate isBarrierOut(DataFlow::Node node) {
- exists(AugAssign aa, AttrNode attr |
- aa.getOperation().getOp() instanceof BitOr and
- aa.getTarget() = attr.getNode() and
- attr.getName() = "options" and
- attr.getObject() = node.asCfgNode() and
- aa.getValue() = API::moduleImport("ssl").getMember(flag()).getAUse().asExpr()
+ exists(ProtocolRestriction r |
+ r = library.protocol_restriction() and
+ node = r.getContext() and
+ r.getRestriction() = flag()
)
}
}
@@ -231,7 +275,7 @@ class AllowsTLSv1 extends InsecureContextConfiguration {
class AllowsTLSv1_1 extends InsecureContextConfiguration {
AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" }
- override string flag() { result = "OP_NO_TLSv1_2" }
+ override string flag() { result = "OP_NO_TLSv1_1" }
}
predicate unsafe_connection_creation(DataFlow::Node node) {
From 7ed018aff6c9f79a3ea60ef8ae4d2f2067b81442 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 17 Feb 2021 14:30:28 +0100
Subject: [PATCH 0014/1429] Python: refactor into modules and turn on the
pyOpenSSL module
---
.../src/Security/CWE-327/InsecureProtocol.ql | 429 +++++++-----------
1 file changed, 160 insertions(+), 269 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index 6b5b2e75d22..81b3558907e 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -39,96 +39,18 @@ abstract class ContextCreation extends DataFlow::CfgNode {
abstract DataFlow::CfgNode getProtocol();
}
-class SSLContextCreation extends ContextCreation {
- override CallNode node;
-
- SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
-
- override DataFlow::CfgNode getProtocol() {
- result.getNode() in [node.getArg(0), node.getArgByName("protocol")]
- }
-}
-
-class PyOpenSSLContextCreation extends ContextCreation {
- override CallNode node;
-
- PyOpenSSLContextCreation() {
- this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Context").getACall()
- }
-
- override DataFlow::CfgNode getProtocol() {
- result.getNode() in [node.getArg(0), node.getArgByName("method")]
- }
-}
-
abstract class ConnectionCreation extends DataFlow::CfgNode {
abstract DataFlow::CfgNode getContext();
}
-class WrapSocketCall extends ConnectionCreation {
- override CallNode node;
-
- WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" }
-
- override DataFlow::CfgNode getContext() {
- result.getNode() = node.getFunction().(AttrNode).getObject()
- }
-}
-
-class ConnectionCall extends ConnectionCreation {
- override CallNode node;
-
- ConnectionCall() {
- this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Connection").getACall()
- }
-
- override DataFlow::CfgNode getContext() {
- result.getNode() in [node.getArg(0), node.getArgByName("context")]
- }
-}
-
class ProtocolRestriction extends DataFlow::CfgNode {
abstract DataFlow::CfgNode getContext();
abstract string getRestriction();
}
-class OptionsAugOr extends ProtocolRestriction {
- string restriction;
-
- OptionsAugOr() {
- exists(AugAssign aa, AttrNode attr |
- aa.getOperation().getOp() instanceof BitOr and
- aa.getTarget() = attr.getNode() and
- attr.getName() = "options" and
- attr.getObject() = node and
- aa.getValue() = API::moduleImport("ssl").getMember(restriction).getAUse().asExpr()
- )
- }
-
- override DataFlow::CfgNode getContext() { result = this }
-
- override string getRestriction() { result = restriction }
-}
-
-class SetOptionsCall extends ProtocolRestriction {
- override CallNode node;
-
- SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" }
-
- override DataFlow::CfgNode getContext() {
- result.getNode() = node.getFunction().(AttrNode).getObject()
- }
-
- override string getRestriction() {
- API::moduleImport("PyOpenSSL").getMember("SSL").getMember(result).getAUse().asCfgNode() in [
- node.getArg(0), node.getArgByName("options")
- ]
- }
-}
-
abstract class TlsLibrary extends string {
- TlsLibrary() { this in ["ssl"] }
+ TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
abstract string specific_insecure_version_name();
@@ -160,85 +82,149 @@ abstract class TlsLibrary extends string {
result.(ContextCreation).getProtocol() = unspecific_version()
}
+ /** A connection is created in an outright insecure manner. */
+ abstract DataFlow::CfgNode insecure_connection_creation();
+
+ /** A connection is created from a context. */
abstract ConnectionCreation connection_creation();
abstract ProtocolRestriction protocol_restriction();
}
-class Ssl extends TlsLibrary {
- Ssl() { this = "ssl" }
-
- override string specific_insecure_version_name() {
- result in [
- "PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_TLSv1", "PROTOCOL_TLSv1_1"
- ]
- }
-
- override string unspecific_version_name() { result = "PROTOCOL_TLS" }
-
- override API::Node version_constants() { result = API::moduleImport("ssl") }
-
- override DataFlow::CfgNode default_context_creation() {
- result = API::moduleImport("ssl").getMember("create_default_context").getACall()
- }
-
- override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
-
- override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
-
- override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr }
-}
-
-class PyOpenSSL extends TlsLibrary {
- PyOpenSSL() { this = "pyOpenSSL" }
-
- override string specific_insecure_version_name() {
- result in ["SSLv2_METHOD", "SSLv23_METHOD", "SSLv3_METHOD", "TLSv1_METHOD", "TLSv1_1_METHOD"]
- }
-
- override string unspecific_version_name() { result = "TLS_METHOD" }
-
- override API::Node version_constants() {
- result = API::moduleImport("pyOpenSSL").getMember("SSL")
- }
-
- override DataFlow::CfgNode default_context_creation() { none() }
-
- override ContextCreation specific_context_creation() {
- result instanceof PyOpenSSLContextCreation
- }
-
- override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
-
- override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr }
-}
-
module ssl {
- string insecure_version_name() {
- result = "PROTOCOL_SSLv2" or
- result = "PROTOCOL_SSLv3" or
- result = "PROTOCOL_SSLv23" or
- result = "PROTOCOL_TLSv1" or
- result = "PROTOCOL_TLSv1_1"
+ class SSLContextCreation extends ContextCreation {
+ override CallNode node;
+
+ SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
+
+ override DataFlow::CfgNode getProtocol() {
+ result.getNode() in [node.getArg(0), node.getArgByName("protocol")]
+ }
}
- DataFlow::Node insecure_version() {
- result = API::moduleImport("ssl").getMember(insecure_version_name()).getAUse()
+ class WrapSocketCall extends ConnectionCreation {
+ override CallNode node;
+
+ WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() = node.getFunction().(AttrNode).getObject()
+ }
+ }
+
+ class OptionsAugOr extends ProtocolRestriction {
+ string restriction;
+
+ OptionsAugOr() {
+ exists(AugAssign aa, AttrNode attr |
+ aa.getOperation().getOp() instanceof BitOr and
+ aa.getTarget() = attr.getNode() and
+ attr.getName() = "options" and
+ attr.getObject() = node and
+ aa.getValue() = API::moduleImport("ssl").getMember(restriction).getAUse().asExpr()
+ )
+ }
+
+ override DataFlow::CfgNode getContext() { result = this }
+
+ override string getRestriction() { result = restriction }
+ }
+
+ class Ssl extends TlsLibrary {
+ Ssl() { this = "ssl" }
+
+ override string specific_insecure_version_name() {
+ result in [
+ "PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_TLSv1",
+ "PROTOCOL_TLSv1_1"
+ ]
+ }
+
+ override string unspecific_version_name() { result = "PROTOCOL_TLS" }
+
+ override API::Node version_constants() { result = API::moduleImport("ssl") }
+
+ override DataFlow::CfgNode default_context_creation() {
+ result = API::moduleImport("ssl").getMember("create_default_context").getACall()
+ }
+
+ override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
+
+ override DataFlow::CfgNode insecure_connection_creation() {
+ result = API::moduleImport("ssl").getMember("wrap_socket").getACall()
+ }
+
+ override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
+
+ override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr }
}
}
module pyOpenSSL {
- string insecure_version_name() {
- result = "SSLv2_METHOD" or
- result = "SSLv23_METHOD" or
- result = "SSLv3_METHOD" or
- result = "TLSv1_METHOD" or
- result = "TLSv1_1_METHOD"
+ class PyOpenSSLContextCreation extends ContextCreation {
+ override CallNode node;
+
+ PyOpenSSLContextCreation() {
+ this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Context").getACall()
+ }
+
+ override DataFlow::CfgNode getProtocol() {
+ result.getNode() in [node.getArg(0), node.getArgByName("method")]
+ }
}
- DataFlow::Node insecure_version() {
- result =
- API::moduleImport("pyOpenSSL").getMember("SSL").getMember(insecure_version_name()).getAUse()
+ class ConnectionCall extends ConnectionCreation {
+ override CallNode node;
+
+ ConnectionCall() {
+ this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Connection").getACall()
+ }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() in [node.getArg(0), node.getArgByName("context")]
+ }
+ }
+
+ class SetOptionsCall extends ProtocolRestriction {
+ override CallNode node;
+
+ SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() = node.getFunction().(AttrNode).getObject()
+ }
+
+ override string getRestriction() {
+ API::moduleImport("PyOpenSSL").getMember("SSL").getMember(result).getAUse().asCfgNode() in [
+ node.getArg(0), node.getArgByName("options")
+ ]
+ }
+ }
+
+ class PyOpenSSL extends TlsLibrary {
+ PyOpenSSL() { this = "pyOpenSSL" }
+
+ override string specific_insecure_version_name() {
+ result in ["SSLv2_METHOD", "SSLv23_METHOD", "SSLv3_METHOD", "TLSv1_METHOD", "TLSv1_1_METHOD"]
+ }
+
+ override string unspecific_version_name() { result = "TLS_METHOD" }
+
+ override API::Node version_constants() {
+ result = API::moduleImport("pyOpenSSL").getMember("SSL")
+ }
+
+ override DataFlow::CfgNode default_context_creation() { none() }
+
+ override ContextCreation specific_context_creation() {
+ result instanceof PyOpenSSLContextCreation
+ }
+
+ override DataFlow::CfgNode insecure_connection_creation() { none() }
+
+ override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
+
+ override ProtocolRestriction protocol_restriction() { result instanceof SetOptionsCall }
}
}
@@ -278,129 +264,34 @@ class AllowsTLSv1_1 extends InsecureContextConfiguration {
override string flag() { result = "OP_NO_TLSv1_1" }
}
-predicate unsafe_connection_creation(DataFlow::Node node) {
- exists(AllowsTLSv1 c | c.hasFlowTo(node)) or
- exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) //or
- // node = API::moduleImport("ssl").getMember("wrap_socket").getACall()
-}
-
-predicate unsafe_context_creation(DataFlow::Node node) {
- exists(TlsLibrary l | l.insecure_context_creation() = node)
-}
-
-// class InsecureTLSContextConfiguration extends DataFlow::Configuration {
-// InsecureTLSContextConfiguration() { this in ["AllowsTLSv1", "AllowsTLSv1_1"] }
-// override predicate isSource(DataFlow::Node source) {
-// source instanceof InsecureSSLContextCreation
-// }
-// override predicate isSink(DataFlow::Node sink) { sink = any(WrapSocketCall c).getContext() }
-// abstract string flag();
-// override predicate isBarrierOut(DataFlow::Node node) {
-// exists(AugAssign aa, AttrNode attr |
-// aa.getOperation().getOp() instanceof BitOr and
-// aa.getTarget() = attr.getNode() and
-// attr.getName() = "options" and
-// attr.getObject() = node.asCfgNode() and
-// aa.getValue() = API::moduleImport("ssl").getMember(flag()).getAUse().asExpr()
-// )
-// }
-// }
-// class AllowsTLSv1 extends InsecureTLSContextConfiguration {
-// AllowsTLSv1() { this = "AllowsTLSv1" }
-// override string flag() { result = "OP_NO_TLSv1" }
-// }
-// class AllowsTLSv1_1 extends InsecureTLSContextConfiguration {
-// AllowsTLSv1_1() { this = "AllowsTLSv1_1" }
-// override string flag() { result = "OP_NO_TLSv1_1" }
-// }
-// predicate unsafe_wrap_socket_call(DataFlow::Node node) {
-// exists(AllowsTLSv1 c | c.hasFlowTo(node)) or
-// exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) or
-// node = API::moduleImport("ssl").getMember("wrap_socket").getACall()
-// }
-private ModuleValue the_ssl_module() { result = Module::named("ssl") }
-
-FunctionValue ssl_wrap_socket() { result = the_ssl_module().attr("wrap_socket") }
-
-ClassValue ssl_Context_class() { result = the_ssl_module().attr("SSLContext") }
-
-private ModuleValue the_pyOpenSSL_module() { result = Value::named("pyOpenSSL.SSL") }
-
-ClassValue the_pyOpenSSL_Context_class() { result = Value::named("pyOpenSSL.SSL.Context") }
-
-// Since version 3.6, it is fine to call `ssl.SSLContext(protocol=PROTOCOL_TLS)`
-// if one also specifies either OP_NO_TLSv1 (introduced in 3.2)
-// or SSLContext.minimum_version other than TLSVersion.TLSv1 (introduced in 3.7)
-// See https://docs.python.org/3/library/ssl.html?highlight=ssl#ssl.SSLContext
-// and https://docs.python.org/3/library/ssl.html?highlight=ssl#protocol-versions
-// FP reported here: https://github.com/github/codeql/issues/2554
-// string insecure_version_name() {
-// // For `pyOpenSSL.SSL`
-// result = "SSLv2_METHOD" or
-// result = "SSLv23_METHOD" or
-// result = "SSLv3_METHOD" or
-// result = "TLSv1_METHOD" or
-// // For the `ssl` module
-// result = "PROTOCOL_SSLv2" or
-// result = "PROTOCOL_SSLv3" or
-// result = "PROTOCOL_SSLv23" or
-// result = "PROTOCOL_TLSv1"
-// }
-/*
- * A syntactic check for cases where points-to analysis cannot infer the presence of
- * a protocol constant, e.g. if it has been removed in later versions of the `ssl`
- * library.
- */
-
-bindingset[named_argument]
-predicate probable_insecure_ssl_constant(
- CallNode call, string insecure_version, string named_argument
-) {
- exists(ControlFlowNode arg |
- arg = call.getArgByName(named_argument) or
- arg = call.getArg(0)
- |
- arg.(AttrNode).getObject(insecure_version).pointsTo(the_ssl_module())
- or
- arg.(NameNode).getId() = insecure_version and
- exists(Import imp |
- imp.getAnImportedModuleName() = "ssl" and
- imp.getAName().getAsname().(Name).getId() = insecure_version
- )
- )
-}
-
-predicate unsafe_ssl_wrap_socket_call(
- CallNode call, string method_name, string insecure_version, string named_argument
-) {
- (
- call = ssl_wrap_socket().getACall() and
- method_name = "deprecated method ssl.wrap_socket" and
- named_argument = "ssl_version"
- or
- call = ssl_Context_class().getACall() and
- named_argument = "protocol" and
- method_name = "ssl.SSLContext"
- ) and
- insecure_version = ssl::insecure_version_name() and
- (
- call.getArgByName(named_argument).pointsTo(the_ssl_module().attr(insecure_version))
- or
- probable_insecure_ssl_constant(call, insecure_version, named_argument)
- )
-}
-
-predicate unsafe_pyOpenSSL_Context_call(CallNode call, string insecure_version) {
- call = the_pyOpenSSL_Context_class().getACall() and
- insecure_version = pyOpenSSL::insecure_version_name() and
- call.getArg(0).pointsTo(the_pyOpenSSL_module().attr(insecure_version))
-}
-
-from CallNode call, string method_name, string insecure_version
-where
- unsafe_ssl_wrap_socket_call(call, method_name, insecure_version, _)
+predicate unsafe_connection_creation(DataFlow::Node node, string insecure_version) {
+ exists(AllowsTLSv1 c | c.hasFlowTo(node)) and
+ insecure_version = "TLSv1"
or
- unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context"
-select call,
- "Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name +
- "."
+ exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) and
+ insecure_version = "TLSv1"
+ or
+ exists(TlsLibrary l | l.insecure_connection_creation() = node) and
+ insecure_version = "[multiple]"
+}
+
+predicate unsafe_context_creation(DataFlow::Node node, string insecure_version) {
+ exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation() |
+ cc = node and insecure_version = cc.getProtocol().toString()
+ )
+}
+
+from DataFlow::Node node, string insecure_version
+where
+ unsafe_connection_creation(node, insecure_version)
+ or
+ unsafe_context_creation(node, insecure_version)
+select node, "Insecure SSL/TLS protocol version " + insecure_version //+ " specified in call to " + method_name + "."
+// from CallNode call, string method_name, string insecure_version
+// where
+// unsafe_ssl_wrap_socket_call(call, method_name, insecure_version, _)
+// or
+// unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context"
+// select call,
+// "Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name +
+// "."
From 186db7f43ec254792ca3b0ded440fd0a06080711 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Sun, 21 Feb 2021 12:07:48 +0100
Subject: [PATCH 0015/1429] Python: factor into modules and files
---
.../src/Security/CWE-327/FluentApiModel.qll | 54 ++++
.../src/Security/CWE-327/InsecureProtocol.ql | 268 +-----------------
python/ql/src/Security/CWE-327/PyOpenSSL.qll | 73 +++++
python/ql/src/Security/CWE-327/Ssl.qll | 106 +++++++
.../src/Security/CWE-327/TlsLibraryModel.qll | 80 ++++++
5 files changed, 315 insertions(+), 266 deletions(-)
create mode 100644 python/ql/src/Security/CWE-327/FluentApiModel.qll
create mode 100644 python/ql/src/Security/CWE-327/PyOpenSSL.qll
create mode 100644 python/ql/src/Security/CWE-327/Ssl.qll
create mode 100644 python/ql/src/Security/CWE-327/TlsLibraryModel.qll
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
new file mode 100644
index 00000000000..4c2278b8694
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -0,0 +1,54 @@
+import python
+import TlsLibraryModel
+
+class InsecureContextConfiguration extends DataFlow::Configuration {
+ TlsLibrary library;
+
+ InsecureContextConfiguration() { this = library + ["AllowsTLSv1", "AllowsTLSv1_1"] }
+
+ override predicate isSource(DataFlow::Node source) {
+ source = library.unspecific_context_creation()
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink = library.connection_creation().getContext()
+ }
+
+ abstract string flag();
+
+ override predicate isBarrierOut(DataFlow::Node node) {
+ exists(ProtocolRestriction r |
+ r = library.protocol_restriction() and
+ node = r.getContext() and
+ r.getRestriction() = flag()
+ )
+ }
+}
+
+class AllowsTLSv1 extends InsecureContextConfiguration {
+ AllowsTLSv1() { this = library + "AllowsTLSv1" }
+
+ override string flag() { result = "TLSv1" }
+}
+
+class AllowsTLSv1_1 extends InsecureContextConfiguration {
+ AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" }
+
+ override string flag() { result = "TLSv1_1" }
+}
+
+predicate unsafe_connection_creation(DataFlow::Node node, ProtocolVersion insecure_version) {
+ exists(AllowsTLSv1 c | c.hasFlowTo(node)) and
+ insecure_version = "TLSv1"
+ or
+ exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) and
+ insecure_version = "TLSv1_1"
+ or
+ exists(TlsLibrary l | l.insecure_connection_creation(insecure_version) = node)
+}
+
+predicate unsafe_context_creation(DataFlow::Node node, string insecure_version) {
+ exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation(insecure_version) |
+ cc = node
+ )
+}
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index 81b3558907e..b873e5f3ce8 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -10,277 +10,13 @@
*/
import python
-import semmle.python.ApiGraphs
+import FluentApiModel
+// string foo(ProtocolRestriction r) { result = r.getRestriction() }
// The idea is to track flow from the creation of an insecure context to a use
// such as `wrap_socket`. There should be a data-flow path for each insecure version
// and each path should have a version specific sanitizer. This will allow fluent api
// style code to block the paths one by one.
-//
-// class InsecureContextCreation extends DataFlow::CfgNode {
-// override CallNode node;
-// InsecureContextCreation() {
-// this = API::moduleImport("ssl").getMember("SSLContext").getACall() and
-// insecure_version().asCfgNode() in [node.getArg(0), node.getArgByName("protocol")]
-// }
-// }
-// class InsecureSSLContextCreation extends DataFlow::CfgNode {
-// override CallNode node;
-// InsecureSSLContextCreation() {
-// this = API::moduleImport("ssl").getMember("create_default_context").getACall()
-// or
-// this = API::moduleImport("ssl").getMember("SSLContext").getACall() and
-// API::moduleImport("ssl").getMember("PROTOCOL_TLS").getAUse().asCfgNode() in [
-// node.getArg(0), node.getArgByName("protocol")
-// ]
-// }
-// }
-abstract class ContextCreation extends DataFlow::CfgNode {
- abstract DataFlow::CfgNode getProtocol();
-}
-
-abstract class ConnectionCreation extends DataFlow::CfgNode {
- abstract DataFlow::CfgNode getContext();
-}
-
-class ProtocolRestriction extends DataFlow::CfgNode {
- abstract DataFlow::CfgNode getContext();
-
- abstract string getRestriction();
-}
-
-abstract class TlsLibrary extends string {
- TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
-
- abstract string specific_insecure_version_name();
-
- abstract string unspecific_version_name();
-
- abstract API::Node version_constants();
-
- DataFlow::Node insecure_version() {
- result = version_constants().getMember(specific_insecure_version_name()).getAUse()
- }
-
- DataFlow::Node unspecific_version() {
- result = version_constants().getMember(unspecific_version_name()).getAUse()
- }
-
- abstract DataFlow::CfgNode default_context_creation();
-
- abstract ContextCreation specific_context_creation();
-
- ContextCreation insecure_context_creation() {
- result = specific_context_creation() and
- result.getProtocol() = insecure_version()
- }
-
- DataFlow::CfgNode unspecific_context_creation() {
- result = default_context_creation()
- or
- result = specific_context_creation() and
- result.(ContextCreation).getProtocol() = unspecific_version()
- }
-
- /** A connection is created in an outright insecure manner. */
- abstract DataFlow::CfgNode insecure_connection_creation();
-
- /** A connection is created from a context. */
- abstract ConnectionCreation connection_creation();
-
- abstract ProtocolRestriction protocol_restriction();
-}
-
-module ssl {
- class SSLContextCreation extends ContextCreation {
- override CallNode node;
-
- SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
-
- override DataFlow::CfgNode getProtocol() {
- result.getNode() in [node.getArg(0), node.getArgByName("protocol")]
- }
- }
-
- class WrapSocketCall extends ConnectionCreation {
- override CallNode node;
-
- WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" }
-
- override DataFlow::CfgNode getContext() {
- result.getNode() = node.getFunction().(AttrNode).getObject()
- }
- }
-
- class OptionsAugOr extends ProtocolRestriction {
- string restriction;
-
- OptionsAugOr() {
- exists(AugAssign aa, AttrNode attr |
- aa.getOperation().getOp() instanceof BitOr and
- aa.getTarget() = attr.getNode() and
- attr.getName() = "options" and
- attr.getObject() = node and
- aa.getValue() = API::moduleImport("ssl").getMember(restriction).getAUse().asExpr()
- )
- }
-
- override DataFlow::CfgNode getContext() { result = this }
-
- override string getRestriction() { result = restriction }
- }
-
- class Ssl extends TlsLibrary {
- Ssl() { this = "ssl" }
-
- override string specific_insecure_version_name() {
- result in [
- "PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_TLSv1",
- "PROTOCOL_TLSv1_1"
- ]
- }
-
- override string unspecific_version_name() { result = "PROTOCOL_TLS" }
-
- override API::Node version_constants() { result = API::moduleImport("ssl") }
-
- override DataFlow::CfgNode default_context_creation() {
- result = API::moduleImport("ssl").getMember("create_default_context").getACall()
- }
-
- override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
-
- override DataFlow::CfgNode insecure_connection_creation() {
- result = API::moduleImport("ssl").getMember("wrap_socket").getACall()
- }
-
- override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
-
- override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr }
- }
-}
-
-module pyOpenSSL {
- class PyOpenSSLContextCreation extends ContextCreation {
- override CallNode node;
-
- PyOpenSSLContextCreation() {
- this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Context").getACall()
- }
-
- override DataFlow::CfgNode getProtocol() {
- result.getNode() in [node.getArg(0), node.getArgByName("method")]
- }
- }
-
- class ConnectionCall extends ConnectionCreation {
- override CallNode node;
-
- ConnectionCall() {
- this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Connection").getACall()
- }
-
- override DataFlow::CfgNode getContext() {
- result.getNode() in [node.getArg(0), node.getArgByName("context")]
- }
- }
-
- class SetOptionsCall extends ProtocolRestriction {
- override CallNode node;
-
- SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" }
-
- override DataFlow::CfgNode getContext() {
- result.getNode() = node.getFunction().(AttrNode).getObject()
- }
-
- override string getRestriction() {
- API::moduleImport("PyOpenSSL").getMember("SSL").getMember(result).getAUse().asCfgNode() in [
- node.getArg(0), node.getArgByName("options")
- ]
- }
- }
-
- class PyOpenSSL extends TlsLibrary {
- PyOpenSSL() { this = "pyOpenSSL" }
-
- override string specific_insecure_version_name() {
- result in ["SSLv2_METHOD", "SSLv23_METHOD", "SSLv3_METHOD", "TLSv1_METHOD", "TLSv1_1_METHOD"]
- }
-
- override string unspecific_version_name() { result = "TLS_METHOD" }
-
- override API::Node version_constants() {
- result = API::moduleImport("pyOpenSSL").getMember("SSL")
- }
-
- override DataFlow::CfgNode default_context_creation() { none() }
-
- override ContextCreation specific_context_creation() {
- result instanceof PyOpenSSLContextCreation
- }
-
- override DataFlow::CfgNode insecure_connection_creation() { none() }
-
- override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
-
- override ProtocolRestriction protocol_restriction() { result instanceof SetOptionsCall }
- }
-}
-
-class InsecureContextConfiguration extends DataFlow::Configuration {
- TlsLibrary library;
-
- InsecureContextConfiguration() { this = library + ["AllowsTLSv1", "AllowsTLSv1_1"] }
-
- override predicate isSource(DataFlow::Node source) {
- source = library.unspecific_context_creation()
- }
-
- override predicate isSink(DataFlow::Node sink) {
- sink = library.connection_creation().getContext()
- }
-
- abstract string flag();
-
- override predicate isBarrierOut(DataFlow::Node node) {
- exists(ProtocolRestriction r |
- r = library.protocol_restriction() and
- node = r.getContext() and
- r.getRestriction() = flag()
- )
- }
-}
-
-class AllowsTLSv1 extends InsecureContextConfiguration {
- AllowsTLSv1() { this = library + "AllowsTLSv1" }
-
- override string flag() { result = "OP_NO_TLSv1" }
-}
-
-class AllowsTLSv1_1 extends InsecureContextConfiguration {
- AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" }
-
- override string flag() { result = "OP_NO_TLSv1_1" }
-}
-
-predicate unsafe_connection_creation(DataFlow::Node node, string insecure_version) {
- exists(AllowsTLSv1 c | c.hasFlowTo(node)) and
- insecure_version = "TLSv1"
- or
- exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) and
- insecure_version = "TLSv1"
- or
- exists(TlsLibrary l | l.insecure_connection_creation() = node) and
- insecure_version = "[multiple]"
-}
-
-predicate unsafe_context_creation(DataFlow::Node node, string insecure_version) {
- exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation() |
- cc = node and insecure_version = cc.getProtocol().toString()
- )
-}
-
from DataFlow::Node node, string insecure_version
where
unsafe_connection_creation(node, insecure_version)
diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
new file mode 100644
index 00000000000..b9a68648f93
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
@@ -0,0 +1,73 @@
+import python
+import semmle.python.ApiGraphs
+import TlsLibraryModel
+
+class PyOpenSSLContextCreation extends ContextCreation {
+ override CallNode node;
+
+ PyOpenSSLContextCreation() {
+ this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Context").getACall()
+ }
+
+ override DataFlow::CfgNode getProtocol() {
+ result.getNode() in [node.getArg(0), node.getArgByName("method")]
+ }
+}
+
+class ConnectionCall extends ConnectionCreation {
+ override CallNode node;
+
+ ConnectionCall() {
+ this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Connection").getACall()
+ }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() in [node.getArg(0), node.getArgByName("context")]
+ }
+}
+
+class SetOptionsCall extends ProtocolRestriction {
+ override CallNode node;
+
+ SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() = node.getFunction().(AttrNode).getObject()
+ }
+
+ override ProtocolVersion getRestriction() {
+ API::moduleImport("OpenSSL").getMember("SSL").getMember("OP_NO_" + result).getAUse().asCfgNode() in [
+ node.getArg(0), node.getArgByName("options")
+ ]
+ }
+}
+
+class PyOpenSSL extends TlsLibrary {
+ PyOpenSSL() { this = "pyOpenSSL" }
+
+ override string specific_insecure_version_name(ProtocolVersion version) {
+ version in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] and
+ result = version + "_METHOD"
+ }
+
+ override string unspecific_version_name() {
+ result in [
+ "TLS_METHOD", // This is not actually available in pyOpenSSL yet
+ "SSLv23_METHOD" // This is what can negotiate TLS 1.3 (indeed, I know, I did test that..)
+ ]
+ }
+
+ override API::Node version_constants() { result = API::moduleImport("OpenSSL").getMember("SSL") }
+
+ override DataFlow::CfgNode default_context_creation() { none() }
+
+ override ContextCreation specific_context_creation() {
+ result instanceof PyOpenSSLContextCreation
+ }
+
+ override DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version) { none() }
+
+ override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
+
+ override ProtocolRestriction protocol_restriction() { result instanceof SetOptionsCall }
+}
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
new file mode 100644
index 00000000000..369149fd638
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -0,0 +1,106 @@
+import python
+import semmle.python.ApiGraphs
+import semmle.python.dataflow.new.internal.Attributes as Attributes
+import TlsLibraryModel
+
+class SSLContextCreation extends ContextCreation {
+ override CallNode node;
+
+ SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
+
+ override DataFlow::CfgNode getProtocol() {
+ result.getNode() in [node.getArg(0), node.getArgByName("protocol")]
+ }
+}
+
+class WrapSocketCall extends ConnectionCreation {
+ override CallNode node;
+
+ WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() = node.getFunction().(AttrNode).getObject()
+ }
+}
+
+class OptionsAugOr extends ProtocolRestriction {
+ ProtocolVersion restriction;
+
+ OptionsAugOr() {
+ exists(AugAssign aa, AttrNode attr |
+ aa.getOperation().getOp() instanceof BitOr and
+ aa.getTarget() = attr.getNode() and
+ attr.getName() = "options" and
+ attr.getObject() = node and
+ API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() in [
+ aa.getValue(), aa.getValue().getAChildNode()
+ ]
+ )
+ }
+
+ override DataFlow::CfgNode getContext() { result = this }
+
+ override ProtocolVersion getRestriction() { result = restriction }
+}
+
+class ContextSetVersion extends ProtocolRestriction {
+ string restriction;
+
+ ContextSetVersion() {
+ exists(Attributes::AttrWrite aw |
+ aw.getObject().asCfgNode() = node and
+ aw.getAttributeName() = "minimum_version" and
+ aw.getValue() =
+ API::moduleImport("ssl").getMember("TLSVersion").getMember(restriction).getAUse()
+ )
+ }
+
+ override DataFlow::CfgNode getContext() { result = this }
+
+ override ProtocolVersion getRestriction() { result.lessThan(restriction) }
+}
+
+class Ssl extends TlsLibrary {
+ Ssl() { this = "ssl" }
+
+ override string specific_insecure_version_name(ProtocolVersion version) {
+ version in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] and
+ result = "PROTOCOL_" + version
+ // result in ["PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_TLSv1", "PROTOCOL_TLSv1_1"]
+ }
+
+ override string unspecific_version_name() {
+ result =
+ "PROTOCOL_" +
+ [
+ "TLS",
+ // This can negotiate a TLS 1.3 connection (!)
+ // see https://docs.python.org/3/library/ssl.html#ssl-contexts
+ "SSLv23"
+ ]
+ }
+
+ override API::Node version_constants() { result = API::moduleImport("ssl") }
+
+ override DataFlow::CfgNode default_context_creation() {
+ result = API::moduleImport("ssl").getMember("create_default_context").getACall() //and
+ // see https://docs.python.org/3/library/ssl.html#context-creation
+ // version in ["TLSv1", "TLSv1_1"]
+ }
+
+ override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
+
+ override DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version) {
+ result = API::moduleImport("ssl").getMember("wrap_socket").getACall() and
+ insecure_version(version).asCfgNode() =
+ result.asCfgNode().(CallNode).getArgByName("ssl_version")
+ }
+
+ override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
+
+ override ProtocolRestriction protocol_restriction() {
+ result instanceof OptionsAugOr
+ or
+ result instanceof ContextSetVersion
+ }
+}
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
new file mode 100644
index 00000000000..41db81ad1c8
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -0,0 +1,80 @@
+import python
+import semmle.python.ApiGraphs
+import Ssl
+import PyOpenSSL
+
+/**
+ * A specific protocol version.
+ * We use this to identify a protocol.
+ */
+class ProtocolVersion extends string {
+ ProtocolVersion() { this in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"] }
+
+ predicate lessThan(ProtocolVersion version) {
+ this = "SSLv2" and version = "SSLv3"
+ or
+ this = "TLSv1" and version = ["TLSv1_1", "TLSv1_2", "TLSv1_3"]
+ or
+ this = ["TLSv1", "TLSv1_1"] and version = ["TLSv1_2", "TLSv1_3"]
+ or
+ this = ["TLSv1", "TLSv1_1", "TLSv1_2"] and version = "TLSv1_3"
+ }
+}
+
+abstract class ContextCreation extends DataFlow::CfgNode {
+ abstract DataFlow::CfgNode getProtocol();
+}
+
+abstract class ConnectionCreation extends DataFlow::CfgNode {
+ abstract DataFlow::CfgNode getContext();
+}
+
+abstract class ProtocolRestriction extends DataFlow::CfgNode {
+ abstract DataFlow::CfgNode getContext();
+
+ abstract ProtocolVersion getRestriction();
+}
+
+abstract class TlsLibrary extends string {
+ TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
+
+ /** The name of a specific protocol version, known to be insecure. */
+ abstract string specific_insecure_version_name(ProtocolVersion version);
+
+ /** The name of an unspecific protocol version, say TLS, known to have insecure insatnces. */
+ abstract string unspecific_version_name();
+
+ abstract API::Node version_constants();
+
+ DataFlow::Node insecure_version(ProtocolVersion version) {
+ result = version_constants().getMember(specific_insecure_version_name(version)).getAUse()
+ }
+
+ DataFlow::Node unspecific_version() {
+ result = version_constants().getMember(unspecific_version_name()).getAUse()
+ }
+
+ abstract DataFlow::CfgNode default_context_creation();
+
+ abstract ContextCreation specific_context_creation();
+
+ ContextCreation insecure_context_creation(ProtocolVersion version) {
+ result = specific_context_creation() and
+ result.getProtocol() = insecure_version(version)
+ }
+
+ DataFlow::CfgNode unspecific_context_creation() {
+ result = default_context_creation()
+ or
+ result = specific_context_creation() and
+ result.(ContextCreation).getProtocol() = unspecific_version()
+ }
+
+ /** A connection is created in an outright insecure manner. */
+ abstract DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version);
+
+ /** A connection is created from a context. */
+ abstract ConnectionCreation connection_creation();
+
+ abstract ProtocolRestriction protocol_restriction();
+}
From 87e1a062ea750a3b2acc95d75583099c055defe9 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Sun, 21 Feb 2021 12:08:11 +0100
Subject: [PATCH 0016/1429] Python: fluent api tests
---
.../Security/CWE-327/pyOpenSSL_fluent.py | 31 +++++++
.../Security/CWE-327/ssl_fluent.py | 87 +++++++++++++++++++
2 files changed, 118 insertions(+)
create mode 100644 python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
create mode 100644 python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
diff --git a/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py b/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
new file mode 100644
index 00000000000..028c318be66
--- /dev/null
+++ b/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
@@ -0,0 +1,31 @@
+import socket
+from OpenSSL import SSL
+
+def test_fluent():
+ hostname = 'www.python.org'
+ context = SSL.Context(SSL.SSLv23_METHOD)
+
+ conn = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
+ r = conn.connect((hostname, 443))
+ print(conn.get_protocol_version_name())
+
+
+def test_fluent_no_TLSv1():
+ hostname = 'www.python.org'
+ context = SSL.Context(SSL.SSLv23_METHOD)
+ context.set_options(SSL.OP_NO_TLSv1)
+
+ conn = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
+ r = conn.connect((hostname, 443))
+ print(conn.get_protocol_version_name())
+
+
+def test_fluent_safe():
+ hostname = 'www.python.org'
+ context = SSL.Context(SSL.SSLv23_METHOD)
+ context.set_options(SSL.OP_NO_TLSv1)
+ context.set_options(SSL.OP_NO_TLSv1_1)
+
+ conn = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
+ r = conn.connect((hostname, 443))
+ print(r, conn.get_protocol_version_name())
diff --git a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
new file mode 100644
index 00000000000..9f9e01294cb
--- /dev/null
+++ b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
@@ -0,0 +1,87 @@
+import socket
+import ssl
+
+def test_fluent_tls():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+
+def test_fluent_tls_no_TLSv1():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+ context.options |= ssl.OP_NO_TLSv1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_tls_safe():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+ context.options |= ssl.OP_NO_TLSv1
+ context.options |= ssl.OP_NO_TLSv1_1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_ssl():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+
+def test_fluent_ssl_no_TLSv1():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_ssl_safe():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1
+ context.options |= ssl.OP_NO_TLSv1_1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_ssl_safe_combined():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+# From Python 3.7
+# see https://docs.python.org/3/library/ssl.html#ssl.SSLContext.minimum_version
+def test_fluent_ssl_unsafe_version():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+ context.minimum_version = ssl.TLSVersion.TLSv1_1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_ssl_safe_version():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+ context.minimum_version = ssl.TLSVersion.TLSv1_3
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
From ea8c6f04e2ba137b6501152f22b93f642e17b372 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Sun, 21 Feb 2021 12:09:56 +0100
Subject: [PATCH 0017/1429] Python: Update old test and qlhelp
---
.../Security/CWE-327/InsecureProtocol.qhelp | 6 ++---
python/ql/src/Security/CWE-327/ReadMe.md | 24 +++++++++++++++++++
.../Security/CWE-327/InsecureProtocol.py | 8 +++----
3 files changed, 31 insertions(+), 7 deletions(-)
create mode 100644 python/ql/src/Security/CWE-327/ReadMe.md
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp b/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp
index 63545129cc7..4a7364ca14e 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp
@@ -13,8 +13,8 @@
Ensure that a modern, strong protocol is used. All versions of SSL,
- and TLS 1.0 are known to be vulnerable to attacks. Using TLS 1.1 or
- above is strongly recommended.
+ and TLS versions 1.0 and 1.1 are known to be vulnerable to attacks.
+ Using TLS 1.2 or above is strongly recommended.
@@ -30,7 +30,7 @@
All cases should be updated to use a secure protocol, such as
- PROTOCOL_TLSv1_1.
+ PROTOCOL_TLSv1_2.
Note that ssl.wrap_socket has been deprecated in
diff --git a/python/ql/src/Security/CWE-327/ReadMe.md b/python/ql/src/Security/CWE-327/ReadMe.md
new file mode 100644
index 00000000000..c5330a78fda
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/ReadMe.md
@@ -0,0 +1,24 @@
+# Current status (Feb 2021)
+
+This should be kept up to date; the world is moving fast and protocols are being broken.
+
+## Protocols
+
+- All versions of SSL are insecure
+- TLS 1.0 and TLS 1.1 are insecure
+- TLS 1.2 have some issues. but TLS 1.3 is not widely supported
+
+## Conection methods
+
+- `ssl.wrap_socket` is creating insecure connections, use `SSLContext.wrap_socket` instead. [link](https://docs.python.org/3/library/ssl.html#ssl.wrap_socket)
+ > Deprecated since version 3.7: Since Python 3.2 and 2.7.9, it is recommended to use the `SSLContext.wrap_socket()` instead of `wrap_socket()`. The top-level function is limited and creates an insecure client socket without server name indication or hostname matching.
+- Default consteructors are fine, a sluent api is used to constrain possible protocols later.
+
+## Current recomendation
+
+TLS 1.2 or TLS 1.3
+
+## Queries
+
+- `InsecureProtocol` detects uses of insecure protocols.
+- `InsecureDefaultProtocol` detect default constructions, this is no longer unsafe.
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
index 5a6d044aa6d..cb21a6623c9 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
@@ -1,5 +1,5 @@
import ssl
-from pyOpenSSL import SSL
+from OpenSSL import SSL
from ssl import SSLContext
# true positives
@@ -33,9 +33,9 @@ SSL.Context(METHOD)
# secure versions
-ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_1)
-SSLContext(protocol=ssl.PROTOCOL_TLSv1_1)
-SSL.Context(SSL.TLSv1_1_METHOD)
+ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_2)
+SSLContext(protocol=ssl.PROTOCOL_TLSv1_2)
+SSL.Context(SSL.TLSv1_2_METHOD)
# possibly insecure default
ssl.wrap_socket()
From 3b856010f28ade83481b3e9fe553c4bb56f0f432 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 23 Feb 2021 19:29:30 +0100
Subject: [PATCH 0018/1429] Python: add TODO comment
---
python/ql/src/Security/CWE-327/Ssl.qll | 1 +
1 file changed, 1 insertion(+)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 369149fd638..e3247d5143c 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -32,6 +32,7 @@ class OptionsAugOr extends ProtocolRestriction {
aa.getTarget() = attr.getNode() and
attr.getName() = "options" and
attr.getObject() = node and
+ // TODO: Use something like BoolExpr::impliesValue here
API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() in [
aa.getValue(), aa.getValue().getAChildNode()
]
From d5171fc043cd4a66372780bcbe5c758cd7c594e2 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Feb 2021 21:12:34 +0100
Subject: [PATCH 0019/1429] Python: Comment everything
---
.../src/Security/CWE-327/FluentApiModel.qll | 15 +++++++++++++
.../src/Security/CWE-327/InsecureProtocol.ql | 13 ------------
python/ql/src/Security/CWE-327/PyOpenSSL.qll | 2 +-
python/ql/src/Security/CWE-327/Ssl.qll | 17 ++++++++++-----
.../src/Security/CWE-327/TlsLibraryModel.qll | 21 ++++++++++++++++---
5 files changed, 46 insertions(+), 22 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 4c2278b8694..3dc12e2f434 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -1,6 +1,11 @@
import python
import TlsLibraryModel
+/**
+ * Configuration to track flow from the creation of a context to
+ * that context being used to create a connection.
+ * Flow is broken if the insecure protocol of interest is being restricted.
+ */
class InsecureContextConfiguration extends DataFlow::Configuration {
TlsLibrary library;
@@ -25,28 +30,38 @@ class InsecureContextConfiguration extends DataFlow::Configuration {
}
}
+/** Configuration to specifically track the insecure protocol TLS 1.0 */
class AllowsTLSv1 extends InsecureContextConfiguration {
AllowsTLSv1() { this = library + "AllowsTLSv1" }
override string flag() { result = "TLSv1" }
}
+/** Configuration to specifically track the insecure protocol TLS 1.1 */
class AllowsTLSv1_1 extends InsecureContextConfiguration {
AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" }
override string flag() { result = "TLSv1_1" }
}
+/**
+ * A connection is created from a context allowing an insecure protocol,
+ * and that protocol has not been restricted appropriately.
+ */
predicate unsafe_connection_creation(DataFlow::Node node, ProtocolVersion insecure_version) {
+ // Connection created from a context allowing TLS 1.0.
exists(AllowsTLSv1 c | c.hasFlowTo(node)) and
insecure_version = "TLSv1"
or
+ // Connection created from a context allowing TLS 1.1.
exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) and
insecure_version = "TLSv1_1"
or
+ // Connection created from a context for an insecure protocol.
exists(TlsLibrary l | l.insecure_connection_creation(insecure_version) = node)
}
+/** A connection is created insecurely without reference to a context. */
predicate unsafe_context_creation(DataFlow::Node node, string insecure_version) {
exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation(insecure_version) |
cc = node
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index b873e5f3ce8..c902d9a284d 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -12,22 +12,9 @@
import python
import FluentApiModel
-// string foo(ProtocolRestriction r) { result = r.getRestriction() }
-// The idea is to track flow from the creation of an insecure context to a use
-// such as `wrap_socket`. There should be a data-flow path for each insecure version
-// and each path should have a version specific sanitizer. This will allow fluent api
-// style code to block the paths one by one.
from DataFlow::Node node, string insecure_version
where
unsafe_connection_creation(node, insecure_version)
or
unsafe_context_creation(node, insecure_version)
select node, "Insecure SSL/TLS protocol version " + insecure_version //+ " specified in call to " + method_name + "."
-// from CallNode call, string method_name, string insecure_version
-// where
-// unsafe_ssl_wrap_socket_call(call, method_name, insecure_version, _)
-// or
-// unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context"
-// select call,
-// "Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name +
-// "."
diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
index b9a68648f93..a89c7ff0886 100644
--- a/python/ql/src/Security/CWE-327/PyOpenSSL.qll
+++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
@@ -59,7 +59,7 @@ class PyOpenSSL extends TlsLibrary {
override API::Node version_constants() { result = API::moduleImport("OpenSSL").getMember("SSL") }
- override DataFlow::CfgNode default_context_creation() { none() }
+ override ContextCreation default_context_creation() { none() }
override ContextCreation specific_context_creation() {
result instanceof PyOpenSSLContextCreation
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index e3247d5143c..ba91b39bdeb 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -13,6 +13,16 @@ class SSLContextCreation extends ContextCreation {
}
}
+class SSLDefaultContextCreation extends ContextCreation {
+ SSLDefaultContextCreation() {
+ this = API::moduleImport("ssl").getMember("create_default_context").getACall()
+ }
+
+ // Allowed insecure versions are "TLSv1" and "TLSv1_1"
+ // see https://docs.python.org/3/library/ssl.html#context-creation
+ override DataFlow::CfgNode getProtocol() { none() }
+}
+
class WrapSocketCall extends ConnectionCreation {
override CallNode node;
@@ -67,7 +77,6 @@ class Ssl extends TlsLibrary {
override string specific_insecure_version_name(ProtocolVersion version) {
version in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] and
result = "PROTOCOL_" + version
- // result in ["PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_TLSv1", "PROTOCOL_TLSv1_1"]
}
override string unspecific_version_name() {
@@ -83,10 +92,8 @@ class Ssl extends TlsLibrary {
override API::Node version_constants() { result = API::moduleImport("ssl") }
- override DataFlow::CfgNode default_context_creation() {
- result = API::moduleImport("ssl").getMember("create_default_context").getACall() //and
- // see https://docs.python.org/3/library/ssl.html#context-creation
- // version in ["TLSv1", "TLSv1_1"]
+ override ContextCreation default_context_creation() {
+ result instanceof SSLDefaultContextCreation
}
override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
index 41db81ad1c8..36e58acd926 100644
--- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -21,17 +21,24 @@ class ProtocolVersion extends string {
}
}
+/** The creation of a context. */
abstract class ContextCreation extends DataFlow::CfgNode {
+ /** Gets the requested protocol if any. */
abstract DataFlow::CfgNode getProtocol();
}
+/** The creation of a connection from a context. */
abstract class ConnectionCreation extends DataFlow::CfgNode {
+ /** Gets the context used to create the connection. */
abstract DataFlow::CfgNode getContext();
}
+/** A context is being restricted on which protocols it can accepts. */
abstract class ProtocolRestriction extends DataFlow::CfgNode {
+ /** Gets the context being restricted. */
abstract DataFlow::CfgNode getContext();
+ /** Gets the protocol version being disallowed. */
abstract ProtocolVersion getRestriction();
}
@@ -41,28 +48,35 @@ abstract class TlsLibrary extends string {
/** The name of a specific protocol version, known to be insecure. */
abstract string specific_insecure_version_name(ProtocolVersion version);
- /** The name of an unspecific protocol version, say TLS, known to have insecure insatnces. */
+ /** The name of an unspecific protocol version, say TLS, known to have insecure instances. */
abstract string unspecific_version_name();
+ /** The module or class holding the version constants. */
abstract API::Node version_constants();
+ /** A dataflow node representing a specific protocol version, known to be insecure. */
DataFlow::Node insecure_version(ProtocolVersion version) {
result = version_constants().getMember(specific_insecure_version_name(version)).getAUse()
}
+ /** A dataflow node representing an unspecific protocol version, say TLS, known to have insecure instances. */
DataFlow::Node unspecific_version() {
result = version_constants().getMember(unspecific_version_name()).getAUse()
}
- abstract DataFlow::CfgNode default_context_creation();
+ /** The creation of a context with a deafult protocol. */
+ abstract ContextCreation default_context_creation();
+ /** The creation of a context with a specific protocol. */
abstract ContextCreation specific_context_creation();
+ /** The creation of a context with a specific protocol version, known to be insecure. */
ContextCreation insecure_context_creation(ProtocolVersion version) {
result = specific_context_creation() and
result.getProtocol() = insecure_version(version)
}
+ /** The creation of a context with an unspecific protocol version, say TLS, known to have insecure instances. */
DataFlow::CfgNode unspecific_context_creation() {
result = default_context_creation()
or
@@ -70,11 +84,12 @@ abstract class TlsLibrary extends string {
result.(ContextCreation).getProtocol() = unspecific_version()
}
- /** A connection is created in an outright insecure manner. */
+ /** A connection is created in an insecure manner, not from a context. */
abstract DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version);
/** A connection is created from a context. */
abstract ConnectionCreation connection_creation();
+ /** A context is being restricted on which protocols it can accepts. */
abstract ProtocolRestriction protocol_restriction();
}
From 9e696ff0fbaac008a12bfa1a7241a0f0140bbc9d Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Feb 2021 21:58:00 +0100
Subject: [PATCH 0020/1429] Python: Add false negative to test
---
.../ql/test/query-tests/Security/CWE-327/ssl_fluent.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
index 9f9e01294cb..252ab0a9e49 100644
--- a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
@@ -85,3 +85,13 @@ def test_fluent_ssl_safe_version():
with socket.create_connection((hostname, 443)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
print(ssock.version())
+
+# Taken from https://docs.python.org/3/library/ssl.html#context-creation
+def test_fluent_explicitly_unsafe():
+ hostname = 'www.python.org'
+ context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+ context.options &= ~ssl.OP_NO_SSLv3 # This not recognized
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock: # SSLv3 not flagged here
+ print(ssock.version())
From 60525ec3018924898d47d72d133cb0b1431a2bea Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Feb 2021 21:58:30 +0100
Subject: [PATCH 0021/1429] Python: Also track offending call update test
expectations at this point
---
.../src/Security/CWE-327/FluentApiModel.qll | 20 +++++++---
.../src/Security/CWE-327/InsecureProtocol.ql | 18 ++++++---
.../CWE-327/InsecureProtocol.expected | 38 ++++++++++++-------
3 files changed, 51 insertions(+), 25 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 3dc12e2f434..23922a90595 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -48,22 +48,30 @@ class AllowsTLSv1_1 extends InsecureContextConfiguration {
* A connection is created from a context allowing an insecure protocol,
* and that protocol has not been restricted appropriately.
*/
-predicate unsafe_connection_creation(DataFlow::Node node, ProtocolVersion insecure_version) {
+predicate unsafe_connection_creation(
+ DataFlow::Node node, ProtocolVersion insecure_version, CallNode call
+) {
// Connection created from a context allowing TLS 1.0.
- exists(AllowsTLSv1 c | c.hasFlowTo(node)) and
+ exists(AllowsTLSv1 c, ContextCreation cc | c.hasFlow(cc, node) | cc.getNode() = call) and
insecure_version = "TLSv1"
or
// Connection created from a context allowing TLS 1.1.
- exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) and
+ exists(AllowsTLSv1_1 c, ContextCreation cc | c.hasFlow(cc, node) | cc.getNode() = call) and
insecure_version = "TLSv1_1"
or
// Connection created from a context for an insecure protocol.
- exists(TlsLibrary l | l.insecure_connection_creation(insecure_version) = node)
+ exists(TlsLibrary l, DataFlow::CfgNode cc |
+ cc = l.insecure_connection_creation(insecure_version)
+ |
+ cc = node and
+ cc.getNode() = call
+ )
}
/** A connection is created insecurely without reference to a context. */
-predicate unsafe_context_creation(DataFlow::Node node, string insecure_version) {
+predicate unsafe_context_creation(DataFlow::Node node, string insecure_version, CallNode call) {
exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation(insecure_version) |
- cc = node
+ cc = node and
+ cc.getNode() = call
)
}
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index c902d9a284d..51247dbd75a 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -12,9 +12,17 @@
import python
import FluentApiModel
-from DataFlow::Node node, string insecure_version
-where
- unsafe_connection_creation(node, insecure_version)
+string callName(AstNode call) {
+ result = call.(Name).getId()
or
- unsafe_context_creation(node, insecure_version)
-select node, "Insecure SSL/TLS protocol version " + insecure_version //+ " specified in call to " + method_name + "."
+ exists(Attribute a | a = call | result = callName(a.getObject()) + "." + a.getName())
+}
+
+from DataFlow::Node node, string insecure_version, CallNode call
+where
+ unsafe_connection_creation(node, insecure_version, call)
+ or
+ unsafe_context_creation(node, insecure_version, call)
+select node, "Insecure SSL/TLS protocol version " + insecure_version + " specified in $@ ", call,
+ "call to " + callName(call.getFunction().getNode())
+//+ " specified in call to " + method_name + "."
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index 33542e4a048..7b646f9fd3e 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -1,14 +1,24 @@
-| InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to deprecated method ssl.wrap_socket. |
-| InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version PROTOCOL_SSLv3 specified in call to deprecated method ssl.wrap_socket. |
-| InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version PROTOCOL_TLSv1 specified in call to deprecated method ssl.wrap_socket. |
-| InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to ssl.SSLContext. |
-| InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_SSLv3 specified in call to ssl.SSLContext. |
-| InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_TLSv1 specified in call to ssl.SSLContext. |
-| InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2_METHOD specified in call to pyOpenSSL.SSL.Context. |
-| InsecureProtocol.py:15:1:15:30 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv23_METHOD specified in call to pyOpenSSL.SSL.Context. |
-| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3_METHOD specified in call to pyOpenSSL.SSL.Context. |
-| InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1_METHOD specified in call to pyOpenSSL.SSL.Context. |
-| InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2_METHOD specified in call to pyOpenSSL.SSL.Context. |
-| InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to deprecated method ssl.wrap_socket. |
-| InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to ssl.SSLContext. |
-| InsecureProtocol.py:52:1:52:33 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv23_METHOD specified in call to ssl.SSLContext. |
+| InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified in $@ | InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified in $@ | InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv3 specified in $@ | InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version TLSv1 specified in $@ | InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified in $@ | InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified in $@ | InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | call to SSLContext |
+| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:19:14:19:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:15:15:15:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:47:14:47:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:43:15:43:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:77:14:77:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:73:15:73:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:96:14:96:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:92:15:92:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| ssl_fluent.py:96:14:96:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:92:15:92:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
From 7a1d953fcac8d159440345dd26dd13b4136f1481 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 2 Mar 2021 22:46:01 +0100
Subject: [PATCH 0022/1429] Python: More tests
---
.../CWE-327/InsecureProtocol.expected | 14 ++-
.../Security/CWE-327/ssl_fluent.py | 96 +++++++++++++++++++
2 files changed, 107 insertions(+), 3 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index 7b646f9fd3e..e116662ce65 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -19,6 +19,14 @@
| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:47:14:47:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:43:15:43:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:77:14:77:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:73:15:73:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:96:14:96:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:92:15:92:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
-| ssl_fluent.py:96:14:96:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:92:15:92:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:173:14:173:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:169:15:169:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:188:15:188:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:188:15:188:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
diff --git a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
index 252ab0a9e49..eceacaebe21 100644
--- a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
@@ -66,6 +66,102 @@ def test_fluent_ssl_safe_combined():
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
print(ssock.version())
+def test_fluent_ssl_unsafe_combined_wrongly():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 & ssl.OP_NO_TLSv1_1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_ssl_safe_combined_multiple():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+
+def create_relaxed_context():
+ return ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+
+def create_secure_context():
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+ return context
+
+def create_connection(context):
+ with socket.create_connection(('www.python.org', 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_delegated_context_unsafe():
+ context = create_relaxed_context()
+ with socket.create_connection(('www.python.org', 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_delegated_context_safe():
+ context = create_secure_context()
+ with socket.create_connection(('www.python.org', 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_delegated_context_made_safe():
+ context = create_relaxed_context()
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+ with socket.create_connection(('www.python.org', 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_delegated_context_made_unsafe():
+ context = create_secure_context()
+ context.options &= ~ssl.OP_NO_TLSv1_1
+ with socket.create_connection(('www.python.org', 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_delegated_connection_unsafe():
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ create_connection(context)
+
+def test_delegated_connection_safe():
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+ create_connection(context)
+
+def test_delegated_connection_made_safe():
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+ create_connection(context)
+
+def test_delegated_connection_made_unsafe():
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+ context.options &= ~ssl.OP_NO_TLSv1_1
+ create_connection(context)
+
+def test_delegated_unsafe():
+ context = create_relaxed_context()
+ create_connection(context)
+
+def test_delegated_safe():
+ context = create_secure_context()
+ create_connection(context)
+
+def test_delegated_made_safe():
+ context = create_relaxed_context()
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+ create_connection(context)
+
+def test_delegated_made_unsafe():
+ context = create_secure_context()
+ context.options &= ~ssl.OP_NO_TLSv1_1
+ create_connection(context)
+
# From Python 3.7
# see https://docs.python.org/3/library/ssl.html#ssl.SSLContext.minimum_version
def test_fluent_ssl_unsafe_version():
From 97d26687fed6b102c0c2be075ec6a0350a9ef246 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 1 Mar 2021 15:04:48 +0100
Subject: [PATCH 0023/1429] Python: Improve logic of bit fields
---
python/ql/src/Security/CWE-327/Ssl.qll | 31 +++++++++++++++++++++-----
1 file changed, 26 insertions(+), 5 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index ba91b39bdeb..4caa0ae7302 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -37,15 +37,17 @@ class OptionsAugOr extends ProtocolRestriction {
ProtocolVersion restriction;
OptionsAugOr() {
- exists(AugAssign aa, AttrNode attr |
+ exists(AugAssign aa, AttrNode attr, Expr flag |
aa.getOperation().getOp() instanceof BitOr and
aa.getTarget() = attr.getNode() and
attr.getName() = "options" and
attr.getObject() = node and
- // TODO: Use something like BoolExpr::impliesValue here
- API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() in [
- aa.getValue(), aa.getValue().getAChildNode()
- ]
+ flag = API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() and
+ (
+ aa.getValue() = flag
+ or
+ impliesValue(aa.getValue(), flag, false, false)
+ )
)
}
@@ -54,6 +56,25 @@ class OptionsAugOr extends ProtocolRestriction {
override ProtocolVersion getRestriction() { result = restriction }
}
+/** Whether `part` evaluates to `partIsTrue` if `whole` evaluates to `wholeIsTrue`. */
+predicate impliesValue(BinaryExpr whole, Expr part, boolean partIsTrue, boolean wholeIsTrue) {
+ whole.getOp() instanceof BitAnd and
+ (
+ wholeIsTrue = true and partIsTrue = true and part in [whole.getLeft(), whole.getRight()]
+ or
+ wholeIsTrue = true and
+ impliesValue([whole.getLeft(), whole.getRight()], part, partIsTrue, wholeIsTrue)
+ )
+ or
+ whole.getOp() instanceof BitOr and
+ (
+ wholeIsTrue = false and partIsTrue = false and part in [whole.getLeft(), whole.getRight()]
+ or
+ wholeIsTrue = false and
+ impliesValue([whole.getLeft(), whole.getRight()], part, partIsTrue, wholeIsTrue)
+ )
+}
+
class ContextSetVersion extends ProtocolRestriction {
string restriction;
From cbbc7b2bcd160bdfc268252644f35daf4c1bcfd9 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 3 Mar 2021 23:42:48 +0100
Subject: [PATCH 0024/1429] Python: support unrestrictions Also pyOpenSSL
allows SSL 2 and SSL 3 on `SSLv23`
---
.../src/Security/CWE-327/FluentApiModel.qll | 62 ++++++++--------
.../src/Security/CWE-327/InsecureProtocol.ql | 28 ++++++--
python/ql/src/Security/CWE-327/PyOpenSSL.qll | 18 +++--
python/ql/src/Security/CWE-327/Ssl.qll | 63 ++++++++++++----
.../src/Security/CWE-327/TlsLibraryModel.qll | 48 +++++++++++--
.../CWE-327/InsecureProtocol.expected | 72 ++++++++++---------
6 files changed, 201 insertions(+), 90 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 23922a90595..d222af70499 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -8,40 +8,44 @@ import TlsLibraryModel
*/
class InsecureContextConfiguration extends DataFlow::Configuration {
TlsLibrary library;
+ ProtocolVersion tracked_version;
- InsecureContextConfiguration() { this = library + ["AllowsTLSv1", "AllowsTLSv1_1"] }
+ InsecureContextConfiguration() {
+ this = library + "Allows" + tracked_version and
+ tracked_version.isInsecure()
+ }
+
+ ProtocolVersion getTrackedVersion() { result = tracked_version }
override predicate isSource(DataFlow::Node source) {
- source = library.unspecific_context_creation()
+ // source = library.unspecific_context_creation()
+ exists(ProtocolUnrestriction pu |
+ pu = library.protocol_unrestriction() and
+ pu.getUnrestriction() = tracked_version
+ |
+ source = pu.getContext()
+ )
}
override predicate isSink(DataFlow::Node sink) {
sink = library.connection_creation().getContext()
}
- abstract string flag();
-
override predicate isBarrierOut(DataFlow::Node node) {
exists(ProtocolRestriction r |
r = library.protocol_restriction() and
node = r.getContext() and
- r.getRestriction() = flag()
+ r.getRestriction() = tracked_version
)
}
-}
-/** Configuration to specifically track the insecure protocol TLS 1.0 */
-class AllowsTLSv1 extends InsecureContextConfiguration {
- AllowsTLSv1() { this = library + "AllowsTLSv1" }
-
- override string flag() { result = "TLSv1" }
-}
-
-/** Configuration to specifically track the insecure protocol TLS 1.1 */
-class AllowsTLSv1_1 extends InsecureContextConfiguration {
- AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" }
-
- override string flag() { result = "TLSv1_1" }
+ override predicate isBarrierIn(DataFlow::Node node) {
+ exists(ProtocolUnrestriction r |
+ r = library.protocol_unrestriction() and
+ node = r.getContext() and
+ r.getUnrestriction() = tracked_version
+ )
+ }
}
/**
@@ -49,22 +53,22 @@ class AllowsTLSv1_1 extends InsecureContextConfiguration {
* and that protocol has not been restricted appropriately.
*/
predicate unsafe_connection_creation(
- DataFlow::Node node, ProtocolVersion insecure_version, CallNode call
+ DataFlow::Node creation, ProtocolVersion insecure_version, DataFlow::Node source, boolean specific
) {
- // Connection created from a context allowing TLS 1.0.
- exists(AllowsTLSv1 c, ContextCreation cc | c.hasFlow(cc, node) | cc.getNode() = call) and
- insecure_version = "TLSv1"
+ // Connection created from a context allowing `insecure_version`.
+ exists(InsecureContextConfiguration c, ProtocolUnrestriction cc | c.hasFlow(cc, creation) |
+ insecure_version = c.getTrackedVersion() and
+ source = cc and
+ specific = false
+ )
or
- // Connection created from a context allowing TLS 1.1.
- exists(AllowsTLSv1_1 c, ContextCreation cc | c.hasFlow(cc, node) | cc.getNode() = call) and
- insecure_version = "TLSv1_1"
- or
- // Connection created from a context for an insecure protocol.
+ // Connection created from a context specifying `insecure_version`.
exists(TlsLibrary l, DataFlow::CfgNode cc |
cc = l.insecure_connection_creation(insecure_version)
|
- cc = node and
- cc.getNode() = call
+ creation = cc and
+ source = cc and
+ specific = true
)
}
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index 51247dbd75a..194cc1f5ec1 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -18,11 +18,25 @@ string callName(AstNode call) {
exists(Attribute a | a = call | result = callName(a.getObject()) + "." + a.getName())
}
-from DataFlow::Node node, string insecure_version, CallNode call
-where
- unsafe_connection_creation(node, insecure_version, call)
+string sourceName(DataFlow::Node source) {
+ result = "call to " + callName(source.asCfgNode().(CallNode).getFunction().getNode())
or
- unsafe_context_creation(node, insecure_version, call)
-select node, "Insecure SSL/TLS protocol version " + insecure_version + " specified in $@ ", call,
- "call to " + callName(call.getFunction().getNode())
-//+ " specified in call to " + method_name + "."
+ not source.asCfgNode() instanceof CallNode and
+ not source instanceof ContextCreation and
+ result = "context modification"
+}
+
+string verb(boolean specific) {
+ specific = true and result = "specified"
+ or
+ specific = false and result = "allowed"
+}
+
+from DataFlow::Node creation, string insecure_version, DataFlow::Node source, boolean specific
+where
+ unsafe_connection_creation(creation, insecure_version, source, specific)
+ or
+ unsafe_context_creation(creation, insecure_version, source.asCfgNode()) and specific = true
+select creation,
+ "Insecure SSL/TLS protocol version " + insecure_version + " " + verb(specific) + " by $@ ",
+ source, sourceName(source)
diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
index a89c7ff0886..3c36c568ef5 100644
--- a/python/ql/src/Security/CWE-327/PyOpenSSL.qll
+++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
@@ -26,6 +26,8 @@ class ConnectionCall extends ConnectionCreation {
}
}
+// This cannot be used to unrestrict,
+// see https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_options
class SetOptionsCall extends ProtocolRestriction {
override CallNode node;
@@ -42,6 +44,10 @@ class SetOptionsCall extends ProtocolRestriction {
}
}
+class UnspecificPyOpenSSLContextCreation extends PyOpenSSLContextCreation, UnspecificContextCreation {
+ UnspecificPyOpenSSLContextCreation() { library = "pyOpenSSL" }
+}
+
class PyOpenSSL extends TlsLibrary {
PyOpenSSL() { this = "pyOpenSSL" }
@@ -50,11 +56,9 @@ class PyOpenSSL extends TlsLibrary {
result = version + "_METHOD"
}
- override string unspecific_version_name() {
- result in [
- "TLS_METHOD", // This is not actually available in pyOpenSSL yet
- "SSLv23_METHOD" // This is what can negotiate TLS 1.3 (indeed, I know, I did test that..)
- ]
+ override string unspecific_version_name(ProtocolFamily family) {
+ // `"TLS_METHOD"` is not actually available in pyOpenSSL yet, but should be coming soon..
+ result = family + "_METHOD"
}
override API::Node version_constants() { result = API::moduleImport("OpenSSL").getMember("SSL") }
@@ -70,4 +74,8 @@ class PyOpenSSL extends TlsLibrary {
override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
override ProtocolRestriction protocol_restriction() { result instanceof SetOptionsCall }
+
+ override ProtocolUnrestriction protocol_unrestriction() {
+ result instanceof UnspecificPyOpenSSLContextCreation
+ }
}
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 4caa0ae7302..e749bb9bc3c 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -56,6 +56,31 @@ class OptionsAugOr extends ProtocolRestriction {
override ProtocolVersion getRestriction() { result = restriction }
}
+class OptionsAugAndNot extends ProtocolUnrestriction {
+ ProtocolVersion restriction;
+
+ OptionsAugAndNot() {
+ exists(AugAssign aa, AttrNode attr, Expr flag, UnaryExpr notFlag |
+ aa.getOperation().getOp() instanceof BitAnd and
+ aa.getTarget() = attr.getNode() and
+ attr.getName() = "options" and
+ attr.getObject() = node and
+ notFlag.getOp() instanceof Invert and
+ notFlag.getOperand() = flag and
+ flag = API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() and
+ (
+ aa.getValue() = notFlag
+ or
+ impliesValue(aa.getValue(), notFlag, true, true)
+ )
+ )
+ }
+
+ override DataFlow::CfgNode getContext() { result = this }
+
+ override ProtocolVersion getUnrestriction() { result = restriction }
+}
+
/** Whether `part` evaluates to `partIsTrue` if `whole` evaluates to `wholeIsTrue`. */
predicate impliesValue(BinaryExpr whole, Expr part, boolean partIsTrue, boolean wholeIsTrue) {
whole.getOp() instanceof BitAnd and
@@ -75,8 +100,8 @@ predicate impliesValue(BinaryExpr whole, Expr part, boolean partIsTrue, boolean
)
}
-class ContextSetVersion extends ProtocolRestriction {
- string restriction;
+class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction {
+ ProtocolVersion restriction;
ContextSetVersion() {
exists(Attributes::AttrWrite aw |
@@ -90,6 +115,21 @@ class ContextSetVersion extends ProtocolRestriction {
override DataFlow::CfgNode getContext() { result = this }
override ProtocolVersion getRestriction() { result.lessThan(restriction) }
+
+ override ProtocolVersion getUnrestriction() {
+ restriction = result or restriction.lessThan(result)
+ }
+}
+
+class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContextCreation {
+ UnspecificSSLContextCreation() { library = "ssl" }
+
+ override ProtocolVersion getUnrestriction() {
+ result = UnspecificContextCreation.super.getUnrestriction() and
+ // These are turned off by default
+ // see https://docs.python.org/3/library/ssl.html#ssl-contexts
+ not result in ["SSLv2", "SSLv3"]
+ }
}
class Ssl extends TlsLibrary {
@@ -100,16 +140,7 @@ class Ssl extends TlsLibrary {
result = "PROTOCOL_" + version
}
- override string unspecific_version_name() {
- result =
- "PROTOCOL_" +
- [
- "TLS",
- // This can negotiate a TLS 1.3 connection (!)
- // see https://docs.python.org/3/library/ssl.html#ssl-contexts
- "SSLv23"
- ]
- }
+ override string unspecific_version_name(ProtocolFamily family) { result = "PROTOCOL_" + family }
override API::Node version_constants() { result = API::moduleImport("ssl") }
@@ -132,4 +163,12 @@ class Ssl extends TlsLibrary {
or
result instanceof ContextSetVersion
}
+
+ override ProtocolUnrestriction protocol_unrestriction() {
+ result instanceof OptionsAugAndNot
+ or
+ result instanceof ContextSetVersion
+ or
+ result instanceof UnspecificSSLContextCreation
+ }
}
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
index 36e58acd926..97ceec00688 100644
--- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -19,6 +19,13 @@ class ProtocolVersion extends string {
or
this = ["TLSv1", "TLSv1_1", "TLSv1_2"] and version = "TLSv1_3"
}
+
+ predicate isInsecure() { this in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] }
+}
+
+/** An unspecific protocol version */
+class ProtocolFamily extends string {
+ ProtocolFamily() { this in ["SSLv23", "TLS"] }
}
/** The creation of a context. */
@@ -42,6 +49,34 @@ abstract class ProtocolRestriction extends DataFlow::CfgNode {
abstract ProtocolVersion getRestriction();
}
+/** A context is being relaxed on which protocols it can accepts. */
+abstract class ProtocolUnrestriction extends DataFlow::CfgNode {
+ /** Gets the context being relaxed. */
+ abstract DataFlow::CfgNode getContext();
+
+ /** Gets the protocol version being allowed. */
+ abstract ProtocolVersion getUnrestriction();
+}
+
+abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrestriction {
+ TlsLibrary library;
+ ProtocolFamily family;
+
+ UnspecificContextCreation() { this.getProtocol() = library.unspecific_version(family) }
+
+ override DataFlow::CfgNode getContext() { result = this }
+
+ override ProtocolVersion getUnrestriction() {
+ family = "TLS" and
+ result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
+ or
+ // This can negotiate a TLS 1.3 connection (!)
+ // see https://docs.python.org/3/library/ssl.html#ssl-contexts
+ family = "SSLv23" and
+ result in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
+ }
+}
+
abstract class TlsLibrary extends string {
TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
@@ -49,7 +84,7 @@ abstract class TlsLibrary extends string {
abstract string specific_insecure_version_name(ProtocolVersion version);
/** The name of an unspecific protocol version, say TLS, known to have insecure instances. */
- abstract string unspecific_version_name();
+ abstract string unspecific_version_name(ProtocolFamily family);
/** The module or class holding the version constants. */
abstract API::Node version_constants();
@@ -60,8 +95,8 @@ abstract class TlsLibrary extends string {
}
/** A dataflow node representing an unspecific protocol version, say TLS, known to have insecure instances. */
- DataFlow::Node unspecific_version() {
- result = version_constants().getMember(unspecific_version_name()).getAUse()
+ DataFlow::Node unspecific_version(ProtocolFamily family) {
+ result = version_constants().getMember(unspecific_version_name(family)).getAUse()
}
/** The creation of a context with a deafult protocol. */
@@ -77,11 +112,11 @@ abstract class TlsLibrary extends string {
}
/** The creation of a context with an unspecific protocol version, say TLS, known to have insecure instances. */
- DataFlow::CfgNode unspecific_context_creation() {
+ DataFlow::CfgNode unspecific_context_creation(ProtocolFamily family) {
result = default_context_creation()
or
result = specific_context_creation() and
- result.(ContextCreation).getProtocol() = unspecific_version()
+ result.(ContextCreation).getProtocol() = unspecific_version(family)
}
/** A connection is created in an insecure manner, not from a context. */
@@ -92,4 +127,7 @@ abstract class TlsLibrary extends string {
/** A context is being restricted on which protocols it can accepts. */
abstract ProtocolRestriction protocol_restriction();
+
+ /** A context is being relaxed on which protocols it can accepts. */
+ abstract ProtocolUnrestriction protocol_unrestriction();
}
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index e116662ce65..afd9cc15d9f 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -1,32 +1,40 @@
-| InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
-| InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified in $@ | InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
-| InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified in $@ | InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
-| InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | call to SSLContext |
-| InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv3 specified in $@ | InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | call to SSLContext |
-| InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version TLSv1 specified in $@ | InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | call to SSLContext |
-| InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified in $@ | InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified in $@ | InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
-| InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | call to SSLContext |
-| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
-| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
-| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
-| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:19:14:19:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:15:15:15:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:47:14:47:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:43:15:43:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:173:14:173:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:169:15:169:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:188:15:188:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
-| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:188:15:188:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified by $@ | InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified by $@ | InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv3 specified by $@ | InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version TLSv1 specified by $@ | InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified by $@ | InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified by $@ | InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | call to SSLContext |
+| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv2 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv2 allowed by $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:29:27:29:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv2 allowed by $@ | pyOpenSSL_fluent.py:25:15:25:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:29:27:29:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | pyOpenSSL_fluent.py:25:15:25:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:19:14:19:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:15:15:15:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:47:14:47:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:43:15:43:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:144:5:144:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:162:5:162:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:124:14:124:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:122:5:122:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:173:14:173:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:170:5:170:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | ssl_fluent.py:189:5:189:11 | ControlFlowNode for context | context modification |
From ee038373571b4d3fe2490c2fd19eaeb9afbbfa49 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 3 Mar 2021 23:46:18 +0100
Subject: [PATCH 0025/1429] Python: small refactor
---
python/ql/src/Security/CWE-327/PyOpenSSL.qll | 5 +----
python/ql/src/Security/CWE-327/Ssl.qll | 5 +----
python/ql/src/Security/CWE-327/TlsLibraryModel.qll | 7 ++++---
3 files changed, 6 insertions(+), 11 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
index 3c36c568ef5..d2cfb74b3ab 100644
--- a/python/ql/src/Security/CWE-327/PyOpenSSL.qll
+++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
@@ -51,10 +51,7 @@ class UnspecificPyOpenSSLContextCreation extends PyOpenSSLContextCreation, Unspe
class PyOpenSSL extends TlsLibrary {
PyOpenSSL() { this = "pyOpenSSL" }
- override string specific_insecure_version_name(ProtocolVersion version) {
- version in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] and
- result = version + "_METHOD"
- }
+ override string specific_version_name(ProtocolVersion version) { result = version + "_METHOD" }
override string unspecific_version_name(ProtocolFamily family) {
// `"TLS_METHOD"` is not actually available in pyOpenSSL yet, but should be coming soon..
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index e749bb9bc3c..66a821e83ac 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -135,10 +135,7 @@ class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContext
class Ssl extends TlsLibrary {
Ssl() { this = "ssl" }
- override string specific_insecure_version_name(ProtocolVersion version) {
- version in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] and
- result = "PROTOCOL_" + version
- }
+ override string specific_version_name(ProtocolVersion version) { result = "PROTOCOL_" + version }
override string unspecific_version_name(ProtocolFamily family) { result = "PROTOCOL_" + family }
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
index 97ceec00688..3ab880e8bd9 100644
--- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -80,8 +80,8 @@ abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrest
abstract class TlsLibrary extends string {
TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
- /** The name of a specific protocol version, known to be insecure. */
- abstract string specific_insecure_version_name(ProtocolVersion version);
+ /** The name of a specific protocol version. */
+ abstract string specific_version_name(ProtocolVersion version);
/** The name of an unspecific protocol version, say TLS, known to have insecure instances. */
abstract string unspecific_version_name(ProtocolFamily family);
@@ -91,7 +91,8 @@ abstract class TlsLibrary extends string {
/** A dataflow node representing a specific protocol version, known to be insecure. */
DataFlow::Node insecure_version(ProtocolVersion version) {
- result = version_constants().getMember(specific_insecure_version_name(version)).getAUse()
+ version.isInsecure() and
+ result = version_constants().getMember(specific_version_name(version)).getAUse()
}
/** A dataflow node representing an unspecific protocol version, say TLS, known to have insecure instances. */
From de9469bbfc1769e6e705165b6e53efa12a0c97eb Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 4 Mar 2021 00:01:44 +0100
Subject: [PATCH 0026/1429] Python: complete `ssl.create_default_context`
---
python/ql/src/Security/CWE-327/Ssl.qll | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 66a821e83ac..d4886219d08 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -132,6 +132,15 @@ class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContext
}
}
+class UnspecificSSLDefaultContextCreation extends SSLDefaultContextCreation, ProtocolUnrestriction {
+ override DataFlow::CfgNode getContext() { result = this }
+
+ // see https://docs.python.org/3/library/ssl.html#ssl.create_default_context
+ override ProtocolVersion getUnrestriction() {
+ result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
+ }
+}
+
class Ssl extends TlsLibrary {
Ssl() { this = "ssl" }
@@ -167,5 +176,7 @@ class Ssl extends TlsLibrary {
result instanceof ContextSetVersion
or
result instanceof UnspecificSSLContextCreation
+ or
+ result instanceof UnspecificSSLDefaultContextCreation
}
}
From d02c5298725695e1fe3dc663922619a7f0a8b35d Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 4 Mar 2021 00:06:36 +0100
Subject: [PATCH 0027/1429] Python: Update annotation
---
python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
index eceacaebe21..ab65e3bd206 100644
--- a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
@@ -186,7 +186,7 @@ def test_fluent_ssl_safe_version():
def test_fluent_explicitly_unsafe():
hostname = 'www.python.org'
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
- context.options &= ~ssl.OP_NO_SSLv3 # This not recognized
+ context.options &= ~ssl.OP_NO_SSLv3
with socket.create_connection((hostname, 443)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock: # SSLv3 not flagged here
From c5577cb09a073c2fb4f6feeca887c437d5fbe2fb Mon Sep 17 00:00:00 2001
From: haby0
Date: Thu, 4 Mar 2021 19:54:49 +0800
Subject: [PATCH 0028/1429] Fix the problem
---
.../Security/CWE/CWE-352/JsonpInjection.ql | 68 --------
.../CWE/CWE-352/JsonpInjectionFilterLib.qll | 77 ---------
.../CWE/CWE-352/JsonpInjectionLib.qll | 155 ------------------
.../Security/CWE/CWE-352/JsonStringLib.qll | 0
.../CWE/CWE-352/JsonpController.java} | 48 +-----
.../Security/CWE/CWE-352/JsonpInjection.java | 0
.../Security/CWE/CWE-352/JsonpInjection.qhelp | 2 +-
.../Security/CWE/CWE-352/JsonpInjection.ql | 64 ++++++++
.../CWE/CWE-352/JsonpInjectionLib.qll | 130 +++++++++++++++
.../CWE/CWE-352/JsonpInjectionServlet1.java | 0
.../CWE/CWE-352/JsonpInjectionServlet2.java | 0
.../Security/CWE/CWE-352/RefererFilter.java | 43 +++++
.../semmle/code/java/frameworks/Servlets.qll | 12 +-
.../security/CWE-352/JsonpController.java | 128 +++++++++++++++
.../security/CWE-352/JsonpInjection.expected | 60 -------
.../CWE-352/JsonpInjectionServlet1.java | 64 ++++++++
.../CWE-352/JsonpInjectionServlet2.java} | 16 +-
.../CWE-352/JsonpInjection_1.expected | 60 +++++++
.../CWE-352/JsonpInjection_2.expected | 78 +++++++++
.../CWE-352/JsonpInjection_3.expected | 66 ++++++++
.../query-tests/security/CWE-352/Readme | 3 +
.../security/CWE-352/RefererFilter.java | 43 +++++
.../gson-2.8.6/com/google/gson/Gson.java | 7 +
.../org/springframework/util/StringUtils.java | 8 +
.../javax/servlet/Filter.java | 13 ++
.../javax/servlet/FilterChain.java | 8 +
.../javax/servlet/FilterConfig.java | 3 +
.../javax/servlet/ServletException.java | 8 +
.../javax/servlet/ServletRequest.java | 87 ++++++++++
.../javax/servlet/ServletResponse.java | 39 +++++
.../servlet/http/HttpServletRequest.java | 116 +++++++++++++
.../servlet/http/HttpServletResponse.java | 106 ++++++++++++
32 files changed, 1084 insertions(+), 428 deletions(-)
delete mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql
delete mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjectionFilterLib.qll
delete mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll
rename java/ql/src/{ => experimental}/Security/CWE/CWE-352/JsonStringLib.qll (100%)
rename java/ql/{test/experimental/query-tests/security/CWE-352/JsonpInjection.java => src/experimental/Security/CWE/CWE-352/JsonpController.java} (72%)
rename java/ql/src/{ => experimental}/Security/CWE/CWE-352/JsonpInjection.java (100%)
rename java/ql/src/{ => experimental}/Security/CWE/CWE-352/JsonpInjection.qhelp (96%)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
rename java/ql/src/{ => experimental}/Security/CWE/CWE-352/JsonpInjectionServlet1.java (100%)
rename java/ql/src/{ => experimental}/Security/CWE/CWE-352/JsonpInjectionServlet2.java (100%)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-352/RefererFilter.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet1.java
rename java/ql/{src/Security/CWE/CWE-352/JsonpInjectionServlet.java => test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet2.java} (75%)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/Readme
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java
create mode 100644 java/ql/test/stubs/gson-2.8.6/com/google/gson/Gson.java
create mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/Filter.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterChain.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterConfig.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletException.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletRequest.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletResponse.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletRequest.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletResponse.java
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql b/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql
deleted file mode 100644
index 53ee6182511..00000000000
--- a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * @name JSONP Injection
- * @description User-controlled callback function names that are not verified are vulnerable
- * to jsonp injection attacks.
- * @kind path-problem
- * @problem.severity error
- * @precision high
- * @id java/JSONP-Injection
- * @tags security
- * external/cwe/cwe-352
- */
-
-import java
-import JsonpInjectionLib
-import JsonpInjectionFilterLib
-import semmle.code.java.dataflow.FlowSources
-import semmle.code.java.deadcode.WebEntryPoints
-import DataFlow::PathGraph
-
-
-/** If there is a method to verify `token`, `auth`, `referer`, and `origin`, it will not pass. */
-class ServletVerifAuth extends DataFlow::BarrierGuard {
- ServletVerifAuth() {
- exists(MethodAccess ma, Node prod, Node succ |
- ma.getMethod().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- prod instanceof RemoteFlowSource and
- succ.asExpr() = ma.getAnArgument() and
- ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- localFlowStep*(prod, succ) and
- this = ma
- )
- }
-
- override predicate checks(Expr e, boolean branch) {
- exists(Node node |
- node instanceof JsonpInjectionSink and
- e = node.asExpr() and
- branch = true
- )
- }
-}
-
-/** Taint-tracking configuration tracing flow from get method request sources to output jsonp data. */
-class JsonpInjectionConfig extends TaintTracking::Configuration {
- JsonpInjectionConfig() { this = "JsonpInjectionConfig" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof GetHttpRequestSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof JsonpInjectionSink }
-
- override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
- guard instanceof ServletVerifAuth
- }
-
- override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
- exists(MethodAccess ma |
- isRequestGetParamMethod(ma) and pred.asExpr() = ma.getQualifier() and succ.asExpr() = ma
- )
- }
-}
-
-from DataFlow::PathNode source, DataFlow::PathNode sink, JsonpInjectionConfig conf
-where
- not checks() = false and
- conf.hasFlowPath(source, sink) and
- exists(JsonpInjectionFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode()))
-select sink.getNode(), source, sink, "Jsonp Injection query might include code from $@.",
- source.getNode(), "this user input"
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionFilterLib.qll b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionFilterLib.qll
deleted file mode 100644
index b349bed2641..00000000000
--- a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionFilterLib.qll
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * @name JSONP Injection
- * @description User-controlled callback function names that are not verified are vulnerable
- * to json hijacking attacks.
- * @kind path-problem
- */
-
-import java
-import DataFlow
-import semmle.code.java.dataflow.FlowSources
-import semmle.code.java.dataflow.TaintTracking2
-import DataFlow::PathGraph
-
-class FilterVerifAuth extends DataFlow::BarrierGuard {
- FilterVerifAuth() {
- exists(MethodAccess ma, Node prod, Node succ |
- ma.getMethod().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- prod instanceof RemoteFlowSource and
- succ.asExpr() = ma.getAnArgument() and
- ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- localFlowStep*(prod, succ) and
- this = ma
- )
- }
-
- override predicate checks(Expr e, boolean branch) {
- exists(Node node |
- node instanceof DoFilterMethodSink and
- e = node.asExpr() and
- branch = true
- )
- }
-}
-
-/** A data flow source for `Filter.doFilter` method paramters. */
-private class DoFilterMethodSource extends DataFlow::Node {
- DoFilterMethodSource() {
- exists(Method m |
- isDoFilterMethod(m) and
- m.getAParameter().getAnAccess() = this.asExpr()
- )
- }
-}
-
-/** A data flow sink for `FilterChain.doFilter` method qualifying expression. */
-private class DoFilterMethodSink extends DataFlow::Node {
- DoFilterMethodSink() {
- exists(MethodAccess ma, Method m | ma.getMethod() = m |
- m.hasName("doFilter") and
- m.getDeclaringType*().hasQualifiedName("javax.servlet", "FilterChain") and
- ma.getQualifier() = this.asExpr()
- )
- }
-}
-
-/** Taint-tracking configuration tracing flow from `doFilter` method paramter source to output
- * `FilterChain.doFilter` method qualifying expression.
- * */
-class DoFilterMethodConfig extends TaintTracking::Configuration {
- DoFilterMethodConfig() { this = "DoFilterMethodConfig" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof DoFilterMethodSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof DoFilterMethodSink }
-
- override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
- guard instanceof FilterVerifAuth
- }
-}
-
-/** Implement class modeling verification for `Filter.doFilter`, return false if it fails. */
-boolean checks() {
- exists(DataFlow::PathNode source, DataFlow::PathNode sink, DoFilterMethodConfig conf |
- conf.hasFlowPath(source, sink) and
- result = false
- )
-}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll
deleted file mode 100644
index 3f730425823..00000000000
--- a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll
+++ /dev/null
@@ -1,155 +0,0 @@
-import java
-import DataFlow
-import JsonStringLib
-import semmle.code.java.dataflow.DataFlow
-import semmle.code.java.dataflow.FlowSources
-import semmle.code.java.frameworks.spring.SpringController
-
-/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
-private predicate isGetServletMethod(Method m) {
- isServletRequestMethod(m) and m.getName() = "doGet"
-}
-
-/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
-private predicate isGetSpringControllerMethod(Method m) {
- exists(Annotation a |
- a = m.getAnAnnotation() and
- a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "GetMapping")
- )
- or
- exists(Annotation a |
- a = m.getAnAnnotation() and
- a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and
- a.getValue("method").toString().regexpMatch("RequestMethod.GET|\\{...\\}")
- )
-}
-
-/** Method parameters use the annotation `@RequestParam` or the parameter type is `ServletRequest`, `String`, `Object` */
-predicate checkSpringMethodParameterType(Method m, int i) {
- m.getParameter(i).getType() instanceof ServletRequest
- or
- exists(Parameter p |
- p = m.getParameter(i) and
- p.hasAnnotation() and
- p.getAnAnnotation()
- .getType()
- .hasQualifiedName("org.springframework.web.bind.annotation", "RequestParam") and
- p.getType().getName().regexpMatch("String|Object")
- )
- or
- exists(Parameter p |
- p = m.getParameter(i) and
- not p.hasAnnotation() and
- p.getType().getName().regexpMatch("String|Object")
- )
-}
-
-/** A data flow source for get method request parameters. */
-abstract class GetHttpRequestSource extends DataFlow::Node { }
-
-/** A data flow source for servlet get method request parameters. */
-private class ServletGetHttpRequestSource extends GetHttpRequestSource {
- ServletGetHttpRequestSource() {
- exists(Method m |
- isGetServletMethod(m) and
- m.getParameter(0).getAnAccess() = this.asExpr()
- )
- }
-}
-
-/** A data flow source for spring controller get method request parameters. */
-private class SpringGetHttpRequestSource extends GetHttpRequestSource {
- SpringGetHttpRequestSource() {
- exists(SpringControllerMethod scm, int i |
- isGetSpringControllerMethod(scm) and
- checkSpringMethodParameterType(scm, i) and
- scm.getParameter(i).getAnAccess() = this.asExpr()
- )
- }
-}
-
-/** A data flow sink for unvalidated user input that is used to jsonp. */
-abstract class JsonpInjectionSink extends DataFlow::Node { }
-
-/** Use ```print```, ```println```, ```write``` to output result. */
-private class WriterPrintln extends JsonpInjectionSink {
- WriterPrintln() {
- exists(MethodAccess ma |
- ma.getMethod().getName().regexpMatch("print|println|write") and
- ma.getMethod()
- .getDeclaringType()
- .getASourceSupertype*()
- .hasQualifiedName("java.io", "PrintWriter") and
- ma.getArgument(0) = this.asExpr()
- )
- }
-}
-
-/** Spring Request Method return result. */
-private class SpringReturn extends JsonpInjectionSink {
- SpringReturn() {
- exists(ReturnStmt rs, Method m | m = rs.getEnclosingCallable() |
- isGetSpringControllerMethod(m) and
- rs.getResult() = this.asExpr()
- )
- }
-}
-
-/** A concatenate expression using `(` and `)` or `);`. */
-class JsonpInjectionExpr extends AddExpr {
- JsonpInjectionExpr() {
- getRightOperand().toString().regexpMatch("\"\\)\"|\"\\);\"") and
- getLeftOperand()
- .(AddExpr)
- .getLeftOperand()
- .(AddExpr)
- .getRightOperand()
- .toString()
- .regexpMatch("\"\\(\"")
- }
-
- /** Get the jsonp function name of this expression */
- Expr getFunctionName() {
- result = getLeftOperand().(AddExpr).getLeftOperand().(AddExpr).getLeftOperand()
- }
-
- /** Get the json data of this expression */
- Expr getJsonExpr() { result = getLeftOperand().(AddExpr).getRightOperand() }
-}
-
-/** A data flow configuration tracing flow from remote sources to jsonp function name. */
-class RemoteFlowConfig extends DataFlow2::Configuration {
- RemoteFlowConfig() { this = "RemoteFlowConfig" }
-
- override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(JsonpInjectionExpr jhe | jhe.getFunctionName() = sink.asExpr())
- }
-}
-
-/** A data flow configuration tracing flow from json data to splicing jsonp data. */
-class JsonDataFlowConfig extends DataFlow2::Configuration {
- JsonDataFlowConfig() { this = "JsonDataFlowConfig" }
-
- override predicate isSource(DataFlow::Node src) { src instanceof JsonpStringSource }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(JsonpInjectionExpr jhe | jhe.getJsonExpr() = sink.asExpr())
- }
-}
-
-/** Taint-tracking configuration tracing flow from user-controllable function name jsonp data to output jsonp data. */
-class JsonpInjectionFlowConfig extends DataFlow::Configuration {
- JsonpInjectionFlowConfig() { this = "JsonpInjectionFlowConfig" }
-
- override predicate isSource(DataFlow::Node src) {
- exists(JsonpInjectionExpr jhe, JsonDataFlowConfig jdfc, RemoteFlowConfig rfc |
- jhe = src.asExpr() and
- jdfc.hasFlowTo(DataFlow::exprNode(jhe.getJsonExpr())) and
- rfc.hasFlowTo(DataFlow::exprNode(jhe.getFunctionName()))
- )
- }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof JsonpInjectionSink }
-}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonStringLib.qll b/java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll
similarity index 100%
rename from java/ql/src/Security/CWE/CWE-352/JsonStringLib.qll
rename to java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpController.java
similarity index 72%
rename from java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java
rename to java/ql/src/experimental/Security/CWE/CWE-352/JsonpController.java
index 9f079513a8b..84a172a7aeb 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpController.java
@@ -3,17 +3,14 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import java.io.PrintWriter;
import java.util.HashMap;
-import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
-public class JsonpInjection {
+public class JsonpController {
private static HashMap hashMap = new HashMap();
static {
@@ -96,54 +93,13 @@ public class JsonpInjection {
@GetMapping(value = "jsonp7")
@ResponseBody
- public String good(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
- String val = "";
- Random random = new Random();
- for (int i = 0; i < 10; i++) {
- val += String.valueOf(random.nextInt(10));
- }
- // good
- jsonpCallback = jsonpCallback + "_" + val;
- String jsonStr = getJsonStr(hashMap);
- resultStr = jsonpCallback + "(" + jsonStr + ")";
- return resultStr;
- }
-
- @GetMapping(value = "jsonp8")
- @ResponseBody
public String good1(HttpServletRequest request) {
String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
String token = request.getParameter("token");
- // good
if (verifToken(token)){
- System.out.println(token);
- String jsonStr = getJsonStr(hashMap);
- resultStr = jsonpCallback + "(" + jsonStr + ")";
- return resultStr;
- }
-
- return "error";
- }
-
- @GetMapping(value = "jsonp9")
- @ResponseBody
- public String good2(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
- String referer = request.getHeader("Referer");
-
- boolean result = verifReferer(referer);
-
- boolean test = result;
- // good
- if (test){
+ String jsonpCallback = request.getParameter("jsonpCallback");
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
return resultStr;
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.java b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
similarity index 100%
rename from java/ql/src/Security/CWE/CWE-352/JsonpInjection.java
rename to java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
similarity index 96%
rename from java/ql/src/Security/CWE/CWE-352/JsonpInjection.qhelp
rename to java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
index b063b409d3a..bb5d628ac0b 100644
--- a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
@@ -16,7 +16,7 @@ there is a problem of sensitive information leakage.
The following example shows the case of no verification processing and verification processing for the external input function name.
-
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
new file mode 100644
index 00000000000..f3ae25daa03
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
@@ -0,0 +1,64 @@
+/**
+ * @name JSONP Injection
+ * @description User-controlled callback function names that are not verified are vulnerable
+ * to jsonp injection attacks.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id java/JSONP-Injection
+ * @tags security
+ * external/cwe/cwe-352
+ */
+
+import java
+import JsonpInjectionLib
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.deadcode.WebEntryPoints
+import semmle.code.java.security.XSS
+import DataFlow::PathGraph
+
+/** Determine whether there is a verification method for the remote streaming source data flow path method. */
+predicate existsFilterVerificationMethod() {
+ exists(MethodAccess ma,Node existsNode, Method m|
+ ma.getMethod() instanceof VerificationMethodClass and
+ existsNode.asExpr() = ma and
+ m = getAnMethod(existsNode.getEnclosingCallable()) and
+ isDoFilterMethod(m)
+ )
+}
+
+/** Determine whether there is a verification method for the remote streaming source data flow path method. */
+predicate existsServletVerificationMethod(Node checkNode) {
+ exists(MethodAccess ma,Node existsNode|
+ ma.getMethod() instanceof VerificationMethodClass and
+ existsNode.asExpr() = ma and
+ getAnMethod(existsNode.getEnclosingCallable()) = getAnMethod(checkNode.getEnclosingCallable())
+ )
+}
+
+/** Taint-tracking configuration tracing flow from get method request sources to output jsonp data. */
+class RequestResponseFlowConfig extends TaintTracking::Configuration {
+ RequestResponseFlowConfig() { this = "RequestResponseFlowConfig" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source instanceof RemoteFlowSource and
+ getAnMethod(source.getEnclosingCallable()) instanceof RequestGetMethod
+ }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ exists(MethodAccess ma |
+ isRequestGetParamMethod(ma) and pred.asExpr() = ma.getQualifier() and succ.asExpr() = ma
+ )
+ }
+}
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, RequestResponseFlowConfig conf
+where
+ not existsServletVerificationMethod(source.getNode()) and
+ not existsFilterVerificationMethod() and
+ conf.hasFlowPath(source, sink) and
+ exists(JsonpInjectionFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode()))
+select sink.getNode(), source, sink, "Jsonp Injection query might include code from $@.",
+ source.getNode(), "this user input"
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
new file mode 100644
index 00000000000..b8964524a9f
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
@@ -0,0 +1,130 @@
+import java
+import DataFlow
+import JsonStringLib
+import semmle.code.java.security.XSS
+import semmle.code.java.dataflow.DataFlow
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.frameworks.spring.SpringController
+
+/** Taint-tracking configuration tracing flow from user-controllable function name jsonp data to output jsonp data. */
+class VerificationMethodFlowConfig extends TaintTracking::Configuration {
+ VerificationMethodFlowConfig() { this = "VerificationMethodFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma, BarrierGuard bg |
+ ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
+ bg = ma and
+ sink.asExpr() = ma.getAnArgument()
+ )
+ }
+}
+
+/** The parameter name of the method is `token`, `auth`, `referer`, `origin`. */
+class VerificationMethodClass extends Method {
+ VerificationMethodClass() {
+ exists(MethodAccess ma, BarrierGuard bg, VerificationMethodFlowConfig vmfc, Node node |
+ this = ma.getMethod() and
+ this.getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
+ bg = ma and
+ node.asExpr() = ma.getAnArgument() and
+ vmfc.hasFlowTo(node)
+ )
+ }
+}
+
+/** Get Callable by recursive method. */
+Callable getAnMethod(Callable call) {
+ result = call
+ or
+ result = getAnMethod(call.getAReference().getEnclosingCallable())
+}
+
+abstract class RequestGetMethod extends Method { }
+
+/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
+private class ServletGetMethod extends RequestGetMethod {
+ ServletGetMethod() {
+ exists(Method m |
+ m = this and
+ isServletRequestMethod(m) and
+ m.getName() = "doGet"
+ )
+ }
+}
+
+/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
+private class SpringControllerGetMethod extends RequestGetMethod {
+ SpringControllerGetMethod() {
+ exists(Annotation a |
+ a = this.getAnAnnotation() and
+ a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "GetMapping")
+ )
+ or
+ exists(Annotation a |
+ a = this.getAnAnnotation() and
+ a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and
+ a.getValue("method").toString().regexpMatch("RequestMethod.GET|\\{...\\}")
+ )
+ }
+}
+
+/** A concatenate expression using `(` and `)` or `);`. */
+class JsonpInjectionExpr extends AddExpr {
+ JsonpInjectionExpr() {
+ getRightOperand().toString().regexpMatch("\"\\)\"|\"\\);\"") and
+ getLeftOperand()
+ .(AddExpr)
+ .getLeftOperand()
+ .(AddExpr)
+ .getRightOperand()
+ .toString()
+ .regexpMatch("\"\\(\"")
+ }
+
+ /** Get the jsonp function name of this expression */
+ Expr getFunctionName() {
+ result = getLeftOperand().(AddExpr).getLeftOperand().(AddExpr).getLeftOperand()
+ }
+
+ /** Get the json data of this expression */
+ Expr getJsonExpr() { result = getLeftOperand().(AddExpr).getRightOperand() }
+}
+
+/** A data flow configuration tracing flow from remote sources to jsonp function name. */
+class RemoteFlowConfig extends DataFlow2::Configuration {
+ RemoteFlowConfig() { this = "RemoteFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(JsonpInjectionExpr jhe | jhe.getFunctionName() = sink.asExpr())
+ }
+}
+
+/** A data flow configuration tracing flow from json data to splicing jsonp data. */
+class JsonDataFlowConfig extends DataFlow2::Configuration {
+ JsonDataFlowConfig() { this = "JsonDataFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof JsonpStringSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(JsonpInjectionExpr jhe | jhe.getJsonExpr() = sink.asExpr())
+ }
+}
+
+/** Taint-tracking configuration tracing flow from user-controllable function name jsonp data to output jsonp data. */
+class JsonpInjectionFlowConfig extends TaintTracking::Configuration {
+ JsonpInjectionFlowConfig() { this = "JsonpInjectionFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) {
+ exists(JsonpInjectionExpr jhe, JsonDataFlowConfig jdfc, RemoteFlowConfig rfc |
+ jhe = src.asExpr() and
+ jdfc.hasFlowTo(DataFlow::exprNode(jhe.getJsonExpr())) and
+ rfc.hasFlowTo(DataFlow::exprNode(jhe.getFunctionName()))
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink }
+}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet1.java b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet1.java
similarity index 100%
rename from java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet1.java
rename to java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet1.java
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet2.java b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet2.java
similarity index 100%
rename from java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet2.java
rename to java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet2.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/RefererFilter.java b/java/ql/src/experimental/Security/CWE/CWE-352/RefererFilter.java
new file mode 100644
index 00000000000..97444932ae1
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/RefererFilter.java
@@ -0,0 +1,43 @@
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.util.StringUtils;
+
+public class RefererFilter implements Filter {
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ HttpServletResponse response = (HttpServletResponse) servletResponse;
+ String refefer = request.getHeader("Referer");
+ boolean result = verifReferer(refefer);
+ if (result){
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+ response.sendError(444, "Referer xxx.");
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ public static boolean verifReferer(String referer){
+ if (StringUtils.isEmpty(referer)){
+ return false;
+ }
+ if (referer.startsWith("http://www.baidu.com/")){
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/java/ql/src/semmle/code/java/frameworks/Servlets.qll b/java/ql/src/semmle/code/java/frameworks/Servlets.qll
index b2054dc30cb..5cccf62122f 100644
--- a/java/ql/src/semmle/code/java/frameworks/Servlets.qll
+++ b/java/ql/src/semmle/code/java/frameworks/Servlets.qll
@@ -338,7 +338,6 @@ predicate isRequestGetParamMethod(MethodAccess ma) {
ma.getMethod() instanceof HttpServletRequestGetQueryStringMethod
}
-
/**
* A class that has `javax.servlet.Filter` as an ancestor.
*/
@@ -346,21 +345,18 @@ class FilterClass extends Class {
FilterClass() { getAnAncestor().hasQualifiedName("javax.servlet", "Filter") }
}
-
/**
* The interface `javax.servlet.FilterChain`
*/
-class FilterChain extends RefType {
- FilterChain() {
- hasQualifiedName("javax.servlet", "FilterChain")
- }
+class FilterChain extends Interface {
+ FilterChain() { hasQualifiedName("javax.servlet", "FilterChain") }
}
-/** Holds if `m` is a request handler method (for example `doGet` or `doPost`). */
+/** Holds if `m` is a filter handler method (for example `doFilter`). */
predicate isDoFilterMethod(Method m) {
m.getDeclaringType() instanceof FilterClass and
m.getNumberOfParameters() = 3 and
m.getParameter(0).getType() instanceof ServletRequest and
m.getParameter(1).getType() instanceof ServletResponse and
m.getParameter(2).getType() instanceof FilterChain
-}
\ No newline at end of file
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java
new file mode 100644
index 00000000000..cf860c75640
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java
@@ -0,0 +1,128 @@
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+public class JsonpController {
+ private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+
+ @GetMapping(value = "jsonp1", produces="text/javascript")
+ @ResponseBody
+ public String bad1(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp2")
+ @ResponseBody
+ public String bad2(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
+
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp3")
+ @ResponseBody
+ public String bad3(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp4")
+ @ResponseBody
+ public String bad4(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp5")
+ @ResponseBody
+ public void bad5(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp6")
+ @ResponseBody
+ public void bad6(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ ObjectMapper mapper = new ObjectMapper();
+ String result = mapper.writeValueAsString(hashMap);
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp7")
+ @ResponseBody
+ public String good1(HttpServletRequest request) {
+ String resultStr = null;
+
+ String token = request.getParameter("token");
+
+ if (verifToken(token)){
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ return "error";
+ }
+
+ public static String getJsonStr(Object result) {
+ return JSONObject.toJSONString(result);
+ }
+
+ public static boolean verifToken(String token){
+ if (token != "xxxx"){
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean verifReferer(String referer){
+ if (!referer.startsWith("http://test.com/")){
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected
deleted file mode 100644
index 7e3069cf1d9..00000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected
+++ /dev/null
@@ -1,60 +0,0 @@
-edges
-| JsonpInjection.java:29:32:29:38 | request : HttpServletRequest | JsonpInjection.java:34:16:34:24 | resultStr |
-| JsonpInjection.java:33:21:33:54 | ... + ... : String | JsonpInjection.java:34:16:34:24 | resultStr |
-| JsonpInjection.java:41:32:41:38 | request : HttpServletRequest | JsonpInjection.java:45:16:45:24 | resultStr |
-| JsonpInjection.java:43:21:43:80 | ... + ... : String | JsonpInjection.java:45:16:45:24 | resultStr |
-| JsonpInjection.java:52:32:52:38 | request : HttpServletRequest | JsonpInjection.java:55:16:55:24 | resultStr |
-| JsonpInjection.java:54:21:54:55 | ... + ... : String | JsonpInjection.java:55:16:55:24 | resultStr |
-| JsonpInjection.java:62:32:62:38 | request : HttpServletRequest | JsonpInjection.java:65:16:65:24 | resultStr |
-| JsonpInjection.java:64:21:64:54 | ... + ... : String | JsonpInjection.java:65:16:65:24 | resultStr |
-| JsonpInjection.java:72:32:72:38 | request : HttpServletRequest | JsonpInjection.java:80:20:80:28 | resultStr |
-| JsonpInjection.java:79:21:79:54 | ... + ... : String | JsonpInjection.java:80:20:80:28 | resultStr |
-| JsonpInjection.java:87:32:87:38 | request : HttpServletRequest | JsonpInjection.java:94:20:94:28 | resultStr |
-| JsonpInjection.java:93:21:93:54 | ... + ... : String | JsonpInjection.java:94:20:94:28 | resultStr |
-| JsonpInjection.java:101:32:101:38 | request : HttpServletRequest | JsonpInjection.java:112:16:112:24 | resultStr |
-| JsonpInjection.java:127:25:127:59 | ... + ... : String | JsonpInjection.java:128:20:128:28 | resultStr |
-| JsonpInjection.java:148:25:148:59 | ... + ... : String | JsonpInjection.java:149:20:149:28 | resultStr |
-nodes
-| JsonpInjection.java:29:32:29:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:33:21:33:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:34:16:34:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:34:16:34:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:41:32:41:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:43:21:43:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:45:16:45:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:45:16:45:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:52:32:52:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:54:21:54:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:55:16:55:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:55:16:55:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:62:32:62:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:64:21:64:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:65:16:65:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:65:16:65:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:72:32:72:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:79:21:79:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:80:20:80:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:80:20:80:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:87:32:87:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:93:21:93:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:94:20:94:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:94:20:94:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:101:32:101:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:112:16:112:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:127:25:127:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:128:20:128:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:148:25:148:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:149:20:149:28 | resultStr | semmle.label | resultStr |
-#select
-| JsonpInjection.java:34:16:34:24 | resultStr | JsonpInjection.java:29:32:29:38 | request : HttpServletRequest | JsonpInjection.java:34:16:34:24 |
-resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:29:32:29:38 | request | this user input |
-| JsonpInjection.java:45:16:45:24 | resultStr | JsonpInjection.java:41:32:41:38 | request : HttpServletRequest | JsonpInjection.java:45:16:45:24 |
-resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:41:32:41:38 | request | this user input |
-| JsonpInjection.java:55:16:55:24 | resultStr | JsonpInjection.java:52:32:52:38 | request : HttpServletRequest | JsonpInjection.java:55:16:55:24 |
-resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:52:32:52:38 | request | this user input |
-| JsonpInjection.java:65:16:65:24 | resultStr | JsonpInjection.java:62:32:62:38 | request : HttpServletRequest | JsonpInjection.java:65:16:65:24 |
-resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:62:32:62:38 | request | this user input |
-| JsonpInjection.java:80:20:80:28 | resultStr | JsonpInjection.java:72:32:72:38 | request : HttpServletRequest | JsonpInjection.java:80:20:80:28 |
-resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:72:32:72:38 | request | this user input |
-| JsonpInjection.java:94:20:94:28 | resultStr | JsonpInjection.java:87:32:87:38 | request : HttpServletRequest | JsonpInjection.java:94:20:94:28 |
-resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:87:32:87:38 | request | this user input |
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet1.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet1.java
new file mode 100644
index 00000000000..14ef76275b1
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet1.java
@@ -0,0 +1,64 @@
+import com.google.gson.Gson;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class JsonpInjectionServlet1 extends HttpServlet {
+
+ private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ private String key = "test";
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ doPost(req, resp);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ resp.setContentType("application/json");
+ String jsonpCallback = req.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String jsonResult = gson.toJson(hashMap);
+
+ String referer = req.getHeader("Referer");
+
+ boolean result = verifReferer(referer);
+
+ // good
+ if (result){
+ String resultStr = null;
+ pw = resp.getWriter();
+ resultStr = jsonpCallback + "(" + jsonResult + ")";
+ pw.println(resultStr);
+ pw.flush();
+ }
+ }
+
+ public static boolean verifReferer(String referer){
+ if (!referer.startsWith("http://test.com/")){
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ this.key = config.getInitParameter("key");
+ System.out.println("åˆå§‹åŒ–" + this.key);
+ super.init(config);
+ }
+
+}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet2.java
similarity index 75%
rename from java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet2.java
index 916cd9bf676..bbfbc2dc436 100644
--- a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet2.java
@@ -4,12 +4,11 @@ import java.io.PrintWriter;
import java.util.HashMap;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-public class JsonpInjectionServlet extends HttpServlet {
+public class JsonpInjectionServlet2 extends HttpServlet {
private static HashMap hashMap = new HashMap();
@@ -23,21 +22,12 @@ public class JsonpInjectionServlet extends HttpServlet {
private String key = "test";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- String jsonpCallback = req.getParameter("jsonpCallback");
-
- PrintWriter pw = null;
- Gson gson = new Gson();
- String result = gson.toJson(hashMap);
-
- String resultStr = null;
- pw = resp.getWriter();
- resultStr = jsonpCallback + "(" + result + ")";
- pw.println(resultStr);
- pw.flush();
+ doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ resp.setContentType("application/json");
String jsonpCallback = req.getParameter("jsonpCallback");
PrintWriter pw = null;
Gson gson = new Gson();
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected
new file mode 100644
index 00000000000..a89d03b67a7
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected
@@ -0,0 +1,60 @@
+edges
+| JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 | resultStr |
+| JsonpController.java:30:21:30:54 | ... + ... : String | JsonpController.java:31:16:31:24 | resultStr |
+| JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 | resultStr |
+| JsonpController.java:40:21:40:80 | ... + ... : String | JsonpController.java:42:16:42:24 | resultStr |
+| JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 | resultStr |
+| JsonpController.java:51:21:51:55 | ... + ... : String | JsonpController.java:52:16:52:24 | resultStr |
+| JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 | resultStr |
+| JsonpController.java:61:21:61:54 | ... + ... : String | JsonpController.java:62:16:62:24 | resultStr |
+| JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 | resultStr |
+| JsonpController.java:76:21:76:54 | ... + ... : String | JsonpController.java:77:20:77:28 | resultStr |
+| JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 | resultStr |
+| JsonpController.java:90:21:90:54 | ... + ... : String | JsonpController.java:91:20:91:28 | resultStr |
+| JsonpController.java:99:24:99:52 | getParameter(...) : String | JsonpController.java:101:24:101:28 | token |
+| JsonpController.java:102:36:102:72 | getParameter(...) : String | JsonpController.java:105:20:105:28 | resultStr |
+| JsonpController.java:104:25:104:59 | ... + ... : String | JsonpController.java:105:20:105:28 | resultStr |
+nodes
+| JsonpController.java:26:32:26:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:30:21:30:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:38:32:38:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:40:21:40:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:49:32:49:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:51:21:51:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:59:32:59:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:61:21:61:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:69:32:69:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:76:21:76:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:84:32:84:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:90:21:90:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:99:24:99:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:101:24:101:28 | token | semmle.label | token |
+| JsonpController.java:102:36:102:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:104:25:104:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
+#select
+| JsonpController.java:31:16:31:24 | resultStr | JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:26:32:26:68 | getParameter(...) | this user input |
+| JsonpController.java:42:16:42:24 | resultStr | JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:38:32:38:68 | getParameter(...) | this user input |
+| JsonpController.java:52:16:52:24 | resultStr | JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:49:32:49:68 | getParameter(...) | this user input |
+| JsonpController.java:62:16:62:24 | resultStr | JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:59:32:59:68 | getParameter(...) | this user input |
+| JsonpController.java:77:20:77:28 | resultStr | JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:69:32:69:68 | getParameter(...) | this user input |
+| JsonpController.java:91:20:91:28 | resultStr | JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:84:32:84:68 | getParameter(...) | this user input |
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected
new file mode 100644
index 00000000000..4b12308a212
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected
@@ -0,0 +1,78 @@
+edges
+| JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 | resultStr |
+| JsonpController.java:30:21:30:54 | ... + ... : String | JsonpController.java:31:16:31:24 | resultStr |
+| JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 | resultStr |
+| JsonpController.java:40:21:40:80 | ... + ... : String | JsonpController.java:42:16:42:24 | resultStr |
+| JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 | resultStr |
+| JsonpController.java:51:21:51:55 | ... + ... : String | JsonpController.java:52:16:52:24 | resultStr |
+| JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 | resultStr |
+| JsonpController.java:61:21:61:54 | ... + ... : String | JsonpController.java:62:16:62:24 | resultStr |
+| JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 | resultStr |
+| JsonpController.java:76:21:76:54 | ... + ... : String | JsonpController.java:77:20:77:28 | resultStr |
+| JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 | resultStr |
+| JsonpController.java:90:21:90:54 | ... + ... : String | JsonpController.java:91:20:91:28 | resultStr |
+| JsonpController.java:99:24:99:52 | getParameter(...) : String | JsonpController.java:101:24:101:28 | token |
+| JsonpController.java:102:36:102:72 | getParameter(...) : String | JsonpController.java:105:20:105:28 | resultStr |
+| JsonpController.java:104:25:104:59 | ... + ... : String | JsonpController.java:105:20:105:28 | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+nodes
+| JsonpController.java:26:32:26:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:30:21:30:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:38:32:38:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:40:21:40:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:49:32:49:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:51:21:51:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:59:32:59:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:61:21:61:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:69:32:69:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:76:21:76:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:84:32:84:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:90:21:90:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:99:24:99:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:101:24:101:28 | token | semmle.label | token |
+| JsonpController.java:102:36:102:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:104:25:104:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+#select
+| JsonpController.java:31:16:31:24 | resultStr | JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:26:32:26:68 | getParameter(...) | this user input |
+| JsonpController.java:42:16:42:24 | resultStr | JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:38:32:38:68 | getParameter(...) | this user input |
+| JsonpController.java:52:16:52:24 | resultStr | JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:49:32:49:68 | getParameter(...) | this user input |
+| JsonpController.java:62:16:62:24 | resultStr | JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:59:32:59:68 | getParameter(...) | this user input |
+| JsonpController.java:77:20:77:28 | resultStr | JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:69:32:69:68 | getParameter(...) | this user input |
+| JsonpController.java:91:20:91:28 | resultStr | JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:84:32:84:68 | getParameter(...) | this user input |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServle
+t2.java:39:20:39:28 | resultStr | Jsonp Injection query might include code from $@. | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) |
+ this user input |
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected
new file mode 100644
index 00000000000..8e33ca6984c
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected
@@ -0,0 +1,66 @@
+edges
+| JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 | resultStr |
+| JsonpController.java:30:21:30:54 | ... + ... : String | JsonpController.java:31:16:31:24 | resultStr |
+| JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 | resultStr |
+| JsonpController.java:40:21:40:80 | ... + ... : String | JsonpController.java:42:16:42:24 | resultStr |
+| JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 | resultStr |
+| JsonpController.java:51:21:51:55 | ... + ... : String | JsonpController.java:52:16:52:24 | resultStr |
+| JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 | resultStr |
+| JsonpController.java:61:21:61:54 | ... + ... : String | JsonpController.java:62:16:62:24 | resultStr |
+| JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 | resultStr |
+| JsonpController.java:76:21:76:54 | ... + ... : String | JsonpController.java:77:20:77:28 | resultStr |
+| JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 | resultStr |
+| JsonpController.java:90:21:90:54 | ... + ... : String | JsonpController.java:91:20:91:28 | resultStr |
+| JsonpController.java:99:24:99:52 | getParameter(...) : String | JsonpController.java:101:24:101:28 | token |
+| JsonpController.java:102:36:102:72 | getParameter(...) : String | JsonpController.java:105:20:105:28 | resultStr |
+| JsonpController.java:104:25:104:59 | ... + ... : String | JsonpController.java:105:20:105:28 | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+| RefererFilter.java:22:26:22:53 | getHeader(...) : String | RefererFilter.java:23:39:23:45 | refefer |
+nodes
+| JsonpController.java:26:32:26:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:30:21:30:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:38:32:38:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:40:21:40:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:49:32:49:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:51:21:51:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:59:32:59:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:61:21:61:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:69:32:69:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:76:21:76:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:84:32:84:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:90:21:90:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:99:24:99:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:101:24:101:28 | token | semmle.label | token |
+| JsonpController.java:102:36:102:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:104:25:104:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+| RefererFilter.java:22:26:22:53 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| RefererFilter.java:23:39:23:45 | refefer | semmle.label | refefer |
+#select
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/Readme b/java/ql/test/experimental/query-tests/security/CWE-352/Readme
new file mode 100644
index 00000000000..15715d6187c
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/Readme
@@ -0,0 +1,3 @@
+1. The JsonpInjection_1.expected result is obtained through the test of `JsonpController.java`.
+2. The JsonpInjection_2.expected result is obtained through the test of `JsonpController.java`, `JsonpInjectionServlet1.java`, `JsonpInjectionServlet2.java`.
+3. The JsonpInjection_3.expected result is obtained through the test of `JsonpController.java`, `JsonpInjectionServlet1.java`, `JsonpInjectionServlet2.java`, `RefererFilter.java`.
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java b/java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java
new file mode 100644
index 00000000000..97444932ae1
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java
@@ -0,0 +1,43 @@
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.util.StringUtils;
+
+public class RefererFilter implements Filter {
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ HttpServletResponse response = (HttpServletResponse) servletResponse;
+ String refefer = request.getHeader("Referer");
+ boolean result = verifReferer(refefer);
+ if (result){
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+ response.sendError(444, "Referer xxx.");
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ public static boolean verifReferer(String referer){
+ if (StringUtils.isEmpty(referer)){
+ return false;
+ }
+ if (referer.startsWith("http://www.baidu.com/")){
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/java/ql/test/stubs/gson-2.8.6/com/google/gson/Gson.java b/java/ql/test/stubs/gson-2.8.6/com/google/gson/Gson.java
new file mode 100644
index 00000000000..bbe53dc2a5f
--- /dev/null
+++ b/java/ql/test/stubs/gson-2.8.6/com/google/gson/Gson.java
@@ -0,0 +1,7 @@
+package com.google.gson;
+
+public final class Gson {
+ public String toJson(Object src) {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java
new file mode 100644
index 00000000000..6ee07f84593
--- /dev/null
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java
@@ -0,0 +1,8 @@
+package org.springframework.util;
+
+public abstract class StringUtils {
+
+ public static boolean isEmpty(Object str) {
+ return str == null || "".equals(str);
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/Filter.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/Filter.java
new file mode 100644
index 00000000000..5833e3c909d
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/Filter.java
@@ -0,0 +1,13 @@
+package javax.servlet;
+
+import java.io.IOException;
+
+public interface Filter {
+ default void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
+
+ default void destroy() {
+ }
+}
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterChain.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterChain.java
new file mode 100644
index 00000000000..6a1dfc588b6
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterChain.java
@@ -0,0 +1,8 @@
+package javax.servlet;
+
+import java.io.IOException;
+
+public interface FilterChain {
+ void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
+}
+
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterConfig.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterConfig.java
new file mode 100644
index 00000000000..66c13eb54f0
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterConfig.java
@@ -0,0 +1,3 @@
+package javax.servlet;
+
+public interface FilterConfig {}
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletException.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletException.java
new file mode 100644
index 00000000000..ce5f7c4465a
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletException.java
@@ -0,0 +1,8 @@
+package javax.servlet;
+
+public class ServletException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ServletException() {
+ }
+}
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletRequest.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletRequest.java
new file mode 100644
index 00000000000..4ee0026d066
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletRequest.java
@@ -0,0 +1,87 @@
+package javax.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+
+public interface ServletRequest {
+ Object getAttribute(String var1);
+
+ Enumeration getAttributeNames();
+
+ String getCharacterEncoding();
+
+ void setCharacterEncoding(String var1) throws UnsupportedEncodingException;
+
+ int getContentLength();
+
+ long getContentLengthLong();
+
+ String getContentType();
+
+ ServletInputStream getInputStream() throws IOException;
+
+ String getParameter(String var1);
+
+ Enumeration getParameterNames();
+
+ String[] getParameterValues(String var1);
+
+ Map getParameterMap();
+
+ String getProtocol();
+
+ String getScheme();
+
+ String getServerName();
+
+ int getServerPort();
+
+ BufferedReader getReader() throws IOException;
+
+ String getRemoteAddr();
+
+ String getRemoteHost();
+
+ void setAttribute(String var1, Object var2);
+
+ void removeAttribute(String var1);
+
+ Locale getLocale();
+
+ Enumeration getLocales();
+
+ boolean isSecure();
+
+ RequestDispatcher getRequestDispatcher(String var1);
+
+ /** @deprecated */
+ @Deprecated
+ String getRealPath(String var1);
+
+ int getRemotePort();
+
+ String getLocalName();
+
+ String getLocalAddr();
+
+ int getLocalPort();
+
+ ServletContext getServletContext();
+
+ AsyncContext startAsync() throws IllegalStateException;
+
+ AsyncContext startAsync(ServletRequest var1, ServletResponse var2) throws IllegalStateException;
+
+ boolean isAsyncStarted();
+
+ boolean isAsyncSupported();
+
+ AsyncContext getAsyncContext();
+
+ DispatcherType getDispatcherType();
+}
+
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletResponse.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletResponse.java
new file mode 100644
index 00000000000..0aa6121e686
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletResponse.java
@@ -0,0 +1,39 @@
+package javax.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Locale;
+
+public interface ServletResponse {
+ String getCharacterEncoding();
+
+ String getContentType();
+
+ ServletOutputStream getOutputStream() throws IOException;
+
+ PrintWriter getWriter() throws IOException;
+
+ void setCharacterEncoding(String var1);
+
+ void setContentLength(int var1);
+
+ void setContentLengthLong(long var1);
+
+ void setContentType(String var1);
+
+ void setBufferSize(int var1);
+
+ int getBufferSize();
+
+ void flushBuffer() throws IOException;
+
+ void resetBuffer();
+
+ boolean isCommitted();
+
+ void reset();
+
+ void setLocale(Locale var1);
+
+ Locale getLocale();
+}
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletRequest.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletRequest.java
new file mode 100644
index 00000000000..02d53a96a33
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletRequest.java
@@ -0,0 +1,116 @@
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Map;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+
+public interface HttpServletRequest extends ServletRequest {
+ String BASIC_AUTH = "BASIC";
+ String FORM_AUTH = "FORM";
+ String CLIENT_CERT_AUTH = "CLIENT_CERT";
+ String DIGEST_AUTH = "DIGEST";
+
+ String getAuthType();
+
+ Cookie[] getCookies();
+
+ long getDateHeader(String var1);
+
+ String getHeader(String var1);
+
+ Enumeration getHeaders(String var1);
+
+ Enumeration getHeaderNames();
+
+ int getIntHeader(String var1);
+
+ default HttpServletMapping getHttpServletMapping() {
+ return new HttpServletMapping() {
+ public String getMatchValue() {
+ return "";
+ }
+
+ public String getPattern() {
+ return "";
+ }
+
+ public String getServletName() {
+ return "";
+ }
+
+ public MappingMatch getMappingMatch() {
+ return null;
+ }
+ };
+ }
+
+ String getMethod();
+
+ String getPathInfo();
+
+ String getPathTranslated();
+
+ default PushBuilder newPushBuilder() {
+ return null;
+ }
+
+ String getContextPath();
+
+ String getQueryString();
+
+ String getRemoteUser();
+
+ boolean isUserInRole(String var1);
+
+ Principal getUserPrincipal();
+
+ String getRequestedSessionId();
+
+ String getRequestURI();
+
+ StringBuffer getRequestURL();
+
+ String getServletPath();
+
+ HttpSession getSession(boolean var1);
+
+ HttpSession getSession();
+
+ String changeSessionId();
+
+ boolean isRequestedSessionIdValid();
+
+ boolean isRequestedSessionIdFromCookie();
+
+ boolean isRequestedSessionIdFromURL();
+
+ /** @deprecated */
+ @Deprecated
+ boolean isRequestedSessionIdFromUrl();
+
+ boolean authenticate(HttpServletResponse var1) throws IOException, ServletException;
+
+ void login(String var1, String var2) throws ServletException;
+
+ void logout() throws ServletException;
+
+ Collection getParts() throws IOException, ServletException;
+
+ Part getPart(String var1) throws IOException, ServletException;
+
+ T upgrade(Class var1) throws IOException, ServletException;
+
+ default Map getTrailerFields() {
+ return Collections.emptyMap();
+ }
+
+ default boolean isTrailerFieldsReady() {
+ return false;
+ }
+}
+
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletResponse.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletResponse.java
new file mode 100644
index 00000000000..0a2c6af0913
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletResponse.java
@@ -0,0 +1,106 @@
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.function.Supplier;
+import javax.servlet.ServletResponse;
+
+public interface HttpServletResponse extends ServletResponse {
+ int SC_CONTINUE = 100;
+ int SC_SWITCHING_PROTOCOLS = 101;
+ int SC_OK = 200;
+ int SC_CREATED = 201;
+ int SC_ACCEPTED = 202;
+ int SC_NON_AUTHORITATIVE_INFORMATION = 203;
+ int SC_NO_CONTENT = 204;
+ int SC_RESET_CONTENT = 205;
+ int SC_PARTIAL_CONTENT = 206;
+ int SC_MULTIPLE_CHOICES = 300;
+ int SC_MOVED_PERMANENTLY = 301;
+ int SC_MOVED_TEMPORARILY = 302;
+ int SC_FOUND = 302;
+ int SC_SEE_OTHER = 303;
+ int SC_NOT_MODIFIED = 304;
+ int SC_USE_PROXY = 305;
+ int SC_TEMPORARY_REDIRECT = 307;
+ int SC_BAD_REQUEST = 400;
+ int SC_UNAUTHORIZED = 401;
+ int SC_PAYMENT_REQUIRED = 402;
+ int SC_FORBIDDEN = 403;
+ int SC_NOT_FOUND = 404;
+ int SC_METHOD_NOT_ALLOWED = 405;
+ int SC_NOT_ACCEPTABLE = 406;
+ int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
+ int SC_REQUEST_TIMEOUT = 408;
+ int SC_CONFLICT = 409;
+ int SC_GONE = 410;
+ int SC_LENGTH_REQUIRED = 411;
+ int SC_PRECONDITION_FAILED = 412;
+ int SC_REQUEST_ENTITY_TOO_LARGE = 413;
+ int SC_REQUEST_URI_TOO_LONG = 414;
+ int SC_UNSUPPORTED_MEDIA_TYPE = 415;
+ int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+ int SC_EXPECTATION_FAILED = 417;
+ int SC_INTERNAL_SERVER_ERROR = 500;
+ int SC_NOT_IMPLEMENTED = 501;
+ int SC_BAD_GATEWAY = 502;
+ int SC_SERVICE_UNAVAILABLE = 503;
+ int SC_GATEWAY_TIMEOUT = 504;
+ int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
+
+ void addCookie(Cookie var1);
+
+ boolean containsHeader(String var1);
+
+ String encodeURL(String var1);
+
+ String encodeRedirectURL(String var1);
+
+ /** @deprecated */
+ @Deprecated
+ String encodeUrl(String var1);
+
+ /** @deprecated */
+ @Deprecated
+ String encodeRedirectUrl(String var1);
+
+ void sendError(int var1, String var2) throws IOException;
+
+ void sendError(int var1) throws IOException;
+
+ void sendRedirect(String var1) throws IOException;
+
+ void setDateHeader(String var1, long var2);
+
+ void addDateHeader(String var1, long var2);
+
+ void setHeader(String var1, String var2);
+
+ void addHeader(String var1, String var2);
+
+ void setIntHeader(String var1, int var2);
+
+ void addIntHeader(String var1, int var2);
+
+ void setStatus(int var1);
+
+ /** @deprecated */
+ @Deprecated
+ void setStatus(int var1, String var2);
+
+ int getStatus();
+
+ String getHeader(String var1);
+
+ Collection getHeaders(String var1);
+
+ Collection getHeaderNames();
+
+ default void setTrailerFields(Supplier
+
+ Note that ssl.wrap_socket has been deprecated in
+ Python 3.7. The recommended alternatives are:
+
+
+ ssl.SSLContext - supported in Python 2.7.9,
+ 3.2, and later versions
+ ssl.create_default_context - a convenience function,
+ supported in Python 3.4 and later versions.
+
+
+ Even when you use these alternatives, you should
+ ensure that a safe protocol is used. The following code illustrates
+ how to use flags (available since Python 3.2) or the `minimum_version`
+ field (favored since Python 3.7) to restrict the protocols accepted when
+ creating a connection.
+
+
+
Wikipedia: Transport Layer Security.
Python 3 documentation: class ssl.SSLContext.
Python 3 documentation: ssl.wrap_socket.
+ Python 3 documentation: notes on context creation.
+ Python 3 documentation: notes on security considerations.
pyOpenSSL documentation: An interface to the SSL-specific parts of OpenSSL.
diff --git a/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py b/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py
index 83c6dbbba0e..535a97f0b93 100644
--- a/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py
+++ b/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py
@@ -1,13 +1,9 @@
-# taken from https://docs.python.org/3/library/ssl.html?highlight=ssl#ssl.SSLContext
-
-import socket
import ssl
-hostname = 'www.python.org'
-context = ssl.create_default_context()
-context.options |= ssl.OP_NO_TLSv1 # This added by me
-context.options |= ssl.OP_NO_TLSv1_1 # This added by me
+# Using flags to restrict the protocol
+context = ssl.SSLContext()
+context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
-with socket.create_connection((hostname, 443)) as sock:
- with context.wrap_socket(sock, server_hostname=hostname) as ssock:
- print(ssock.version())
+# Declaring a minimum version to restrict the protocol
+context = ssl.create_default_context()
+context.minimum_version = ssl.TLSVersion.TLSv1_2
From 4094b184074ca1c4b20e4ad599bef6d536f518c3 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 15 Mar 2021 16:28:08 +0100
Subject: [PATCH 0046/1429] Python: Clean up tests
---
.../CWE-327/InsecureProtocol.expected | 10 ++---
.../Security/CWE-327/InsecureProtocol.py | 39 ++++++-------------
2 files changed, 17 insertions(+), 32 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index f4202a4634d..db30e41b480 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -5,11 +5,11 @@
| InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv3 specified by $@ | InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | call to SSLContext |
| InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version TLSv1 specified by $@ | InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | call to SSLContext |
| InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified by $@ | InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified by $@ | InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
-| InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:15:1:15:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified by $@ | InsecureProtocol.py:15:1:15:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified by $@ | InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:19:1:19:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:19:1:19:19 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:23:1:23:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:23:1:23:43 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:24:1:24:35 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:24:1:24:35 | ControlFlowNode for SSLContext() | call to SSLContext |
| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv2 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
index cb21a6623c9..3ff1207b527 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
@@ -2,7 +2,7 @@ import ssl
from OpenSSL import SSL
from ssl import SSLContext
-# true positives
+# insecure versions specified
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2)
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv3)
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1)
@@ -12,41 +12,26 @@ SSLContext(protocol=ssl.PROTOCOL_SSLv3)
SSLContext(protocol=ssl.PROTOCOL_TLSv1)
SSL.Context(SSL.SSLv2_METHOD)
-SSL.Context(SSL.SSLv23_METHOD)
SSL.Context(SSL.SSLv3_METHOD)
SSL.Context(SSL.TLSv1_METHOD)
-# not relevant
-wrap_socket(ssl_version=ssl.PROTOCOL_SSLv3)
-wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1)
-wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2)
-
-Context(SSL.SSLv3_METHOD)
-Context(SSL.TLSv1_METHOD)
-Context(SSL.SSLv2_METHOD)
-Context(SSL.SSLv23_METHOD)
-
-# true positive using flow
-
METHOD = SSL.SSLv2_METHOD
SSL.Context(METHOD)
-# secure versions
+# importing the protocol constant directly
+from ssl import PROTOCOL_SSLv2
+ssl.wrap_socket(ssl_version=PROTOCOL_SSLv2)
+SSLContext(protocol=PROTOCOL_SSLv2)
+# secure versions specified
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_2)
SSLContext(protocol=ssl.PROTOCOL_TLSv1_2)
SSL.Context(SSL.TLSv1_2_METHOD)
-# possibly insecure default
-ssl.wrap_socket()
-context = SSLContext()
+# possibly secure versions specified
+SSLContext(protocol=ssl.PROTOCOL_SSLv23)
+SSLContext(protocol=ssl.PROTOCOL_TLS)
+SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT)
+SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER)
-# importing the protocol constant directly
-
-from ssl import PROTOCOL_SSLv2
-
-ssl.wrap_socket(ssl_version=PROTOCOL_SSLv2)
-SSLContext(protocol=PROTOCOL_SSLv2)
-
-# FP for insecure default
-ssl.SSLContext(ssl.SSLv23_METHOD)
+SSL.Context(SSL.SSLv23_METHOD)
From 731f4559b415dce07d8118707a42081bcba42c08 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 15 Mar 2021 17:23:58 +0100
Subject: [PATCH 0047/1429] Python: update test expectations
---
.../Security/CWE-327/InsecureDefaultProtocol.expected | 2 --
1 file changed, 2 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureDefaultProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureDefaultProtocol.expected
index 1e389aefdc1..e69de29bb2d 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureDefaultProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureDefaultProtocol.expected
@@ -1,2 +0,0 @@
-| InsecureProtocol.py:41:1:41:17 | ControlFlowNode for Attribute() | Call to deprecated method ssl.wrap_socket does not specify a protocol, which may result in an insecure default being used. |
-| InsecureProtocol.py:42:11:42:22 | ControlFlowNode for SSLContext() | Call to ssl.SSLContext does not specify a protocol, which may result in an insecure default being used. |
From 87f3ba26843b8f861df47b9ca03226926aa58b5b Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 15 Mar 2021 17:24:39 +0100
Subject: [PATCH 0048/1429] Python: add tests for `ssl.PROTOCOL_TLS_SERVER` and
`ssl.PROTOCOL_TLS_CLIENT`
---
.../CWE-327/InsecureProtocol.expected | 36 +++++++++----------
.../Security/CWE-327/ssl_fluent.py | 18 ++++++++++
2 files changed, 36 insertions(+), 18 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index db30e41b480..3f383ea5297 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -22,21 +22,21 @@
| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:19:14:19:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:15:15:15:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:47:14:47:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:43:15:43:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:144:5:144:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:162:5:162:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:124:14:124:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:122:5:122:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:173:14:173:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:170:5:170:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | ssl_fluent.py:189:5:189:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:188:15:188:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
-| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:188:15:188:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| ssl_fluent.py:55:14:55:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:52:15:52:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:55:14:55:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:52:15:52:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:65:14:65:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:61:15:61:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:93:14:93:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:89:15:89:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:93:14:93:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:89:15:89:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:107:12:107:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:146:15:146:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:107:12:107:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:146:15:146:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:162:5:162:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:180:5:180:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:122:14:122:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:107:12:107:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:122:14:122:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:107:12:107:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:142:14:142:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:140:5:140:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:191:14:191:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:188:5:188:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:210:14:210:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | ssl_fluent.py:207:5:207:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:210:14:210:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:206:15:206:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| ssl_fluent.py:210:14:210:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:206:15:206:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
diff --git a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
index 577f342765e..ceddaa32f09 100644
--- a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
@@ -19,6 +19,24 @@ def test_fluent_tls_no_TLSv1():
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
print(ssock.version())
+def test_fluent_tls_client_no_TLSv1():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ context.options |= ssl.OP_NO_TLSv1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_tls_server_no_TLSv1():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ context.options |= ssl.OP_NO_TLSv1
+
+ with socket.create_server((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
def test_fluent_tls_safe():
hostname = 'www.python.org'
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
From 514a69c47ad79318883483f159662503bc7358b0 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 15 Mar 2021 17:30:01 +0100
Subject: [PATCH 0049/1429] Python: Support `ssl.PROTOCOL_TLS_SERVER` and
`ssl.PROTOCOL_TLS_CLIENT`
---
python/ql/src/Security/CWE-327/Ssl.qll | 6 +++++-
.../query-tests/Security/CWE-327/InsecureProtocol.expected | 2 ++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index d4886219d08..be16138b961 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -146,7 +146,11 @@ class Ssl extends TlsLibrary {
override string specific_version_name(ProtocolVersion version) { result = "PROTOCOL_" + version }
- override string unspecific_version_name(ProtocolFamily family) { result = "PROTOCOL_" + family }
+ override string unspecific_version_name(ProtocolFamily family) {
+ family = "SSLv23" and result = "PROTOCOL_" + family
+ or
+ family = "TLS" and result = "PROTOCOL_" + family + ["", "_CLIENT", "_SERVER"]
+ }
override API::Node version_constants() { result = API::moduleImport("ssl") }
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index 3f383ea5297..e578a335d84 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -22,6 +22,8 @@
| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:19:14:19:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:15:15:15:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:28:14:28:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:24:15:24:53 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:33:15:33:53 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:55:14:55:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:52:15:52:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:55:14:55:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:52:15:52:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:65:14:65:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:61:15:61:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
From 9a962305236c194d18d80d6321bf11ddc6eabe8b Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 15 Mar 2021 17:35:30 +0100
Subject: [PATCH 0050/1429] Python: Add changenote
---
python/change-notes/2021-03-15-port-insecure-protocol.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 python/change-notes/2021-03-15-port-insecure-protocol.md
diff --git a/python/change-notes/2021-03-15-port-insecure-protocol.md b/python/change-notes/2021-03-15-port-insecure-protocol.md
new file mode 100644
index 00000000000..c92f387b29a
--- /dev/null
+++ b/python/change-notes/2021-03-15-port-insecure-protocol.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Ported use of insecure SSL/TLS version (`py/insecure-protocol`) query to use new data-flow library. This might result in different results, but overall a more robust and accurate analysis.
From 98204a15a6ad8c8dd5dfb42269bfdd153cfc1827 Mon Sep 17 00:00:00 2001
From: haby0
Date: Wed, 17 Mar 2021 15:28:04 +0800
Subject: [PATCH 0051/1429] Fix the problem
---
.../Security/CWE/CWE-352/JsonStringLib.qll | 2 +-
.../Security/CWE/CWE-352/JsonpInjection.java | 117 ++++++----
.../Security/CWE/CWE-352/JsonpInjection.qhelp | 11 +-
.../Security/CWE/CWE-352/JsonpInjection.ql | 24 ++-
.../CWE/CWE-352/JsonpInjectionLib.qll | 66 +++---
.../Security/CWE/CWE-598/SensitiveGetQuery.ql | 10 -
.../semmle/code/java/frameworks/Servlets.qll | 11 +
.../security/CWE-352/JsonpInjection.qlref | 1 -
.../JsonpController.java | 105 +++++++--
.../JsonpInjection.expected | 87 ++++++++
.../JsonpInjection.qlref | 1 +
.../JsonpInjectionServlet1.java | 0
.../JsonpInjectionServlet2.java | 0
.../RefererFilter.java | 0
.../CWE-352/JsonpInjectionWithFilter/options | 1 +
.../JsonpController.java | 107 +++++++--
.../JsonpInjection.expected | 76 +++++++
.../JsonpInjection.qlref | 1 +
.../options | 1 +
.../JsonpController.java | 203 ++++++++++++++++++
.../JsonpInjection.expected | 92 ++++++++
.../JsonpInjection.qlref | 1 +
.../JsonpInjectionServlet1.java | 0
.../JsonpInjectionServlet2.java | 0
.../options | 1 +
.../CWE-352/JsonpInjection_1.expected | 60 ------
.../CWE-352/JsonpInjection_2.expected | 78 -------
.../CWE-352/JsonpInjection_3.expected | 66 ------
.../query-tests/security/CWE-352/Readme | 3 -
.../security/CWE-352/RefererFilter.java | 43 ----
.../query-tests/security/CWE-352/options | 1 -
.../core/annotation/AliasFor.java | 13 +-
.../core/io/InputStreamSource.java | 8 +
.../org/springframework/core/io/Resource.java | 46 ++++
.../org/springframework/lang/Nullable.java | 13 ++
.../springframework/util/FileCopyUtils.java | 53 +++++
.../org/springframework/util/StringUtils.java | 2 +-
.../web/bind/annotation/GetMapping.java | 36 +++-
.../web/bind/annotation/Mapping.java | 4 +
.../web/bind/annotation/RequestMapping.java | 19 +-
.../web/bind/annotation/RequestParam.java | 23 ++
.../web/bind/annotation/ResponseBody.java | 9 +
.../web/multipart/MultipartFile.java | 38 ++++
.../javax/servlet/annotation/WebServlet.java | 30 +++
44 files changed, 1075 insertions(+), 388 deletions(-)
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref
rename java/ql/test/experimental/query-tests/security/CWE-352/{ => JsonpInjectionWithFilter}/JsonpController.java (54%)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.qlref
rename java/ql/{src/experimental/Security/CWE/CWE-352 => test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter}/JsonpInjectionServlet1.java (100%)
rename java/ql/{src/experimental/Security/CWE/CWE-352 => test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter}/JsonpInjectionServlet2.java (100%)
rename java/ql/{src/experimental/Security/CWE/CWE-352 => test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter}/RefererFilter.java (100%)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/options
rename java/ql/{src/experimental/Security/CWE/CWE-352 => test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController}/JsonpController.java (54%)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.qlref
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/options
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.qlref
rename java/ql/test/experimental/query-tests/security/CWE-352/{ => JsonpInjectionWithSpringControllerAndServlet}/JsonpInjectionServlet1.java (100%)
rename java/ql/test/experimental/query-tests/security/CWE-352/{ => JsonpInjectionWithSpringControllerAndServlet}/JsonpInjectionServlet2.java (100%)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/options
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/Readme
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/options
create mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/InputStreamSource.java
create mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/Resource.java
create mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/lang/Nullable.java
create mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/FileCopyUtils.java
create mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/Mapping.java
create mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestParam.java
create mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/multipart/MultipartFile.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/annotation/WebServlet.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll b/java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll
index 0da8bc860d1..5cc52e97e33 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll
@@ -3,7 +3,7 @@ import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
-/** Json string type data */
+/** Json string type data. */
abstract class JsonpStringSource extends DataFlow::Node { }
/** Convert to String using Gson library. */
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
index 8b4e7cc005e..7f479a8c023 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
@@ -1,31 +1,39 @@
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.HashMap;
-import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
@Controller
public class JsonpInjection {
-private static HashMap hashMap = new HashMap();
+
+ private static HashMap hashMap = new HashMap();
static {
hashMap.put("username","admin");
hashMap.put("password","123456");
}
+ private String name = null;
+
@GetMapping(value = "jsonp1")
@ResponseBody
public String bad1(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
Gson gson = new Gson();
String result = gson.toJson(hashMap);
resultStr = jsonpCallback + "(" + result + ")";
@@ -37,9 +45,7 @@ private static HashMap hashMap = new HashMap();
public String bad2(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
-
return resultStr;
}
@@ -67,7 +73,6 @@ private static HashMap hashMap = new HashMap();
@ResponseBody
public void bad5(HttpServletRequest request,
HttpServletResponse response) throws Exception {
- response.setContentType("application/json");
String jsonpCallback = request.getParameter("jsonpCallback");
PrintWriter pw = null;
Gson gson = new Gson();
@@ -83,7 +88,6 @@ private static HashMap hashMap = new HashMap();
@ResponseBody
public void bad6(HttpServletRequest request,
HttpServletResponse response) throws Exception {
- response.setContentType("application/json");
String jsonpCallback = request.getParameter("jsonpCallback");
PrintWriter pw = null;
ObjectMapper mapper = new ObjectMapper();
@@ -94,60 +98,96 @@ private static HashMap hashMap = new HashMap();
pw.println(resultStr);
}
- @GetMapping(value = "jsonp7")
+ @RequestMapping(value = "jsonp7", method = RequestMethod.GET)
@ResponseBody
- public String good(HttpServletRequest request) {
+ public String bad7(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
- String val = "";
- Random random = new Random();
- for (int i = 0; i < 10; i++) {
- val += String.valueOf(random.nextInt(10));
- }
- // good
- jsonpCallback = jsonpCallback + "_" + val;
- String jsonStr = getJsonStr(hashMap);
- resultStr = jsonpCallback + "(" + jsonStr + ")";
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
return resultStr;
}
+
@GetMapping(value = "jsonp8")
@ResponseBody
public String good1(HttpServletRequest request) {
String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
String token = request.getParameter("token");
-
- // good
if (verifToken(token)){
- System.out.println(token);
+ String jsonpCallback = request.getParameter("jsonpCallback");
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
return resultStr;
}
-
return "error";
}
+
@GetMapping(value = "jsonp9")
@ResponseBody
public String good2(HttpServletRequest request) {
String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
- String referer = request.getHeader("Referer");
-
- boolean result = verifReferer(referer);
- // good
+ String token = request.getParameter("token");
+ boolean result = verifToken(token);
if (result){
- String jsonStr = getJsonStr(hashMap);
- resultStr = jsonpCallback + "(" + jsonStr + ")";
- return resultStr;
+ return "";
}
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
- return "error";
+ @RequestMapping(value = "jsonp10")
+ @ResponseBody
+ public String good3(HttpServletRequest request) {
+ JSONObject parameterObj = readToJSONObect(request);
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp11")
+ @ResponseBody
+ public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
+ if(null == file){
+ return "upload file error";
+ }
+ String fileName = file.getOriginalFilename();
+ System.out.println("file operations");
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ public static JSONObject readToJSONObect(HttpServletRequest request){
+ String jsonText = readPostContent(request);
+ JSONObject jsonObj = JSONObject.parseObject(jsonText, JSONObject.class);
+ return jsonObj;
+ }
+
+ public static String readPostContent(HttpServletRequest request){
+ BufferedReader in= null;
+ String content = null;
+ String line = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
+ StringBuilder buf = new StringBuilder();
+ while ((line = in.readLine()) != null) {
+ buf.append(line);
+ }
+ content = buf.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ String uri = request.getRequestURI();
+ return content;
}
public static String getJsonStr(Object result) {
@@ -160,11 +200,4 @@ private static HashMap hashMap = new HashMap();
}
return true;
}
-
- public static boolean verifReferer(String referer){
- if (!referer.startsWith("http://test.com/")){
- return false;
- }
- return true;
- }
}
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
index bb5d628ac0b..93c167d6c2c 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
@@ -3,18 +3,21 @@
"qhelp.dtd">
-The software uses external input as the function name to wrap JSON data and return it to the client as a request response. When there is a cross-domain problem,
-there is a problem of sensitive information leakage.
+The software uses external input as the function name to wrap JSON data and returns it to the client as a request response.
+When there is a cross-domain problem, the problem of sensitive information leakage may occur.
-Adding `Referer` or random `token` verification processing can effectively prevent the leakage of sensitive information.
+Adding Referer/Origin or random token verification processing can effectively prevent the leakage of sensitive information.
-The following example shows the case of no verification processing and verification processing for the external input function name.
+The following examples show the bad case and the good case respectively. Bad case, such as bad1 to bad7,
+will cause information leakage problems when there are cross-domain problems. In a good case, for example, in the good1
+method and the good2 method, use the verifToken method to do the random token Verification can
+solve the problem of information leakage caused by cross-domain.
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
index f3ae25daa03..068469328ea 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
@@ -14,25 +14,25 @@ import java
import JsonpInjectionLib
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.deadcode.WebEntryPoints
-import semmle.code.java.security.XSS
import DataFlow::PathGraph
/** Determine whether there is a verification method for the remote streaming source data flow path method. */
predicate existsFilterVerificationMethod() {
- exists(MethodAccess ma,Node existsNode, Method m|
+ exists(MethodAccess ma, Node existsNode, Method m |
ma.getMethod() instanceof VerificationMethodClass and
existsNode.asExpr() = ma and
- m = getAnMethod(existsNode.getEnclosingCallable()) and
+ m = getACallingCallableOrSelf(existsNode.getEnclosingCallable()) and
isDoFilterMethod(m)
)
}
/** Determine whether there is a verification method for the remote streaming source data flow path method. */
predicate existsServletVerificationMethod(Node checkNode) {
- exists(MethodAccess ma,Node existsNode|
+ exists(MethodAccess ma, Node existsNode |
ma.getMethod() instanceof VerificationMethodClass and
existsNode.asExpr() = ma and
- getAnMethod(existsNode.getEnclosingCallable()) = getAnMethod(checkNode.getEnclosingCallable())
+ getACallingCallableOrSelf(existsNode.getEnclosingCallable()) =
+ getACallingCallableOrSelf(checkNode.getEnclosingCallable())
)
}
@@ -40,13 +40,15 @@ predicate existsServletVerificationMethod(Node checkNode) {
class RequestResponseFlowConfig extends TaintTracking::Configuration {
RequestResponseFlowConfig() { this = "RequestResponseFlowConfig" }
- override predicate isSource(DataFlow::Node source) {
- source instanceof RemoteFlowSource and
- getAnMethod(source.getEnclosingCallable()) instanceof RequestGetMethod
- }
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink }
+ /** Eliminate the method of calling the node is not the get method. */
+ override predicate isSanitizer(DataFlow::Node node) {
+ not getACallingCallableOrSelf(node.getEnclosingCallable()) instanceof RequestGetMethod
+ }
+
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(MethodAccess ma |
isRequestGetParamMethod(ma) and pred.asExpr() = ma.getQualifier() and succ.asExpr() = ma
@@ -60,5 +62,5 @@ where
not existsFilterVerificationMethod() and
conf.hasFlowPath(source, sink) and
exists(JsonpInjectionFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode()))
-select sink.getNode(), source, sink, "Jsonp Injection query might include code from $@.",
- source.getNode(), "this user input"
\ No newline at end of file
+select sink.getNode(), source, sink, "Jsonp response might include code from $@.", source.getNode(),
+ "this user input"
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
index b8964524a9f..d0e00bcb634 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
@@ -6,28 +6,25 @@ import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.spring.SpringController
-/** Taint-tracking configuration tracing flow from user-controllable function name jsonp data to output jsonp data. */
+/** Taint-tracking configuration tracing flow from untrusted inputs to verification of remote user input. */
class VerificationMethodFlowConfig extends TaintTracking::Configuration {
VerificationMethodFlowConfig() { this = "VerificationMethodFlowConfig" }
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma, BarrierGuard bg |
+ exists(MethodAccess ma |
ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- bg = ma and
- sink.asExpr() = ma.getAnArgument()
+ ma.getAnArgument() = sink.asExpr()
)
}
}
-/** The parameter name of the method is `token`, `auth`, `referer`, `origin`. */
+/** The parameter names of this method are token/auth/referer/origin. */
class VerificationMethodClass extends Method {
VerificationMethodClass() {
- exists(MethodAccess ma, BarrierGuard bg, VerificationMethodFlowConfig vmfc, Node node |
+ exists(MethodAccess ma, VerificationMethodFlowConfig vmfc, Node node |
this = ma.getMethod() and
- this.getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- bg = ma and
node.asExpr() = ma.getAnArgument() and
vmfc.hasFlowTo(node)
)
@@ -35,38 +32,43 @@ class VerificationMethodClass extends Method {
}
/** Get Callable by recursive method. */
-Callable getAnMethod(Callable call) {
+Callable getACallingCallableOrSelf(Callable call) {
result = call
or
- result = getAnMethod(call.getAReference().getEnclosingCallable())
+ result = getACallingCallableOrSelf(call.getAReference().getEnclosingCallable())
}
abstract class RequestGetMethod extends Method { }
-/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
+/** Override method of `doGet` of `Servlet` subclass. */
private class ServletGetMethod extends RequestGetMethod {
- ServletGetMethod() {
- exists(Method m |
- m = this and
- isServletRequestMethod(m) and
- m.getName() = "doGet"
- )
+ ServletGetMethod() { this instanceof DoGetServletMethod }
+}
+
+/** The method of SpringController class processing `get` request. */
+abstract class SpringControllerGetMethod extends RequestGetMethod { }
+
+/** Method using `GetMapping` annotation in SpringController class. */
+class SpringControllerGetMappingGetMethod extends SpringControllerGetMethod {
+ SpringControllerGetMappingGetMethod() {
+ this.getAnAnnotation()
+ .getType()
+ .hasQualifiedName("org.springframework.web.bind.annotation", "GetMapping")
}
}
-/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
-private class SpringControllerGetMethod extends RequestGetMethod {
- SpringControllerGetMethod() {
- exists(Annotation a |
- a = this.getAnAnnotation() and
- a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "GetMapping")
- )
- or
- exists(Annotation a |
- a = this.getAnAnnotation() and
- a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and
- a.getValue("method").toString().regexpMatch("RequestMethod.GET|\\{...\\}")
- )
+/** The method that uses the `RequestMapping` annotation in the SpringController class and only handles the get request. */
+class SpringControllerRequestMappingGetMethod extends SpringControllerGetMethod {
+ SpringControllerRequestMappingGetMethod() {
+ this.getAnAnnotation()
+ .getType()
+ .hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and
+ this.getAnAnnotation().getValue("method").toString().regexpMatch("RequestMethod.GET|\\{...\\}") and
+ not exists(MethodAccess ma |
+ ma.getMethod() instanceof ServletRequestGetBodyMethod and
+ this = getACallingCallableOrSelf(ma.getEnclosingCallable())
+ ) and
+ not this.getAParamType().getName() = "MultipartFile"
}
}
@@ -83,12 +85,12 @@ class JsonpInjectionExpr extends AddExpr {
.regexpMatch("\"\\(\"")
}
- /** Get the jsonp function name of this expression */
+ /** Get the jsonp function name of this expression. */
Expr getFunctionName() {
result = getLeftOperand().(AddExpr).getLeftOperand().(AddExpr).getLeftOperand()
}
- /** Get the json data of this expression */
+ /** Get the json data of this expression. */
Expr getJsonExpr() { result = getLeftOperand().(AddExpr).getRightOperand() }
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql b/java/ql/src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql
index bc9850cfddb..c381595af14 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql
@@ -23,16 +23,6 @@ class SensitiveInfoExpr extends Expr {
}
}
-/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
-private predicate isGetServletMethod(Method m) {
- isServletRequestMethod(m) and m.getName() = "doGet"
-}
-
-/** The `doGet` method of `HttpServlet`. */
-class DoGetServletMethod extends Method {
- DoGetServletMethod() { isGetServletMethod(this) }
-}
-
/** Holds if `ma` is (perhaps indirectly) called from the `doGet` method of `HttpServlet`. */
predicate isReachableFromServletDoGet(MethodAccess ma) {
ma.getEnclosingCallable() instanceof DoGetServletMethod
diff --git a/java/ql/src/semmle/code/java/frameworks/Servlets.qll b/java/ql/src/semmle/code/java/frameworks/Servlets.qll
index 5cccf62122f..7ac452affa9 100644
--- a/java/ql/src/semmle/code/java/frameworks/Servlets.qll
+++ b/java/ql/src/semmle/code/java/frameworks/Servlets.qll
@@ -354,9 +354,20 @@ class FilterChain extends Interface {
/** Holds if `m` is a filter handler method (for example `doFilter`). */
predicate isDoFilterMethod(Method m) {
+ m.getName().matches("doFilter") and
m.getDeclaringType() instanceof FilterClass and
m.getNumberOfParameters() = 3 and
m.getParameter(0).getType() instanceof ServletRequest and
m.getParameter(1).getType() instanceof ServletResponse and
m.getParameter(2).getType() instanceof FilterChain
}
+
+/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
+predicate isGetServletMethod(Method m) {
+ isServletRequestMethod(m) and m.getName() = "doGet"
+}
+
+/** The `doGet` method of `HttpServlet`. */
+class DoGetServletMethod extends Method {
+ DoGetServletMethod() { isGetServletMethod(this) }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref
deleted file mode 100644
index 6ad4b8acda7..00000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Security/CWE/CWE-352/JsonpInjection.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpController.java
similarity index 54%
rename from java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpController.java
index cf860c75640..e5b5e70a38d 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpController.java
@@ -1,16 +1,24 @@
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
@Controller
public class JsonpController {
+
private static HashMap hashMap = new HashMap();
static {
@@ -18,13 +26,14 @@ public class JsonpController {
hashMap.put("password","123456");
}
+ private String name = null;
- @GetMapping(value = "jsonp1", produces="text/javascript")
+
+ @GetMapping(value = "jsonp1")
@ResponseBody
public String bad1(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
Gson gson = new Gson();
String result = gson.toJson(hashMap);
resultStr = jsonpCallback + "(" + result + ")";
@@ -36,9 +45,7 @@ public class JsonpController {
public String bad2(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
-
return resultStr;
}
@@ -91,23 +98,98 @@ public class JsonpController {
pw.println(resultStr);
}
- @GetMapping(value = "jsonp7")
+ @RequestMapping(value = "jsonp7", method = RequestMethod.GET)
+ @ResponseBody
+ public String bad7(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
+
+
+ @GetMapping(value = "jsonp8")
@ResponseBody
public String good1(HttpServletRequest request) {
String resultStr = null;
-
String token = request.getParameter("token");
-
if (verifToken(token)){
String jsonpCallback = request.getParameter("jsonpCallback");
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
return resultStr;
}
-
return "error";
}
+
+ @GetMapping(value = "jsonp9")
+ @ResponseBody
+ public String good2(HttpServletRequest request) {
+ String resultStr = null;
+ String token = request.getParameter("token");
+ boolean result = verifToken(token);
+ if (result){
+ return "";
+ }
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp10")
+ @ResponseBody
+ public String good3(HttpServletRequest request) {
+ JSONObject parameterObj = readToJSONObect(request);
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp11")
+ @ResponseBody
+ public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
+ if(null == file){
+ return "upload file error";
+ }
+ String fileName = file.getOriginalFilename();
+ System.out.println("file operations");
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ public static JSONObject readToJSONObect(HttpServletRequest request){
+ String jsonText = readPostContent(request);
+ JSONObject jsonObj = JSONObject.parseObject(jsonText, JSONObject.class);
+ return jsonObj;
+ }
+
+ public static String readPostContent(HttpServletRequest request){
+ BufferedReader in= null;
+ String content = null;
+ String line = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
+ StringBuilder buf = new StringBuilder();
+ while ((line = in.readLine()) != null) {
+ buf.append(line);
+ }
+ content = buf.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ String uri = request.getRequestURI();
+ return content;
+ }
+
public static String getJsonStr(Object result) {
return JSONObject.toJSONString(result);
}
@@ -118,11 +200,4 @@ public class JsonpController {
}
return true;
}
-
- public static boolean verifReferer(String referer){
- if (!referer.startsWith("http://test.com/")){
- return false;
- }
- return true;
- }
}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected
new file mode 100644
index 00000000000..501565f2b4e
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected
@@ -0,0 +1,87 @@
+edges
+| JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr |
+| JsonpController.java:39:21:39:54 | ... + ... : String | JsonpController.java:40:16:40:24 | resultStr |
+| JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr |
+| JsonpController.java:48:21:48:80 | ... + ... : String | JsonpController.java:49:16:49:24 | resultStr |
+| JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr |
+| JsonpController.java:58:21:58:55 | ... + ... : String | JsonpController.java:59:16:59:24 | resultStr |
+| JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr |
+| JsonpController.java:68:21:68:54 | ... + ... : String | JsonpController.java:69:16:69:24 | resultStr |
+| JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr |
+| JsonpController.java:83:21:83:54 | ... + ... : String | JsonpController.java:84:20:84:28 | resultStr |
+| JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr |
+| JsonpController.java:97:21:97:54 | ... + ... : String | JsonpController.java:98:20:98:28 | resultStr |
+| JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr |
+| JsonpController.java:108:21:108:54 | ... + ... : String | JsonpController.java:109:16:109:24 | resultStr |
+| JsonpController.java:117:24:117:52 | getParameter(...) : String | JsonpController.java:118:24:118:28 | token |
+| JsonpController.java:119:36:119:72 | getParameter(...) : String | JsonpController.java:122:20:122:28 | resultStr |
+| JsonpController.java:121:25:121:59 | ... + ... : String | JsonpController.java:122:20:122:28 | resultStr |
+| JsonpController.java:132:24:132:52 | getParameter(...) : String | JsonpController.java:133:37:133:41 | token |
+| JsonpController.java:137:32:137:68 | getParameter(...) : String | JsonpController.java:140:16:140:24 | resultStr |
+| JsonpController.java:139:21:139:55 | ... + ... : String | JsonpController.java:140:16:140:24 | resultStr |
+| JsonpController.java:150:21:150:54 | ... + ... : String | JsonpController.java:151:16:151:24 | resultStr |
+| JsonpController.java:165:21:165:54 | ... + ... : String | JsonpController.java:166:16:166:24 | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+| RefererFilter.java:22:26:22:53 | getHeader(...) : String | RefererFilter.java:23:39:23:45 | refefer |
+nodes
+| JsonpController.java:36:32:36:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:39:21:39:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:47:32:47:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:48:21:48:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:56:32:56:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:58:21:58:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:66:32:66:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:68:21:68:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:76:32:76:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:83:21:83:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:91:32:91:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:97:21:97:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:32:105:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:108:21:108:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:117:24:117:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:118:24:118:28 | token | semmle.label | token |
+| JsonpController.java:119:36:119:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:121:25:121:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:132:24:132:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:133:37:133:41 | token | semmle.label | token |
+| JsonpController.java:137:32:137:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:139:21:139:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:150:21:150:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:151:16:151:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:165:21:165:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:166:16:166:24 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+| RefererFilter.java:22:26:22:53 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| RefererFilter.java:23:39:23:45 | refefer | semmle.label | refefer |
+#select
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.qlref
new file mode 100644
index 00000000000..3f5fc450669
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-352/JsonpInjection.ql
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet1.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet1.java
similarity index 100%
rename from java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet1.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet1.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet2.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet2.java
similarity index 100%
rename from java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet2.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet2.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/RefererFilter.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/RefererFilter.java
similarity index 100%
rename from java/ql/src/experimental/Security/CWE/CWE-352/RefererFilter.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/RefererFilter.java
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/options b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/options
new file mode 100644
index 00000000000..c53e31e467f
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/apache-http-4.4.13/:${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/fastjson-1.2.74/:${testdir}/../../../../../stubs/gson-2.8.6/:${testdir}/../../../../../stubs/jackson-databind-2.10/:${testdir}/../../../../../stubs/spring-context-5.3.2/:${testdir}/../../../../../stubs/spring-web-5.3.2/:${testdir}/../../../../../stubs/spring-core-5.3.2/:${testdir}/../../../../../stubs/tomcat-embed-core-9.0.41/
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpController.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpController.java
similarity index 54%
rename from java/ql/src/experimental/Security/CWE/CWE-352/JsonpController.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpController.java
index 84a172a7aeb..e5b5e70a38d 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpController.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpController.java
@@ -1,16 +1,24 @@
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
@Controller
public class JsonpController {
+
private static HashMap hashMap = new HashMap();
static {
@@ -18,13 +26,14 @@ public class JsonpController {
hashMap.put("password","123456");
}
+ private String name = null;
- @GetMapping(value = "jsonp1", produces="text/javascript")
+
+ @GetMapping(value = "jsonp1")
@ResponseBody
public String bad1(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
Gson gson = new Gson();
String result = gson.toJson(hashMap);
resultStr = jsonpCallback + "(" + result + ")";
@@ -36,9 +45,7 @@ public class JsonpController {
public String bad2(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
-
return resultStr;
}
@@ -91,23 +98,98 @@ public class JsonpController {
pw.println(resultStr);
}
- @GetMapping(value = "jsonp7")
+ @RequestMapping(value = "jsonp7", method = RequestMethod.GET)
+ @ResponseBody
+ public String bad7(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
+
+
+ @GetMapping(value = "jsonp8")
@ResponseBody
public String good1(HttpServletRequest request) {
String resultStr = null;
-
String token = request.getParameter("token");
-
if (verifToken(token)){
String jsonpCallback = request.getParameter("jsonpCallback");
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
return resultStr;
}
-
return "error";
}
+
+ @GetMapping(value = "jsonp9")
+ @ResponseBody
+ public String good2(HttpServletRequest request) {
+ String resultStr = null;
+ String token = request.getParameter("token");
+ boolean result = verifToken(token);
+ if (result){
+ return "";
+ }
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp10")
+ @ResponseBody
+ public String good3(HttpServletRequest request) {
+ JSONObject parameterObj = readToJSONObect(request);
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp11")
+ @ResponseBody
+ public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
+ if(null == file){
+ return "upload file error";
+ }
+ String fileName = file.getOriginalFilename();
+ System.out.println("file operations");
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ public static JSONObject readToJSONObect(HttpServletRequest request){
+ String jsonText = readPostContent(request);
+ JSONObject jsonObj = JSONObject.parseObject(jsonText, JSONObject.class);
+ return jsonObj;
+ }
+
+ public static String readPostContent(HttpServletRequest request){
+ BufferedReader in= null;
+ String content = null;
+ String line = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
+ StringBuilder buf = new StringBuilder();
+ while ((line = in.readLine()) != null) {
+ buf.append(line);
+ }
+ content = buf.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ String uri = request.getRequestURI();
+ return content;
+ }
+
public static String getJsonStr(Object result) {
return JSONObject.toJSONString(result);
}
@@ -118,11 +200,4 @@ public class JsonpController {
}
return true;
}
-
- public static boolean verifReferer(String referer){
- if (!referer.startsWith("http://test.com/")){
- return false;
- }
- return true;
- }
-}
\ No newline at end of file
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected
new file mode 100644
index 00000000000..91d23cebbda
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected
@@ -0,0 +1,76 @@
+edges
+| JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr |
+| JsonpController.java:39:21:39:54 | ... + ... : String | JsonpController.java:40:16:40:24 | resultStr |
+| JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr |
+| JsonpController.java:48:21:48:80 | ... + ... : String | JsonpController.java:49:16:49:24 | resultStr |
+| JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr |
+| JsonpController.java:58:21:58:55 | ... + ... : String | JsonpController.java:59:16:59:24 | resultStr |
+| JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr |
+| JsonpController.java:68:21:68:54 | ... + ... : String | JsonpController.java:69:16:69:24 | resultStr |
+| JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr |
+| JsonpController.java:83:21:83:54 | ... + ... : String | JsonpController.java:84:20:84:28 | resultStr |
+| JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr |
+| JsonpController.java:97:21:97:54 | ... + ... : String | JsonpController.java:98:20:98:28 | resultStr |
+| JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr |
+| JsonpController.java:108:21:108:54 | ... + ... : String | JsonpController.java:109:16:109:24 | resultStr |
+| JsonpController.java:117:24:117:52 | getParameter(...) : String | JsonpController.java:118:24:118:28 | token |
+| JsonpController.java:119:36:119:72 | getParameter(...) : String | JsonpController.java:122:20:122:28 | resultStr |
+| JsonpController.java:121:25:121:59 | ... + ... : String | JsonpController.java:122:20:122:28 | resultStr |
+| JsonpController.java:132:24:132:52 | getParameter(...) : String | JsonpController.java:133:37:133:41 | token |
+| JsonpController.java:137:32:137:68 | getParameter(...) : String | JsonpController.java:140:16:140:24 | resultStr |
+| JsonpController.java:139:21:139:55 | ... + ... : String | JsonpController.java:140:16:140:24 | resultStr |
+| JsonpController.java:150:21:150:54 | ... + ... : String | JsonpController.java:151:16:151:24 | resultStr |
+| JsonpController.java:165:21:165:54 | ... + ... : String | JsonpController.java:166:16:166:24 | resultStr |
+nodes
+| JsonpController.java:36:32:36:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:39:21:39:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:47:32:47:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:48:21:48:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:56:32:56:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:58:21:58:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:66:32:66:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:68:21:68:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:76:32:76:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:83:21:83:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:91:32:91:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:97:21:97:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:32:105:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:108:21:108:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:117:24:117:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:118:24:118:28 | token | semmle.label | token |
+| JsonpController.java:119:36:119:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:121:25:121:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:132:24:132:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:133:37:133:41 | token | semmle.label | token |
+| JsonpController.java:137:32:137:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:139:21:139:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:150:21:150:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:151:16:151:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:165:21:165:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:166:16:166:24 | resultStr | semmle.label | resultStr |
+#select
+| JsonpController.java:40:16:40:24 | resultStr | JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:36:32:36:68 | getParameter(...) | this user input |
+| JsonpController.java:49:16:49:24 | resultStr | JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:47:32:47:68 | getParameter(...) | this user input |
+| JsonpController.java:59:16:59:24 | resultStr | JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:56:32:56:68 | getParameter(...) | this user input |
+| JsonpController.java:69:16:69:24 | resultStr | JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:66:32:66:68 | getParameter(...) | this user input |
+| JsonpController.java:84:20:84:28 | resultStr | JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:76:32:76:68 | getParameter(...) | this user input |
+| JsonpController.java:98:20:98:28 | resultStr | JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:91:32:91:68 | getParameter(...) | this user input |
+| JsonpController.java:109:16:109:24 | resultStr | JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:105:32:105:68 | getParameter(...) | this user input |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.qlref
new file mode 100644
index 00000000000..3f5fc450669
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-352/JsonpInjection.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/options b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/options
new file mode 100644
index 00000000000..c53e31e467f
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/apache-http-4.4.13/:${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/fastjson-1.2.74/:${testdir}/../../../../../stubs/gson-2.8.6/:${testdir}/../../../../../stubs/jackson-databind-2.10/:${testdir}/../../../../../stubs/spring-context-5.3.2/:${testdir}/../../../../../stubs/spring-web-5.3.2/:${testdir}/../../../../../stubs/spring-core-5.3.2/:${testdir}/../../../../../stubs/tomcat-embed-core-9.0.41/
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java
new file mode 100644
index 00000000000..e5b5e70a38d
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java
@@ -0,0 +1,203 @@
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
+
+@Controller
+public class JsonpController {
+
+ private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+ private String name = null;
+
+
+ @GetMapping(value = "jsonp1")
+ @ResponseBody
+ public String bad1(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp2")
+ @ResponseBody
+ public String bad2(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp3")
+ @ResponseBody
+ public String bad3(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp4")
+ @ResponseBody
+ public String bad4(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp5")
+ @ResponseBody
+ public void bad5(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp6")
+ @ResponseBody
+ public void bad6(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ ObjectMapper mapper = new ObjectMapper();
+ String result = mapper.writeValueAsString(hashMap);
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @RequestMapping(value = "jsonp7", method = RequestMethod.GET)
+ @ResponseBody
+ public String bad7(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
+
+
+ @GetMapping(value = "jsonp8")
+ @ResponseBody
+ public String good1(HttpServletRequest request) {
+ String resultStr = null;
+ String token = request.getParameter("token");
+ if (verifToken(token)){
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+ return "error";
+ }
+
+
+ @GetMapping(value = "jsonp9")
+ @ResponseBody
+ public String good2(HttpServletRequest request) {
+ String resultStr = null;
+ String token = request.getParameter("token");
+ boolean result = verifToken(token);
+ if (result){
+ return "";
+ }
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp10")
+ @ResponseBody
+ public String good3(HttpServletRequest request) {
+ JSONObject parameterObj = readToJSONObect(request);
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp11")
+ @ResponseBody
+ public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
+ if(null == file){
+ return "upload file error";
+ }
+ String fileName = file.getOriginalFilename();
+ System.out.println("file operations");
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ public static JSONObject readToJSONObect(HttpServletRequest request){
+ String jsonText = readPostContent(request);
+ JSONObject jsonObj = JSONObject.parseObject(jsonText, JSONObject.class);
+ return jsonObj;
+ }
+
+ public static String readPostContent(HttpServletRequest request){
+ BufferedReader in= null;
+ String content = null;
+ String line = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
+ StringBuilder buf = new StringBuilder();
+ while ((line = in.readLine()) != null) {
+ buf.append(line);
+ }
+ content = buf.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ String uri = request.getRequestURI();
+ return content;
+ }
+
+ public static String getJsonStr(Object result) {
+ return JSONObject.toJSONString(result);
+ }
+
+ public static boolean verifToken(String token){
+ if (token != "xxxx"){
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected
new file mode 100644
index 00000000000..c2bcab77d4d
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected
@@ -0,0 +1,92 @@
+edges
+| JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr |
+| JsonpController.java:39:21:39:54 | ... + ... : String | JsonpController.java:40:16:40:24 | resultStr |
+| JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr |
+| JsonpController.java:48:21:48:80 | ... + ... : String | JsonpController.java:49:16:49:24 | resultStr |
+| JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr |
+| JsonpController.java:58:21:58:55 | ... + ... : String | JsonpController.java:59:16:59:24 | resultStr |
+| JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr |
+| JsonpController.java:68:21:68:54 | ... + ... : String | JsonpController.java:69:16:69:24 | resultStr |
+| JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr |
+| JsonpController.java:83:21:83:54 | ... + ... : String | JsonpController.java:84:20:84:28 | resultStr |
+| JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr |
+| JsonpController.java:97:21:97:54 | ... + ... : String | JsonpController.java:98:20:98:28 | resultStr |
+| JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr |
+| JsonpController.java:108:21:108:54 | ... + ... : String | JsonpController.java:109:16:109:24 | resultStr |
+| JsonpController.java:117:24:117:52 | getParameter(...) : String | JsonpController.java:118:24:118:28 | token |
+| JsonpController.java:119:36:119:72 | getParameter(...) : String | JsonpController.java:122:20:122:28 | resultStr |
+| JsonpController.java:121:25:121:59 | ... + ... : String | JsonpController.java:122:20:122:28 | resultStr |
+| JsonpController.java:132:24:132:52 | getParameter(...) : String | JsonpController.java:133:37:133:41 | token |
+| JsonpController.java:137:32:137:68 | getParameter(...) : String | JsonpController.java:140:16:140:24 | resultStr |
+| JsonpController.java:139:21:139:55 | ... + ... : String | JsonpController.java:140:16:140:24 | resultStr |
+| JsonpController.java:150:21:150:54 | ... + ... : String | JsonpController.java:151:16:151:24 | resultStr |
+| JsonpController.java:165:21:165:54 | ... + ... : String | JsonpController.java:166:16:166:24 | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+nodes
+| JsonpController.java:36:32:36:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:39:21:39:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:47:32:47:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:48:21:48:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:56:32:56:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:58:21:58:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:66:32:66:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:68:21:68:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:76:32:76:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:83:21:83:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:91:32:91:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:97:21:97:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:32:105:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:108:21:108:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:117:24:117:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:118:24:118:28 | token | semmle.label | token |
+| JsonpController.java:119:36:119:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:121:25:121:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:132:24:132:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:133:37:133:41 | token | semmle.label | token |
+| JsonpController.java:137:32:137:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:139:21:139:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:150:21:150:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:151:16:151:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:165:21:165:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:166:16:166:24 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+#select
+| JsonpController.java:40:16:40:24 | resultStr | JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:36:32:36:68 | getParameter(...) | this user input |
+| JsonpController.java:49:16:49:24 | resultStr | JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:47:32:47:68 | getParameter(...) | this user input |
+| JsonpController.java:59:16:59:24 | resultStr | JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:56:32:56:68 | getParameter(...) | this user input |
+| JsonpController.java:69:16:69:24 | resultStr | JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:66:32:66:68 | getParameter(...) | this user input |
+| JsonpController.java:84:20:84:28 | resultStr | JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:76:32:76:68 | getParameter(...) | this user input |
+| JsonpController.java:98:20:98:28 | resultStr | JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:91:32:91:68 | getParameter(...) | this user input |
+| JsonpController.java:109:16:109:24 | resultStr | JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:105:32:105:68 | getParameter(...) | this user input |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr | Jsonp response might include code from $@. | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) | this user input |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.qlref
new file mode 100644
index 00000000000..3f5fc450669
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-352/JsonpInjection.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet1.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet1.java
similarity index 100%
rename from java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet1.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet1.java
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet2.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet2.java
similarity index 100%
rename from java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet2.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet2.java
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/options b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/options
new file mode 100644
index 00000000000..c53e31e467f
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/apache-http-4.4.13/:${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/fastjson-1.2.74/:${testdir}/../../../../../stubs/gson-2.8.6/:${testdir}/../../../../../stubs/jackson-databind-2.10/:${testdir}/../../../../../stubs/spring-context-5.3.2/:${testdir}/../../../../../stubs/spring-web-5.3.2/:${testdir}/../../../../../stubs/spring-core-5.3.2/:${testdir}/../../../../../stubs/tomcat-embed-core-9.0.41/
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected
deleted file mode 100644
index a89d03b67a7..00000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected
+++ /dev/null
@@ -1,60 +0,0 @@
-edges
-| JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 | resultStr |
-| JsonpController.java:30:21:30:54 | ... + ... : String | JsonpController.java:31:16:31:24 | resultStr |
-| JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 | resultStr |
-| JsonpController.java:40:21:40:80 | ... + ... : String | JsonpController.java:42:16:42:24 | resultStr |
-| JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 | resultStr |
-| JsonpController.java:51:21:51:55 | ... + ... : String | JsonpController.java:52:16:52:24 | resultStr |
-| JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 | resultStr |
-| JsonpController.java:61:21:61:54 | ... + ... : String | JsonpController.java:62:16:62:24 | resultStr |
-| JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 | resultStr |
-| JsonpController.java:76:21:76:54 | ... + ... : String | JsonpController.java:77:20:77:28 | resultStr |
-| JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 | resultStr |
-| JsonpController.java:90:21:90:54 | ... + ... : String | JsonpController.java:91:20:91:28 | resultStr |
-| JsonpController.java:99:24:99:52 | getParameter(...) : String | JsonpController.java:101:24:101:28 | token |
-| JsonpController.java:102:36:102:72 | getParameter(...) : String | JsonpController.java:105:20:105:28 | resultStr |
-| JsonpController.java:104:25:104:59 | ... + ... : String | JsonpController.java:105:20:105:28 | resultStr |
-nodes
-| JsonpController.java:26:32:26:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:30:21:30:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:38:32:38:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:40:21:40:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:49:32:49:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:51:21:51:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:59:32:59:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:61:21:61:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:69:32:69:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:76:21:76:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:84:32:84:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:90:21:90:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:99:24:99:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:101:24:101:28 | token | semmle.label | token |
-| JsonpController.java:102:36:102:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:104:25:104:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
-#select
-| JsonpController.java:31:16:31:24 | resultStr | JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:26:32:26:68 | getParameter(...) | this user input |
-| JsonpController.java:42:16:42:24 | resultStr | JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:38:32:38:68 | getParameter(...) | this user input |
-| JsonpController.java:52:16:52:24 | resultStr | JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:49:32:49:68 | getParameter(...) | this user input |
-| JsonpController.java:62:16:62:24 | resultStr | JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:59:32:59:68 | getParameter(...) | this user input |
-| JsonpController.java:77:20:77:28 | resultStr | JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:69:32:69:68 | getParameter(...) | this user input |
-| JsonpController.java:91:20:91:28 | resultStr | JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:84:32:84:68 | getParameter(...) | this user input |
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected
deleted file mode 100644
index 4b12308a212..00000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected
+++ /dev/null
@@ -1,78 +0,0 @@
-edges
-| JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 | resultStr |
-| JsonpController.java:30:21:30:54 | ... + ... : String | JsonpController.java:31:16:31:24 | resultStr |
-| JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 | resultStr |
-| JsonpController.java:40:21:40:80 | ... + ... : String | JsonpController.java:42:16:42:24 | resultStr |
-| JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 | resultStr |
-| JsonpController.java:51:21:51:55 | ... + ... : String | JsonpController.java:52:16:52:24 | resultStr |
-| JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 | resultStr |
-| JsonpController.java:61:21:61:54 | ... + ... : String | JsonpController.java:62:16:62:24 | resultStr |
-| JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 | resultStr |
-| JsonpController.java:76:21:76:54 | ... + ... : String | JsonpController.java:77:20:77:28 | resultStr |
-| JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 | resultStr |
-| JsonpController.java:90:21:90:54 | ... + ... : String | JsonpController.java:91:20:91:28 | resultStr |
-| JsonpController.java:99:24:99:52 | getParameter(...) : String | JsonpController.java:101:24:101:28 | token |
-| JsonpController.java:102:36:102:72 | getParameter(...) : String | JsonpController.java:105:20:105:28 | resultStr |
-| JsonpController.java:104:25:104:59 | ... + ... : String | JsonpController.java:105:20:105:28 | resultStr |
-| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
-| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
-| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
-| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
-| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
-nodes
-| JsonpController.java:26:32:26:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:30:21:30:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:38:32:38:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:40:21:40:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:49:32:49:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:51:21:51:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:59:32:59:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:61:21:61:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:69:32:69:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:76:21:76:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:84:32:84:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:90:21:90:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:99:24:99:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:101:24:101:28 | token | semmle.label | token |
-| JsonpController.java:102:36:102:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:104:25:104:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
-| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
-| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
-#select
-| JsonpController.java:31:16:31:24 | resultStr | JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:26:32:26:68 | getParameter(...) | this user input |
-| JsonpController.java:42:16:42:24 | resultStr | JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:38:32:38:68 | getParameter(...) | this user input |
-| JsonpController.java:52:16:52:24 | resultStr | JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:49:32:49:68 | getParameter(...) | this user input |
-| JsonpController.java:62:16:62:24 | resultStr | JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:59:32:59:68 | getParameter(...) | this user input |
-| JsonpController.java:77:20:77:28 | resultStr | JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:69:32:69:68 | getParameter(...) | this user input |
-| JsonpController.java:91:20:91:28 | resultStr | JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:84:32:84:68 | getParameter(...) | this user input |
-| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServle
-t2.java:39:20:39:28 | resultStr | Jsonp Injection query might include code from $@. | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) |
- this user input |
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected
deleted file mode 100644
index 8e33ca6984c..00000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected
+++ /dev/null
@@ -1,66 +0,0 @@
-edges
-| JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 | resultStr |
-| JsonpController.java:30:21:30:54 | ... + ... : String | JsonpController.java:31:16:31:24 | resultStr |
-| JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 | resultStr |
-| JsonpController.java:40:21:40:80 | ... + ... : String | JsonpController.java:42:16:42:24 | resultStr |
-| JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 | resultStr |
-| JsonpController.java:51:21:51:55 | ... + ... : String | JsonpController.java:52:16:52:24 | resultStr |
-| JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 | resultStr |
-| JsonpController.java:61:21:61:54 | ... + ... : String | JsonpController.java:62:16:62:24 | resultStr |
-| JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 | resultStr |
-| JsonpController.java:76:21:76:54 | ... + ... : String | JsonpController.java:77:20:77:28 | resultStr |
-| JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 | resultStr |
-| JsonpController.java:90:21:90:54 | ... + ... : String | JsonpController.java:91:20:91:28 | resultStr |
-| JsonpController.java:99:24:99:52 | getParameter(...) : String | JsonpController.java:101:24:101:28 | token |
-| JsonpController.java:102:36:102:72 | getParameter(...) : String | JsonpController.java:105:20:105:28 | resultStr |
-| JsonpController.java:104:25:104:59 | ... + ... : String | JsonpController.java:105:20:105:28 | resultStr |
-| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
-| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
-| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
-| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
-| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
-| RefererFilter.java:22:26:22:53 | getHeader(...) : String | RefererFilter.java:23:39:23:45 | refefer |
-nodes
-| JsonpController.java:26:32:26:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:30:21:30:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:38:32:38:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:40:21:40:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:49:32:49:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:51:21:51:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:59:32:59:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:61:21:61:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:69:32:69:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:76:21:76:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:84:32:84:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:90:21:90:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:99:24:99:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:101:24:101:28 | token | semmle.label | token |
-| JsonpController.java:102:36:102:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:104:25:104:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
-| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
-| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
-| RefererFilter.java:22:26:22:53 | getHeader(...) : String | semmle.label | getHeader(...) : String |
-| RefererFilter.java:23:39:23:45 | refefer | semmle.label | refefer |
-#select
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/Readme b/java/ql/test/experimental/query-tests/security/CWE-352/Readme
deleted file mode 100644
index 15715d6187c..00000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/Readme
+++ /dev/null
@@ -1,3 +0,0 @@
-1. The JsonpInjection_1.expected result is obtained through the test of `JsonpController.java`.
-2. The JsonpInjection_2.expected result is obtained through the test of `JsonpController.java`, `JsonpInjectionServlet1.java`, `JsonpInjectionServlet2.java`.
-3. The JsonpInjection_3.expected result is obtained through the test of `JsonpController.java`, `JsonpInjectionServlet1.java`, `JsonpInjectionServlet2.java`, `RefererFilter.java`.
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java b/java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java
deleted file mode 100644
index 97444932ae1..00000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java
+++ /dev/null
@@ -1,43 +0,0 @@
-import java.io.IOException;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.springframework.util.StringUtils;
-
-public class RefererFilter implements Filter {
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- }
-
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest) servletRequest;
- HttpServletResponse response = (HttpServletResponse) servletResponse;
- String refefer = request.getHeader("Referer");
- boolean result = verifReferer(refefer);
- if (result){
- filterChain.doFilter(servletRequest, servletResponse);
- }
- response.sendError(444, "Referer xxx.");
- }
-
- @Override
- public void destroy() {
- }
-
- public static boolean verifReferer(String referer){
- if (StringUtils.isEmpty(referer)){
- return false;
- }
- if (referer.startsWith("http://www.baidu.com/")){
- return true;
- }
- return false;
- }
-}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/options b/java/ql/test/experimental/query-tests/security/CWE-352/options
deleted file mode 100644
index 3676b8e38b6..00000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/options
+++ /dev/null
@@ -1 +0,0 @@
-//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/apache-http-4.4.13/:${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/fastjson-1.2.74/:${testdir}/../../../../stubs/gson-2.8.6/:${testdir}/../../../../stubs/jackson-databind-2.10/:${testdir}/../../../../stubs/springframework-5.2.3/:${testdir}/../../../../stubs/spring-context-5.3.2/:${testdir}/../../../../stubs/spring-web-5.3.2/:${testdir}/../../../../stubs/spring-core-5.3.2/
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java
index 3a823fade5b..edfe917400b 100644
--- a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java
@@ -1,10 +1,21 @@
package org.springframework.core.annotation;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+@Documented
public @interface AliasFor {
@AliasFor("attribute")
String value() default "";
@AliasFor("value")
String attribute() default "";
-
+
+ Class extends Annotation> annotation() default Annotation.class;
}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/InputStreamSource.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/InputStreamSource.java
new file mode 100644
index 00000000000..372d06cc738
--- /dev/null
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/InputStreamSource.java
@@ -0,0 +1,8 @@
+package org.springframework.core.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface InputStreamSource {
+ InputStream getInputStream() throws IOException;
+}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/Resource.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/Resource.java
new file mode 100644
index 00000000000..6bd357f2228
--- /dev/null
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/Resource.java
@@ -0,0 +1,46 @@
+package org.springframework.core.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import org.springframework.lang.Nullable;
+
+public interface Resource extends InputStreamSource {
+ boolean exists();
+
+ default boolean isReadable() {
+ return this.exists();
+ }
+
+ default boolean isOpen() {
+ return false;
+ }
+
+ default boolean isFile() {
+ return false;
+ }
+
+ URL getURL() throws IOException;
+
+ URI getURI() throws IOException;
+
+ File getFile() throws IOException;
+
+ default ReadableByteChannel readableChannel() throws IOException {
+ return null;
+ }
+
+ long contentLength() throws IOException;
+
+ long lastModified() throws IOException;
+
+ Resource createRelative(String var1) throws IOException;
+
+ @Nullable
+ String getFilename();
+
+ String getDescription();
+}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/lang/Nullable.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/lang/Nullable.java
new file mode 100644
index 00000000000..44bdae10fda
--- /dev/null
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/lang/Nullable.java
@@ -0,0 +1,13 @@
+package org.springframework.lang;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Nullable {
+}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/FileCopyUtils.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/FileCopyUtils.java
new file mode 100644
index 00000000000..78d384d7266
--- /dev/null
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/FileCopyUtils.java
@@ -0,0 +1,53 @@
+package org.springframework.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.file.Files;
+import org.springframework.lang.Nullable;
+
+public abstract class FileCopyUtils {
+ public static final int BUFFER_SIZE = 4096;
+
+ public FileCopyUtils() {
+ }
+
+ public static int copy(File in, File out) throws IOException {
+ return 1;
+ }
+
+ public static void copy(byte[] in, File out) throws IOException {}
+
+ public static byte[] copyToByteArray(File in) throws IOException {
+ return null;
+ }
+
+ public static int copy(InputStream in, OutputStream out) throws IOException {
+ return 1;
+ }
+
+ public static void copy(byte[] in, OutputStream out) throws IOException {}
+
+ public static byte[] copyToByteArray(@Nullable InputStream in) throws IOException {
+ return null;
+ }
+
+ public static int copy(Reader in, Writer out) throws IOException {
+ return 1;
+ }
+
+ public static void copy(String in, Writer out) throws IOException {}
+
+ public static String copyToString(@Nullable Reader in) throws IOException {
+ return null;
+ }
+
+ private static void close(Closeable closeable) {}
+}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java
index 6ee07f84593..6ea27bbffa2 100644
--- a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java
@@ -5,4 +5,4 @@ public abstract class StringUtils {
public static boolean isEmpty(Object str) {
return str == null || "".equals(str);
}
-}
\ No newline at end of file
+}
diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.java
index 1190be7b879..541c7cd4e21 100644
--- a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.java
+++ b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.java
@@ -1,19 +1,51 @@
package org.springframework.web.bind.annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
-@RequestMapping
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(
+ method = {RequestMethod.GET}
+)
public @interface GetMapping {
-
+ @AliasFor(
+ annotation = RequestMapping.class
+ )
String name() default "";
+ @AliasFor(
+ annotation = RequestMapping.class
+ )
String[] value() default {};
+ @AliasFor(
+ annotation = RequestMapping.class
+ )
String[] path() default {};
+ @AliasFor(
+ annotation = RequestMapping.class
+ )
String[] params() default {};
+ @AliasFor(
+ annotation = RequestMapping.class
+ )
+ String[] headers() default {};
+
+ @AliasFor(
+ annotation = RequestMapping.class
+ )
String[] consumes() default {};
+ @AliasFor(
+ annotation = RequestMapping.class
+ )
String[] produces() default {};
}
diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/Mapping.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/Mapping.java
new file mode 100644
index 00000000000..5f269bbcbb8
--- /dev/null
+++ b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/Mapping.java
@@ -0,0 +1,4 @@
+package org.springframework.web.bind.annotation;
+
+public @interface Mapping {
+}
diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMapping.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMapping.java
index ddb36bce4c0..ed692a03063 100644
--- a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMapping.java
+++ b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMapping.java
@@ -1,15 +1,32 @@
package org.springframework.web.bind.annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
-
+
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
+
+ String[] params() default {};
+
+ String[] headers() default {};
+
+ String[] consumes() default {};
+
+ String[] produces() default {};
}
diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestParam.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestParam.java
new file mode 100644
index 00000000000..56094811c37
--- /dev/null
+++ b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestParam.java
@@ -0,0 +1,23 @@
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.springframework.core.annotation.AliasFor;
+
+@Target({ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RequestParam {
+ @AliasFor("name")
+ String value() default "";
+
+ @AliasFor("value")
+ String name() default "";
+
+ boolean required() default true;
+
+ String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
+}
diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java
index b2134009968..4f21b6daaaf 100644
--- a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java
+++ b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java
@@ -1,4 +1,13 @@
package org.springframework.web.bind.annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
public @interface ResponseBody {
}
diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/multipart/MultipartFile.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/multipart/MultipartFile.java
new file mode 100644
index 00000000000..93ea3439fed
--- /dev/null
+++ b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/multipart/MultipartFile.java
@@ -0,0 +1,38 @@
+package org.springframework.web.multipart;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.springframework.core.io.InputStreamSource;
+import org.springframework.core.io.Resource;
+import org.springframework.lang.Nullable;
+import org.springframework.util.FileCopyUtils;
+
+public interface MultipartFile extends InputStreamSource {
+ String getName();
+
+ @Nullable
+ String getOriginalFilename();
+
+ @Nullable
+ String getContentType();
+
+ boolean isEmpty();
+
+ long getSize();
+
+ byte[] getBytes() throws IOException;
+
+ InputStream getInputStream() throws IOException;
+
+ default Resource getResource() {
+ return null;
+ }
+
+ void transferTo(File var1) throws IOException, IllegalStateException;
+
+ default void transferTo(Path dest) throws IOException, IllegalStateException {
+ }
+}
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/annotation/WebServlet.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/annotation/WebServlet.java
new file mode 100644
index 00000000000..cc255efc00f
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/annotation/WebServlet.java
@@ -0,0 +1,30 @@
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface WebServlet {
+ String name() default "";
+
+ String[] value() default {};
+
+ String[] urlPatterns() default {};
+
+ int loadOnStartup() default -1;
+
+ boolean asyncSupported() default false;
+
+ String smallIcon() default "";
+
+ String largeIcon() default "";
+
+ String description() default "";
+
+ String displayName() default "";
+}
From 15206fd2ce3d53168caa2d0250c2b9036dccfca9 Mon Sep 17 00:00:00 2001
From: haby0
Date: Wed, 17 Mar 2021 15:52:05 +0800
Subject: [PATCH 0052/1429] JsonpInjection.ql autoformatted
---
java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
index 068469328ea..eb4fe4e5b66 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
@@ -46,7 +46,7 @@ class RequestResponseFlowConfig extends TaintTracking::Configuration {
/** Eliminate the method of calling the node is not the get method. */
override predicate isSanitizer(DataFlow::Node node) {
- not getACallingCallableOrSelf(node.getEnclosingCallable()) instanceof RequestGetMethod
+ not getACallingCallableOrSelf(node.getEnclosingCallable()) instanceof RequestGetMethod
}
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
From 79d6731ed8e1725c1064214b7a201c4ad0794009 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Fri, 19 Mar 2021 11:01:28 +0100
Subject: [PATCH 0053/1429] C#: Adjust make_stubs.py to use codeql instead of
odasa
---
csharp/ql/src/Stubs/make_stubs.py | 50 ++++++++++++++-----------------
1 file changed, 23 insertions(+), 27 deletions(-)
diff --git a/csharp/ql/src/Stubs/make_stubs.py b/csharp/ql/src/Stubs/make_stubs.py
index e94cf0c7261..19a917cd8ab 100644
--- a/csharp/ql/src/Stubs/make_stubs.py
+++ b/csharp/ql/src/Stubs/make_stubs.py
@@ -43,12 +43,6 @@ if not foundCS:
print("Test directory does not contain .cs files. Please specify a working qltest directory.")
exit(1)
-cmd = ['odasa', 'selfTest']
-print('Running ' + ' '.join(cmd))
-if subprocess.check_call(cmd):
- print("odasa selfTest failed. Ensure odasa is on your current path.")
- exit(1)
-
csharpQueries = os.path.abspath(os.path.dirname(sys.argv[0]))
outputFile = os.path.join(testDir, 'stubs.cs')
@@ -58,35 +52,36 @@ if os.path.isfile(outputFile):
os.remove(outputFile) # It would interfere with the test.
print("Removed previous", outputFile)
-cmd = ['odasa', 'qltest', '--optimize', '--leave-temp-files', testDir]
+cmd = ['codeql', 'test', 'run', '--keep-databases', testDir]
print('Running ' + ' '.join(cmd))
if subprocess.check_call(cmd):
- print("qltest failed. Please fix up the test before proceeding.")
+ print("codeql test failed. Please fix up the test before proceeding.")
exit(1)
-dbDir = os.path.join(testDir, os.path.basename(testDir) + ".testproj", "db-csharp")
+dbDir = os.path.join(testDir, os.path.basename(testDir) + ".testproj")
if not os.path.isdir(dbDir):
- print("Expected database directory " + dbDir + " not found. Please contact Semmle.")
+ print("Expected database directory " + dbDir + " not found.")
exit(1)
-cmd = ['odasa', 'runQuery', '--query', os.path.join(csharpQueries, 'MinimalStubsFromSource.ql'), '--db', dbDir, '--output-file', outputFile]
+cmd = ['codeql', 'query', 'run', os.path.join(
+ csharpQueries, 'MinimalStubsFromSource.ql'), '--database', dbDir, '--output', outputFile]
print('Running ' + ' '.join(cmd))
if subprocess.check_call(cmd):
- print('Failed to run the query to generate output file. Please contact Semmle.')
+ print('Failed to run the query to generate output file.')
exit(1)
# Remove the leading " and trailing " bytes from the file
len = os.stat(outputFile).st_size
f = open(outputFile, "rb")
try:
- quote = f.read(1)
- if quote != b'"':
- print("Unexpected character in file. Please contact Semmle.")
- contents = f.read(len-3)
- quote = f.read(1)
- if quote != b'"':
- print("Unexpected end character. Please contact Semmle.", quote)
+ quote = f.read(6)
+ if quote != b"\x02\x01\x86'\x85'":
+ print("Unexpected start characters in file.", quote)
+ contents = f.read(len-21)
+ quote = f.read(15)
+ if quote != b'\x0e\x01\x08#select\x01\x01\x00s\x00':
+ print("Unexpected end character in file.", quote)
finally:
f.close()
@@ -94,16 +89,17 @@ f = open(outputFile, "wb")
f.write(contents)
f.close()
-cmd = ['odasa', 'qltest', '--optimize', testDir]
+cmd = ['codeql', 'test', 'run', testDir]
print('Running ' + ' '.join(cmd))
if subprocess.check_call(cmd):
- print('\nTest failed. You may need to fix up', outputFile)
- print('It may help to view', outputFile, ' in Visual Studio')
- print("Next steps:")
- print('1. Look at the compilation errors, and fix up', outputFile, 'so that the test compiles')
- print('2. Re-run odasa qltest --optimize "' + testDir + '"')
- print('3. git add "' + outputFile + '"')
- exit(1)
+ print('\nTest failed. You may need to fix up', outputFile)
+ print('It may help to view', outputFile, ' in Visual Studio')
+ print("Next steps:")
+ print('1. Look at the compilation errors, and fix up',
+ outputFile, 'so that the test compiles')
+ print('2. Re-run codeql test run "' + testDir + '"')
+ print('3. git add "' + outputFile + '"')
+ exit(1)
print("\nStub generation successful! Next steps:")
print('1. Edit "semmle-extractor-options" in the .cs files to remove unused references')
From 164b383fda25be40906e216a53581c5fec4da4df Mon Sep 17 00:00:00 2001
From: yoff
Date: Fri, 19 Mar 2021 19:12:13 +0100
Subject: [PATCH 0054/1429] Update
python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
Co-authored-by: Rasmus Wriedt Larsen
---
.../test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py b/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
index 028c318be66..e4205c49824 100644
--- a/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
@@ -29,3 +29,11 @@ def test_fluent_safe():
conn = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
r = conn.connect((hostname, 443))
print(r, conn.get_protocol_version_name())
+
+def test_safe_by_construction():
+ hostname = 'www.python.org'
+ context = SSL.Context(SSL.TLSv1_2_METHOD)
+
+ conn = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
+ r = conn.connect((hostname, 443))
+ print(conn.get_protocol_version_name())
From 1385b2264234bc6fa3deaaeb7537ee47d89c47fe Mon Sep 17 00:00:00 2001
From: Dilan
Date: Fri, 19 Mar 2021 16:43:29 -0700
Subject: [PATCH 0055/1429] pr fixes, typo in qhelp file and helper method for
queries
---
.../Classes/NamingConventionsClasses.qhelp | 2 +-
.../Classes/NamingConventionsClasses.ql | 17 +++++++-----
.../NamingConventionsFunctions.qhelp | 2 +-
.../Functions/NamingConventionsFunctions.ql | 27 +++++++++++--------
4 files changed, 29 insertions(+), 19 deletions(-)
diff --git a/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp b/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp
index 1a7f4bc45a4..a928668dc6f 100644
--- a/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp
+++ b/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp
@@ -21,7 +21,7 @@ Write the class name beginning with an uppercase letter. For example, clas
- Guido van Rossum, Barry Warsaw, Nick Coghlan PEP 8 -- Style Guide for Python Code
+ Guido van Rossum, Barry Warsaw, Nick Coghlan PEP 8 -- Style Guide for Python Code
Python Class Names
diff --git a/python/ql/src/experimental/Classes/NamingConventionsClasses.ql b/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
index d4919c3ece8..f16d242eadf 100644
--- a/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
+++ b/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
@@ -9,15 +9,20 @@
import python
-from Class c, string first_char
+predicate lower_case_class(Class c) {
+ exists(string first_char |
+ first_char = c.getName().prefix(1) and
+ not first_char = first_char.toUpperCase()
+ )
+}
+
+from Class c
where
c.inSource() and
- first_char = c.getName().prefix(1) and
- not first_char = first_char.toUpperCase() and
- not exists(Class c1, string first_char1 |
+ lower_case_class(c) and
+ not exists(Class c1 |
c1 != c and
c1.getLocation().getFile() = c.getLocation().getFile() and
- first_char1 = c1.getName().prefix(1) and
- not first_char1 = first_char1.toUpperCase()
+ lower_case_class(c1)
)
select c, "Class names should start in uppercase."
diff --git a/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp b/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp
index 46d948592ff..b4ff738782c 100644
--- a/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp
+++ b/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp
@@ -21,7 +21,7 @@ Write the function name beginning with an lowercase letter. For example, j
- Guido van Rossum, Barry Warsaw, Nick Coghlan PEP 8 -- Style Guide for Python Code
+ Guido van Rossum, Barry Warsaw, Nick Coghlan PEP 8 -- Style Guide for Python Code
Python Function and Variable Names
diff --git a/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql b/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
index 80dc0e99cd8..d2868af22c9 100644
--- a/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
+++ b/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
@@ -9,15 +9,20 @@
import python
-from Function f, string first_char
-where
- f.inSource() and
- first_char = f.getName().prefix(1) and
- not first_char = first_char.toLowerCase() and
- not exists(Function f1, string first_char1 |
- f1 != f and
- f1.getLocation().getFile() = f.getLocation().getFile() and
- first_char1 = f1.getName().prefix(1) and
- not first_char1 = first_char1.toLowerCase()
+predicate upper_case_function(Function func) {
+ exists(string first_char |
+ first_char = func.getName().prefix(1) and
+ not first_char = first_char.toLowerCase()
)
-select f, "Function names should start in lowercase."
+}
+
+from Function func
+where
+ func.inSource() and
+ upper_case_function(func) and
+ not exists(Function func1 |
+ func1 != func and
+ func1.getLocation().getFile() = func.getLocation().getFile() and
+ upper_case_function(func1)
+ )
+select func, "Function names should start in lowercase."
From 26bac9f425c2ecab54759797da19bcc1586ed95d Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Sun, 21 Mar 2021 15:25:29 +0300
Subject: [PATCH 0056/1429] Apply suggestions from code review
Co-authored-by: Robert Marsh
---
.../CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql b/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql
index 1a116a83dbf..034df703bc3 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql
@@ -15,12 +15,12 @@
import cpp
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
-/** Holds, if it is an expression, a boolean increment. */
+/** Holds if `exp` increments a boolean value. */
predicate incrementBoolType(Expr exp) {
exp.(IncrementOperation).getOperand().getType() instanceof BoolType
}
-/** Holds, if this is an expression, applies a minus to a boolean type. */
+/** Holds if `exp` applies the unary minus operator to a boolean type. */
predicate revertSignBoolType(Expr exp) {
exp.(AssignExpr).getRValue().(UnaryMinusExpr).getAnOperand().getType() instanceof BoolType and
exp.(AssignExpr).getLValue().getType() instanceof BoolType
From 0200aedc2efc939b96170951d848d440fcbb8e4a Mon Sep 17 00:00:00 2001
From: yo-h <55373593+yo-h@users.noreply.github.com>
Date: Sun, 21 Mar 2021 12:55:25 -0400
Subject: [PATCH 0057/1429] Java 16: adjust test `options`
---
java/ql/test/library-tests/dataflow/records/options | 2 +-
java/ql/test/library-tests/dataflow/taint-format/options | 1 -
java/ql/test/library-tests/printAst/options | 2 +-
java/ql/test/library-tests/ssa/options | 2 +-
java/ql/test/query-tests/StringFormat/options | 1 -
5 files changed, 3 insertions(+), 5 deletions(-)
delete mode 100644 java/ql/test/library-tests/dataflow/taint-format/options
delete mode 100644 java/ql/test/query-tests/StringFormat/options
diff --git a/java/ql/test/library-tests/dataflow/records/options b/java/ql/test/library-tests/dataflow/records/options
index 81817b37785..fc57fe025b9 100644
--- a/java/ql/test/library-tests/dataflow/records/options
+++ b/java/ql/test/library-tests/dataflow/records/options
@@ -1 +1 @@
-//semmle-extractor-options: --javac-args --enable-preview -source 15 -target 15
+//semmle-extractor-options: --javac-args -source 16 -target 16
diff --git a/java/ql/test/library-tests/dataflow/taint-format/options b/java/ql/test/library-tests/dataflow/taint-format/options
deleted file mode 100644
index 81817b37785..00000000000
--- a/java/ql/test/library-tests/dataflow/taint-format/options
+++ /dev/null
@@ -1 +0,0 @@
-//semmle-extractor-options: --javac-args --enable-preview -source 15 -target 15
diff --git a/java/ql/test/library-tests/printAst/options b/java/ql/test/library-tests/printAst/options
index 81817b37785..e41003a6e3c 100644
--- a/java/ql/test/library-tests/printAst/options
+++ b/java/ql/test/library-tests/printAst/options
@@ -1 +1 @@
-//semmle-extractor-options: --javac-args --enable-preview -source 15 -target 15
+//semmle-extractor-options: --javac-args --enable-preview -source 16 -target 16
diff --git a/java/ql/test/library-tests/ssa/options b/java/ql/test/library-tests/ssa/options
index 81817b37785..e41003a6e3c 100644
--- a/java/ql/test/library-tests/ssa/options
+++ b/java/ql/test/library-tests/ssa/options
@@ -1 +1 @@
-//semmle-extractor-options: --javac-args --enable-preview -source 15 -target 15
+//semmle-extractor-options: --javac-args --enable-preview -source 16 -target 16
diff --git a/java/ql/test/query-tests/StringFormat/options b/java/ql/test/query-tests/StringFormat/options
deleted file mode 100644
index 81817b37785..00000000000
--- a/java/ql/test/query-tests/StringFormat/options
+++ /dev/null
@@ -1 +0,0 @@
-//semmle-extractor-options: --javac-args --enable-preview -source 15 -target 15
From 73e940de74bb326664db451b97fa58e67e683400 Mon Sep 17 00:00:00 2001
From: Artem Smotrakov
Date: Tue, 2 Feb 2021 17:37:14 +0100
Subject: [PATCH 0058/1429] Added query for Jakarta EL injections
- Added JakartaExpressionInjection.ql
- Added a qhelp file with examples
---
.../Security/CWE/CWE-094/InjectionLib.qll | 14 +++
.../CWE-094/JakartaExpressionInjection.qhelp | 65 ++++++++++++
.../CWE/CWE-094/JakartaExpressionInjection.ql | 20 ++++
.../CWE-094/JakartaExpressionInjectionLib.qll | 98 +++++++++++++++++++
.../Security/CWE/CWE-094/JexlInjectionLib.qll | 13 +--
.../SaferExpressionEvaluationWithJuel.java | 10 ++
.../UnsafeExpressionEvaluationWithJuel.java | 5 +
7 files changed, 213 insertions(+), 12 deletions(-)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/InjectionLib.qll
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/UnsafeExpressionEvaluationWithJuel.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/InjectionLib.qll
new file mode 100644
index 00000000000..adab79d6f5c
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/InjectionLib.qll
@@ -0,0 +1,14 @@
+import java
+import semmle.code.java.dataflow.FlowSources
+
+/**
+ * Holds if `fromNode` to `toNode` is a dataflow step that returns data from
+ * a bean by calling one of its getters.
+ */
+predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node toNode) {
+ exists(MethodAccess ma, Method m | ma.getMethod() = m |
+ m instanceof GetterMethod and
+ ma.getQualifier() = fromNode.asExpr() and
+ ma = toNode.asExpr()
+ )
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
new file mode 100644
index 00000000000..6e6b5f63394
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
@@ -0,0 +1,65 @@
+
+
+
+
+
+Jakarta Expression Language (EL) is an expression language for Java applications.
+There are a single language specification and multiple implementations
+such as Glassfish, Juel, Apache Commons EL, etc.
+The language allows invocation of methods available in the JVM.
+If an expression is built using attacker-controlled data,
+and then evaluated, then it may allow the attacker to run arbitrary code.
+
+
+
+
+
+It is generally recommended to avoid using untrusted data in an EL expression.
+Before using untrusted data to build an EL expressoin, the data should be validated
+to ensure it is not evaluated as expression language. If the EL implementaion offers
+configuring a sandbox for EL expression, they should be run in a restircitive sandbox
+that allows accessing only explicitly allowed classes. If the EL implementation
+does not allow sandboxing, consider using other expressiong language implementations
+with sandboxing capabilities such as Apache Commons JEXL or the Spring Expression Language.
+
+
+
+
+
+The following example shows how untrusted data is used to build and run an expression
+using the JUEL interpreter:
+
+
+
+
+JUEL does not allow to run expression in a sandbox. To prevent running arbitrary code,
+incoming data has to be checked before including to an expression. The next example
+uses a Regex pattern to check whether a user tries to run an allowed exression or not:
+
+
+
+
+
+
+
+ Eclipse Foundation:
+ Jakarta Expression Language.
+
+
+ Jakarta EE documentation:
+ Jakarta Expression Language API
+
+
+ OWASP:
+ Expression Language Injection.
+
+
+ JUEL:
+ Home page
+
+
+ Apache Foundation:
+ Apache Commons EL
+
+
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
new file mode 100644
index 00000000000..b94c98201de
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Java EE Expression Language injection
+ * @description Evaluation of a user-controlled Jave EE expression
+ * may lead to arbitrary code execution.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id java/javaee-expression-injection
+ * @tags security
+ * external/cwe/cwe-094
+ */
+
+import java
+import JavaEEExpressionInjectionLib
+import DataFlow::PathGraph
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, JavaEEExpressionInjectionConfig conf
+where conf.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Java EE Expression Language injection from $@.",
+ source.getNode(), "this user input"
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
new file mode 100644
index 00000000000..d43b31c9888
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
@@ -0,0 +1,98 @@
+import java
+import InjectionLib
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.dataflow.TaintTracking
+
+/**
+ * A taint-tracking configuration for unsafe user input
+ * that is used to construct and evaluate a Java EE expression.
+ */
+class JavaEEExpressionInjectionConfig extends TaintTracking::Configuration {
+ JavaEEExpressionInjectionConfig() { this = "JavaEEExpressionInjectionConfig" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof ExpressionEvaluationSink }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
+ any(TaintPropagatingCall c).taintFlow(fromNode, toNode) or
+ returnsDataFromBean(fromNode, toNode)
+ }
+}
+
+/**
+ * A sink for Expresssion Language injection vulnerabilities,
+ * i.e. method calls that run evaluation of a Java EE expression.
+ */
+private class ExpressionEvaluationSink extends DataFlow::ExprNode {
+ ExpressionEvaluationSink() {
+ exists(MethodAccess ma, Method m, Expr taintFrom |
+ ma.getMethod() = m and taintFrom = this.asExpr()
+ |
+ m.getDeclaringType() instanceof ValueExpression and
+ m.hasName(["getValue", "setValue"]) and
+ ma.getQualifier() = taintFrom
+ or
+ m.getDeclaringType() instanceof MethodExpression and
+ m.hasName("invoke") and
+ ma.getQualifier() = taintFrom
+ or
+ m.getDeclaringType() instanceof LambdaExpression and
+ m.hasName("invoke") and
+ ma.getQualifier() = taintFrom
+ or
+ m.getDeclaringType() instanceof ELProcessor and
+ m.hasName(["eval", "getValue", "setValue"]) and
+ ma.getArgument(0) = taintFrom
+ )
+ }
+}
+
+/**
+ * Defines method calls that propagate tainted expressions.
+ */
+private class TaintPropagatingCall extends Call {
+ Expr taintFromExpr;
+
+ TaintPropagatingCall() {
+ taintFromExpr = this.getArgument(1) and
+ exists(Method m | this.(MethodAccess).getMethod() = m |
+ m.getDeclaringType() instanceof ExpressionFactory and
+ m.hasName(["createValueExpression", "createMethodExpression"]) and
+ taintFromExpr.getType() instanceof TypeString
+ )
+ or
+ exists(Constructor c | this.(ConstructorCall).getConstructor() = c |
+ c.getDeclaringType() instanceof LambdaExpression and
+ taintFromExpr.getType() instanceof ValueExpression
+ )
+ }
+
+ /**
+ * Holds if `fromNode` to `toNode` is a dataflow step that propagates
+ * tainted data.
+ */
+ predicate taintFlow(DataFlow::Node fromNode, DataFlow::Node toNode) {
+ fromNode.asExpr() = taintFromExpr and toNode.asExpr() = this
+ }
+}
+
+private class ELProcessor extends RefType {
+ ELProcessor() { hasQualifiedName("javax.el", "ELProcessor") }
+}
+
+private class ExpressionFactory extends RefType {
+ ExpressionFactory() { hasQualifiedName("javax.el", "ExpressionFactory") }
+}
+
+private class ValueExpression extends RefType {
+ ValueExpression() { hasQualifiedName("javax.el", "ValueExpression") }
+}
+
+private class MethodExpression extends RefType {
+ MethodExpression() { hasQualifiedName("javax.el", "MethodExpression") }
+}
+
+private class LambdaExpression extends RefType {
+ LambdaExpression() { hasQualifiedName("javax.el", "LambdaExpression") }
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
index 561d7e46ae9..51084a9862c 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
@@ -1,4 +1,5 @@
import java
+import InjectionLib
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
@@ -152,18 +153,6 @@ private predicate createsJexlEngine(DataFlow::Node fromNode, DataFlow::Node toNo
)
}
-/**
- * Holds if `fromNode` to `toNode` is a dataflow step that returns data from
- * a bean by calling one of its getters.
- */
-private predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node toNode) {
- exists(MethodAccess ma, Method m | ma.getMethod() = m |
- m instanceof GetterMethod and
- ma.getQualifier() = fromNode.asExpr() and
- ma = toNode.asExpr()
- )
-}
-
/**
* A methods in the `JexlEngine` class that gets or sets a property with a JEXL expression.
*/
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java b/java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java
new file mode 100644
index 00000000000..54fb9a0ed36
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java
@@ -0,0 +1,10 @@
+String input = getRemoteUserInput();
+String pattern = "(inside|outside)\\.(temperature|humidity)";
+if (!input.matches(pattern)) {
+ throw new IllegalArgumentException("Unexpected exression");
+}
+String expression = "${" + input + "}";
+ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
+ValueExpression e = factory.createValueExpression(context, expression, Object.class);
+SimpleContext context = getContext();
+Object result = e.getValue(context);
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeExpressionEvaluationWithJuel.java b/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeExpressionEvaluationWithJuel.java
new file mode 100644
index 00000000000..27afa0fcb49
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeExpressionEvaluationWithJuel.java
@@ -0,0 +1,5 @@
+String expression = "${" + getRemoteUserInput() + "}";
+ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
+ValueExpression e = factory.createValueExpression(context, expression, Object.class);
+SimpleContext context = getContext();
+Object result = e.getValue(context);
\ No newline at end of file
From adb1ed380ac4736860eac8ea6cfadb8f38497db5 Mon Sep 17 00:00:00 2001
From: Artem Smotrakov
Date: Sun, 21 Mar 2021 20:36:47 +0300
Subject: [PATCH 0059/1429] Added tests for Jakarta expression injection
---
.../CWE/CWE-094/JakartaExpressionInjection.ql | 10 +--
.../CWE-094/JakartaExpressionInjectionLib.qll | 8 +-
.../JakartaExpressionInjection.expected | 59 +++++++++++++
.../CWE-094/JakartaExpressionInjection.java | 87 +++++++++++++++++++
.../CWE-094/JakartaExpressionInjection.qlref | 1 +
.../query-tests/security/CWE-094/options | 3 +-
.../stubs/java-ee-el/javax/el/ELContext.java | 3 +
.../stubs/java-ee-el/javax/el/ELManager.java | 5 ++
.../java-ee-el/javax/el/ELProcessor.java | 7 ++
.../javax/el/ExpressionFactory.java | 17 ++++
.../java-ee-el/javax/el/LambdaExpression.java | 8 ++
.../java-ee-el/javax/el/MethodExpression.java | 5 ++
.../javax/el/StandardELContext.java | 5 ++
.../java-ee-el/javax/el/ValueExpression.java | 6 ++
.../de/odysseus/el/ExpressionFactoryImpl.java | 5 ++
.../de/odysseus/el/util/SimpleContext.java | 5 ++
16 files changed, 223 insertions(+), 11 deletions(-)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.qlref
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/ELContext.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/ELManager.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/ExpressionFactory.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/LambdaExpression.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/MethodExpression.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/StandardELContext.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/ValueExpression.java
create mode 100644 java/ql/test/stubs/juel-2.2/de/odysseus/el/ExpressionFactoryImpl.java
create mode 100644 java/ql/test/stubs/juel-2.2/de/odysseus/el/util/SimpleContext.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
index b94c98201de..8190ec3d61f 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
@@ -1,6 +1,6 @@
/**
- * @name Java EE Expression Language injection
- * @description Evaluation of a user-controlled Jave EE expression
+ * @name Jakarta Expression Language injection
+ * @description Evaluation of a user-controlled expression
* may lead to arbitrary code execution.
* @kind path-problem
* @problem.severity error
@@ -11,10 +11,10 @@
*/
import java
-import JavaEEExpressionInjectionLib
+import JakartaExpressionInjectionLib
import DataFlow::PathGraph
-from DataFlow::PathNode source, DataFlow::PathNode sink, JavaEEExpressionInjectionConfig conf
+from DataFlow::PathNode source, DataFlow::PathNode sink, JakartaExpressionInjectionConfig conf
where conf.hasFlowPath(source, sink)
-select sink.getNode(), source, sink, "Java EE Expression Language injection from $@.",
+select sink.getNode(), source, sink, "Jakarta Expression Language injection from $@.",
source.getNode(), "this user input"
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
index d43b31c9888..bc22f7c3257 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
@@ -5,10 +5,10 @@ import semmle.code.java.dataflow.TaintTracking
/**
* A taint-tracking configuration for unsafe user input
- * that is used to construct and evaluate a Java EE expression.
+ * that is used to construct and evaluate an expression.
*/
-class JavaEEExpressionInjectionConfig extends TaintTracking::Configuration {
- JavaEEExpressionInjectionConfig() { this = "JavaEEExpressionInjectionConfig" }
+class JakartaExpressionInjectionConfig extends TaintTracking::Configuration {
+ JakartaExpressionInjectionConfig() { this = "JakartaExpressionInjectionConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
@@ -22,7 +22,7 @@ class JavaEEExpressionInjectionConfig extends TaintTracking::Configuration {
/**
* A sink for Expresssion Language injection vulnerabilities,
- * i.e. method calls that run evaluation of a Java EE expression.
+ * i.e. method calls that run evaluation of an expression.
*/
private class ExpressionEvaluationSink extends DataFlow::ExprNode {
ExpressionEvaluationSink() {
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
new file mode 100644
index 00000000000..111cc81541c
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
@@ -0,0 +1,59 @@
+edges
+| JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:24:31:24:40 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:30:24:30:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:37:24:37:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:44:24:44:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:54:24:54:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:61:24:61:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:70:24:70:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:79:24:79:33 | expression : String |
+| JakartaExpressionInjection.java:30:24:30:33 | expression : String | JakartaExpressionInjection.java:32:28:32:37 | expression |
+| JakartaExpressionInjection.java:37:24:37:33 | expression : String | JakartaExpressionInjection.java:39:32:39:41 | expression |
+| JakartaExpressionInjection.java:44:24:44:33 | expression : String | JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression |
+| JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression | JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression |
+| JakartaExpressionInjection.java:54:24:54:33 | expression : String | JakartaExpressionInjection.java:56:32:56:41 | expression |
+| JakartaExpressionInjection.java:61:24:61:33 | expression : String | JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression |
+| JakartaExpressionInjection.java:61:24:61:33 | expression : String | JakartaExpressionInjection.java:65:13:65:13 | e |
+| JakartaExpressionInjection.java:61:24:61:33 | expression : String | JakartaExpressionInjection.java:65:13:65:13 | e : ValueExpression |
+| JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression |
+| JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:65:13:65:13 | e |
+| JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:65:13:65:13 | e : ValueExpression |
+| JakartaExpressionInjection.java:65:13:65:13 | e : ValueExpression | JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression |
+| JakartaExpressionInjection.java:70:24:70:33 | expression : String | JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression |
+| JakartaExpressionInjection.java:70:24:70:33 | expression : String | JakartaExpressionInjection.java:74:13:74:13 | e |
+| JakartaExpressionInjection.java:70:24:70:33 | expression : String | JakartaExpressionInjection.java:74:13:74:13 | e : ValueExpression |
+| JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression |
+| JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:74:13:74:13 | e |
+| JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:74:13:74:13 | e : ValueExpression |
+| JakartaExpressionInjection.java:74:13:74:13 | e : ValueExpression | JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression |
+| JakartaExpressionInjection.java:79:24:79:33 | expression : String | JakartaExpressionInjection.java:83:13:83:13 | e |
+nodes
+| JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:30:24:30:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:32:28:32:37 | expression | semmle.label | expression |
+| JakartaExpressionInjection.java:37:24:37:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:39:32:39:41 | expression | semmle.label | expression |
+| JakartaExpressionInjection.java:44:24:44:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression | semmle.label | new LambdaExpression(...) : LambdaExpression |
+| JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression | semmle.label | lambdaExpression |
+| JakartaExpressionInjection.java:54:24:54:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:56:32:56:41 | expression | semmle.label | expression |
+| JakartaExpressionInjection.java:61:24:61:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression | semmle.label | createValueExpression(...) : ValueExpression |
+| JakartaExpressionInjection.java:65:13:65:13 | e | semmle.label | e |
+| JakartaExpressionInjection.java:65:13:65:13 | e : ValueExpression | semmle.label | e : ValueExpression |
+| JakartaExpressionInjection.java:70:24:70:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression | semmle.label | createValueExpression(...) : ValueExpression |
+| JakartaExpressionInjection.java:74:13:74:13 | e | semmle.label | e |
+| JakartaExpressionInjection.java:74:13:74:13 | e : ValueExpression | semmle.label | e : ValueExpression |
+| JakartaExpressionInjection.java:79:24:79:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:83:13:83:13 | e | semmle.label | e |
+#select
+| JakartaExpressionInjection.java:32:28:32:37 | expression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:32:28:32:37 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:39:32:39:41 | expression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:39:32:39:41 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:56:32:56:41 | expression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:56:32:56:41 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:65:13:65:13 | e | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:65:13:65:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:74:13:74:13 | e | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:74:13:74:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:83:13:83:13 | e | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:83:13:83:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.java b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.java
new file mode 100644
index 00000000000..a9fb2d45d6f
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.java
@@ -0,0 +1,87 @@
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+import javax.el.ELContext;
+import javax.el.ELManager;
+import javax.el.ELProcessor;
+import javax.el.ExpressionFactory;
+import javax.el.LambdaExpression;
+import javax.el.MethodExpression;
+import javax.el.StandardELContext;
+import javax.el.ValueExpression;
+
+public class JakartaExpressionInjection {
+
+ private static void testWithSocket(Consumer action) throws IOException {
+ try (ServerSocket serverSocket = new ServerSocket(0)) {
+ try (Socket socket = serverSocket.accept()) {
+ byte[] bytes = new byte[1024];
+ int n = socket.getInputStream().read(bytes);
+ String expression = new String(bytes, 0, n);
+ action.accept(expression);
+ }
+ }
+ }
+
+ private static void testWithELProcessorEval() throws IOException {
+ testWithSocket(expression -> {
+ ELProcessor processor = new ELProcessor();
+ processor.eval(expression);
+ });
+ }
+
+ private static void testWithELProcessorGetValue() throws IOException {
+ testWithSocket(expression -> {
+ ELProcessor processor = new ELProcessor();
+ processor.getValue(expression, Object.class);
+ });
+ }
+
+ private static void testWithLambdaExpressionInvoke() throws IOException {
+ testWithSocket(expression -> {
+ ExpressionFactory factory = ELManager.getExpressionFactory();
+ StandardELContext context = new StandardELContext(factory);
+ ValueExpression valueExpression = factory.createValueExpression(context, expression, Object.class);
+ LambdaExpression lambdaExpression = new LambdaExpression(new ArrayList<>(), valueExpression);
+ lambdaExpression.invoke(context, new Object[0]);
+ });
+ }
+
+ private static void testWithELProcessorSetValue() throws IOException {
+ testWithSocket(expression -> {
+ ELProcessor processor = new ELProcessor();
+ processor.setValue(expression, new Object());
+ });
+ }
+
+ private static void testWithJuelValueExpressionGetValue() throws IOException {
+ testWithSocket(expression -> {
+ ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
+ ELContext context = new de.odysseus.el.util.SimpleContext();
+ ValueExpression e = factory.createValueExpression(context, expression, Object.class);
+ e.getValue(context);
+ });
+ }
+
+ private static void testWithJuelValueExpressionSetValue() throws IOException {
+ testWithSocket(expression -> {
+ ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
+ ELContext context = new de.odysseus.el.util.SimpleContext();
+ ValueExpression e = factory.createValueExpression(context, expression, Object.class);
+ e.setValue(context, new Object());
+ });
+ }
+
+ private static void testWithJuelMethodExpressionInvoke() throws IOException {
+ testWithSocket(expression -> {
+ ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
+ ELContext context = new de.odysseus.el.util.SimpleContext();
+ MethodExpression e = factory.createMethodExpression(context, expression, Object.class, new Class[0]);
+ e.invoke(context, new Object[0]);
+ });
+ }
+
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.qlref
new file mode 100644
index 00000000000..3154ee5ccad
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/options b/java/ql/test/experimental/query-tests/security/CWE-094/options
index a8e30ce30b4..ec3354ea41c 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-094/options
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/options
@@ -1,2 +1 @@
-//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/apache-commons-jexl-2.1.1:${testdir}/../../../../stubs/apache-commons-jexl-3.1:${testdir}/../../../../stubs/scriptengine
-
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/apache-commons-jexl-2.1.1:${testdir}/../../../../stubs/apache-commons-jexl-3.1:${testdir}/../../../../stubs/scriptengine:${testdir}/../../../../stubs/java-ee-el:${testdir}/../../../../stubs/juel-2.2
\ No newline at end of file
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/ELContext.java b/java/ql/test/stubs/java-ee-el/javax/el/ELContext.java
new file mode 100644
index 00000000000..ce3840c69c8
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/ELContext.java
@@ -0,0 +1,3 @@
+package javax.el;
+
+public class ELContext {}
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/ELManager.java b/java/ql/test/stubs/java-ee-el/javax/el/ELManager.java
new file mode 100644
index 00000000000..7d24f739a3f
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/ELManager.java
@@ -0,0 +1,5 @@
+package javax.el;
+
+public class ELManager {
+ public static ExpressionFactory getExpressionFactory() { return null; }
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java b/java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java
new file mode 100644
index 00000000000..3c523e685c5
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java
@@ -0,0 +1,7 @@
+package javax.el;
+
+public class ELProcessor {
+ public Object eval(String expression) { return null; }
+ public Object getValue(String expression, Class> expectedType) { return null; }
+ public void setValue(String expression, Object value) {}
+}
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/ExpressionFactory.java b/java/ql/test/stubs/java-ee-el/javax/el/ExpressionFactory.java
new file mode 100644
index 00000000000..31ff79169ac
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/ExpressionFactory.java
@@ -0,0 +1,17 @@
+package javax.el;
+
+public class ExpressionFactory {
+ public MethodExpression createMethodExpression(ELContext context, String expression, Class> expectedReturnType,
+ Class>[] expectedParamTypes) {
+
+ return null;
+ }
+
+ public ValueExpression createValueExpression(ELContext context, String expression, Class> expectedType) {
+ return null;
+ }
+
+ public ValueExpression createValueExpression(Object instance, Class> expectedType) {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/LambdaExpression.java b/java/ql/test/stubs/java-ee-el/javax/el/LambdaExpression.java
new file mode 100644
index 00000000000..4be01e9d2e4
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/LambdaExpression.java
@@ -0,0 +1,8 @@
+package javax.el;
+
+import java.util.List;
+
+public class LambdaExpression {
+ public LambdaExpression(List formalParameters, ValueExpression expression) {}
+ public Object invoke(Object... args) { return null; }
+}
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/MethodExpression.java b/java/ql/test/stubs/java-ee-el/javax/el/MethodExpression.java
new file mode 100644
index 00000000000..ac50ece80e3
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/MethodExpression.java
@@ -0,0 +1,5 @@
+package javax.el;
+
+public class MethodExpression {
+ public Object invoke(ELContext context, Object[] params) { return null; }
+}
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/StandardELContext.java b/java/ql/test/stubs/java-ee-el/javax/el/StandardELContext.java
new file mode 100644
index 00000000000..22e3f2a9fc1
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/StandardELContext.java
@@ -0,0 +1,5 @@
+package javax.el;
+
+public class StandardELContext extends ELContext {
+ public StandardELContext(ExpressionFactory factory) {}
+}
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/ValueExpression.java b/java/ql/test/stubs/java-ee-el/javax/el/ValueExpression.java
new file mode 100644
index 00000000000..9a231215640
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/ValueExpression.java
@@ -0,0 +1,6 @@
+package javax.el;
+
+public class ValueExpression {
+ public Object getValue(ELContext context) { return null; }
+ public void setValue(ELContext context, Object value) {}
+}
diff --git a/java/ql/test/stubs/juel-2.2/de/odysseus/el/ExpressionFactoryImpl.java b/java/ql/test/stubs/juel-2.2/de/odysseus/el/ExpressionFactoryImpl.java
new file mode 100644
index 00000000000..a555cf88dee
--- /dev/null
+++ b/java/ql/test/stubs/juel-2.2/de/odysseus/el/ExpressionFactoryImpl.java
@@ -0,0 +1,5 @@
+package de.odysseus.el;
+
+import javax.el.ExpressionFactory;
+
+public class ExpressionFactoryImpl extends ExpressionFactory {}
diff --git a/java/ql/test/stubs/juel-2.2/de/odysseus/el/util/SimpleContext.java b/java/ql/test/stubs/juel-2.2/de/odysseus/el/util/SimpleContext.java
new file mode 100644
index 00000000000..aa4f449e5fa
--- /dev/null
+++ b/java/ql/test/stubs/juel-2.2/de/odysseus/el/util/SimpleContext.java
@@ -0,0 +1,5 @@
+package de.odysseus.el.util;
+
+import javax.el.ELContext;
+
+public class SimpleContext extends ELContext {}
From 6c246994035a35925d870f97abc58c04162d53f9 Mon Sep 17 00:00:00 2001
From: Artem Smotrakov
Date: Sun, 21 Mar 2021 21:16:00 +0300
Subject: [PATCH 0060/1429] Cover both javax.el and jakarta.el packages
---
.../CWE-094/JakartaExpressionInjectionLib.qll | 22 +++++++++++--------
1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
index bc22f7c3257..22f421b8bf8 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
@@ -77,22 +77,26 @@ private class TaintPropagatingCall extends Call {
}
}
-private class ELProcessor extends RefType {
- ELProcessor() { hasQualifiedName("javax.el", "ELProcessor") }
+private class JakartaType extends RefType {
+ JakartaType() { getPackage().hasName(["javax.el", "jakarta.el"]) }
}
-private class ExpressionFactory extends RefType {
- ExpressionFactory() { hasQualifiedName("javax.el", "ExpressionFactory") }
+private class ELProcessor extends JakartaType {
+ ELProcessor() { hasName("ELProcessor") }
}
-private class ValueExpression extends RefType {
- ValueExpression() { hasQualifiedName("javax.el", "ValueExpression") }
+private class ExpressionFactory extends JakartaType {
+ ExpressionFactory() { hasName("ExpressionFactory") }
}
-private class MethodExpression extends RefType {
- MethodExpression() { hasQualifiedName("javax.el", "MethodExpression") }
+private class ValueExpression extends JakartaType {
+ ValueExpression() { hasName("ValueExpression") }
+}
+
+private class MethodExpression extends JakartaType {
+ MethodExpression() { hasName("MethodExpression") }
}
private class LambdaExpression extends RefType {
- LambdaExpression() { hasQualifiedName("javax.el", "LambdaExpression") }
+ LambdaExpression() { hasName("LambdaExpression") }
}
From 1534b387bb64cdcd6d88cf36f8516d2b830fd4d5 Mon Sep 17 00:00:00 2001
From: Marcono1234
Date: Mon, 22 Mar 2021 00:54:14 +0100
Subject: [PATCH 0061/1429] Java: Improve documentation regarding minus in
front of numeric literals
---
java/ql/src/semmle/code/java/Expr.qll | 45 ++++++++++++++++++++++++---
1 file changed, 41 insertions(+), 4 deletions(-)
diff --git a/java/ql/src/semmle/code/java/Expr.qll b/java/ql/src/semmle/code/java/Expr.qll
index 43ad6634fc1..2e90075c1eb 100755
--- a/java/ql/src/semmle/code/java/Expr.qll
+++ b/java/ql/src/semmle/code/java/Expr.qll
@@ -638,7 +638,20 @@ class BooleanLiteral extends Literal, @booleanliteral {
override string getAPrimaryQlClass() { result = "BooleanLiteral" }
}
-/** An integer literal. For example, `23`. */
+/**
+ * An integer literal. For example, `23`.
+ *
+ * An integer literal can never be negative except when:
+ * - It is written in binary, octal or hexadecimal notation
+ * - It is written in decimal notation, has the value `2147483648` and is preceded
+ * by a minus; in this case the value of the IntegerLiteral is -2147483648 and
+ * the preceding minus will *not* be modeled as `MinusExpr`.
+ * In all other cases the preceding minus, if any, will be modeled as separate
+ * `MinusExpr`.
+ *
+ * The last exception is necessary because `2147483648` on its own would not be
+ * a valid integer literal (and could also not be parsed as CodeQL `int`).
+ */
class IntegerLiteral extends Literal, @integerliteral {
/** Gets the int representation of this literal. */
int getIntValue() { result = getValue().toInt() }
@@ -646,17 +659,41 @@ class IntegerLiteral extends Literal, @integerliteral {
override string getAPrimaryQlClass() { result = "IntegerLiteral" }
}
-/** A long literal. For example, `23l`. */
+/**
+ * A long literal. For example, `23L`.
+ *
+ * A long literal can never be negative except when:
+ * - It is written in binary, octal or hexadecimal notation
+ * - It is written in decimal notation, has the value `9223372036854775808` and
+ * is preceded by a minus; in this case the value of the LongLiteral is
+ * -9223372036854775808 and the preceding minus will *not* be modeled as
+ * `MinusExpr`.
+ * In all other cases the preceding minus, if any, will be modeled as separate
+ * `MinusExpr`.
+ *
+ * The last exception is necessary because `9223372036854775808` on its own
+ * would not be a valid long literal.
+ */
class LongLiteral extends Literal, @longliteral {
override string getAPrimaryQlClass() { result = "LongLiteral" }
}
-/** A floating point literal. For example, `4.2f`. */
+/**
+ * A float literal. For example, `4.2f`.
+ *
+ * A float literal is never negative; a preceding minus, if any, will always
+ * be modeled as separate `MinusExpr`.
+ */
class FloatingPointLiteral extends Literal, @floatingpointliteral {
override string getAPrimaryQlClass() { result = "FloatingPointLiteral" }
}
-/** A double literal. For example, `4.2`. */
+/**
+ * A double literal. For example, `4.2`.
+ *
+ * A double literal is never negative; a preceding minus, if any, will always
+ * be modeled as separate `MinusExpr`.
+ */
class DoubleLiteral extends Literal, @doubleliteral {
override string getAPrimaryQlClass() { result = "DoubleLiteral" }
}
From 701b935564521d64cc35dc51753493f4dc2782f6 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 22 Mar 2021 00:57:43 +0100
Subject: [PATCH 0062/1429] Python: Add example of QuerySet chain (django)
---
python/ql/test/library-tests/frameworks/django/SqlExecution.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/python/ql/test/library-tests/frameworks/django/SqlExecution.py b/python/ql/test/library-tests/frameworks/django/SqlExecution.py
index 67a6ee81a5d..fba2ccdc73e 100644
--- a/python/ql/test/library-tests/frameworks/django/SqlExecution.py
+++ b/python/ql/test/library-tests/frameworks/django/SqlExecution.py
@@ -27,3 +27,6 @@ def test_model():
raw = RawSQL("so raw")
User.objects.annotate(val=raw) # $getSql="so raw"
+
+ # chaining QuerySet calls
+ User.objects.using("db-name").exclude(username="admin").extra("some sql") # $ MISSING: getSql="some sql"
From 7a0bfd1a69c9a0eb7e13d665ad1f4ce67d05d6fd Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Mon, 22 Mar 2021 12:20:35 +0100
Subject: [PATCH 0063/1429] Skip through any stub preamble
---
csharp/ql/src/Stubs/make_stubs.py | 43 +++++++++++++++++++++----------
1 file changed, 30 insertions(+), 13 deletions(-)
diff --git a/csharp/ql/src/Stubs/make_stubs.py b/csharp/ql/src/Stubs/make_stubs.py
index 19a917cd8ab..dcdd9e87cfe 100644
--- a/csharp/ql/src/Stubs/make_stubs.py
+++ b/csharp/ql/src/Stubs/make_stubs.py
@@ -71,19 +71,36 @@ if subprocess.check_call(cmd):
print('Failed to run the query to generate output file.')
exit(1)
-# Remove the leading " and trailing " bytes from the file
-len = os.stat(outputFile).st_size
-f = open(outputFile, "rb")
-try:
- quote = f.read(6)
- if quote != b"\x02\x01\x86'\x85'":
- print("Unexpected start characters in file.", quote)
- contents = f.read(len-21)
- quote = f.read(15)
- if quote != b'\x0e\x01\x08#select\x01\x01\x00s\x00':
- print("Unexpected end character in file.", quote)
-finally:
- f.close()
+# Remove the leading and trailing bytes from the file
+length = os.stat(outputFile).st_size
+if length < 20:
+ contents = b''
+else:
+ f = open(outputFile, "rb")
+ try:
+ countTillSlash = 0
+ foundSlash = False
+ slash = f.read(1)
+ while slash != b'':
+ if slash == b'/':
+ foundSlash = True
+ break
+ countTillSlash += 1
+ slash = f.read(1)
+
+ if not foundSlash:
+ countTillSlash = 0
+
+ f.seek(0)
+ quote = f.read(countTillSlash)
+ print("Start characters in file skipped.", quote)
+ post = b'\x0e\x01\x08#select\x01\x01\x00s\x00'
+ contents = f.read(length - len(post) - countTillSlash)
+ quote = f.read(len(post))
+ if quote != post:
+ print("Unexpected end character in file.", quote)
+ finally:
+ f.close()
f = open(outputFile, "wb")
f.write(contents)
From c8a6e837b57513df5c8ccd081e193be3443987d1 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 22 Mar 2021 14:36:29 +0100
Subject: [PATCH 0064/1429] Python: Model QuerySet chains in django
---
.../2021-03-22-django-queryset-chains.md | 2 +
.../src/semmle/python/frameworks/Django.qll | 128 +++++++-----------
.../frameworks/django/SqlExecution.py | 2 +-
3 files changed, 54 insertions(+), 78 deletions(-)
create mode 100644 python/change-notes/2021-03-22-django-queryset-chains.md
diff --git a/python/change-notes/2021-03-22-django-queryset-chains.md b/python/change-notes/2021-03-22-django-queryset-chains.md
new file mode 100644
index 00000000000..116a7573360
--- /dev/null
+++ b/python/change-notes/2021-03-22-django-queryset-chains.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Improved modeling of `django` to recognize QuerySet chains such as `User.objects.using("db-name").exclude(username="admin").extra("some sql")`. This can lead to new results for `py/sql-injection`.
diff --git a/python/ql/src/semmle/python/frameworks/Django.qll b/python/ql/src/semmle/python/frameworks/Django.qll
index 9f56bd5a299..384fdf69a15 100644
--- a/python/ql/src/semmle/python/frameworks/Django.qll
+++ b/python/ql/src/semmle/python/frameworks/Django.qll
@@ -8,6 +8,7 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
private import semmle.python.frameworks.PEP249
private import semmle.python.regex
@@ -104,9 +105,6 @@ private module Django {
// -------------------------------------------------------------------------
// django.db.models
// -------------------------------------------------------------------------
- // NOTE: The modelling of django models is currently fairly incomplete.
- // It does not fully take `Model`s, `Manager`s, `and QuerySet`s into account.
- // It simply identifies some common dangerous cases.
/** Gets a reference to the `django.db.models` module. */
private DataFlow::Node models(DataFlow::TypeTracker t) {
t.start() and
@@ -123,72 +121,52 @@ private module Django {
/** Provides models for the `django.db.models` module. */
module models {
- /** Provides models for the `django.db.models.Model` class. */
+ /**
+ * Provides models for the `django.db.models.Model` class and subclasses.
+ *
+ * See https://docs.djangoproject.com/en/3.1/topics/db/models/.
+ */
module Model {
- /** Gets a reference to the `django.db.models.Model` class. */
- private DataFlow::Node classRef(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("django.db.models.Model")
- or
- t.startInAttr("Model") and
- result = models()
- or
- // subclass
- result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
- or
- exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
+ /** Gets a reference to the `flask.views.View` class or any subclass. */
+ API::Node subclassRef() {
+ result =
+ API::moduleImport("django")
+ .getMember("db")
+ .getMember("models")
+ .getMember("Model")
+ .getASubclass*()
}
-
- /** Gets a reference to the `django.db.models.Model` class. */
- DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
- }
-
- /** Gets a reference to the `objects` object of a django model. */
- private DataFlow::Node objects(DataFlow::TypeTracker t) {
- t.startInAttr("objects") and
- result = Model::classRef()
- or
- exists(DataFlow::TypeTracker t2 | result = objects(t2).track(t2, t))
- }
-
- /** Gets a reference to the `objects` object of a model. */
- DataFlow::Node objects() { result = objects(DataFlow::TypeTracker::end()) }
-
- /**
- * Gets a reference to the attribute `attr_name` of an `objects` object.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node objects_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["annotate", "extra", "raw"] and
- t.startInAttr(attr_name) and
- result = objects()
- or
- // Due to bad performance when using normal setup with `objects_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- objects_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate objects_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
- DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(objects_attr(t2, attr_name), res, summary)
}
/**
- * Gets a reference to the attribute `attr_name` of an `objects` object.
- * WARNING: Only holds for a few predefined attributes.
+ * Gets a reference to the Manager (django.db.models.Manager) for a django Model,
+ * accessed by `.objects`.
*/
- DataFlow::Node objects_attr(string attr_name) {
- result = objects_attr(DataFlow::TypeTracker::end(), attr_name)
+ API::Node manager() { result = Model::subclassRef().getMember("objects") }
+
+ /**
+ * Gets a method with `name` that returns a QuerySet.
+ * This method can originate on a QuerySet or a Manager.
+ *
+ * See https://docs.djangoproject.com/en/3.1/ref/models/querysets/
+ */
+ API::Node querySetReturningMethod(string name) {
+ name in [
+ "none", "all", "filter", "exclude", "complex_filter", "union", "intersection",
+ "difference", "select_for_update", "select_related", "prefetch_related", "order_by",
+ "distinct", "reverse", "defer", "only", "using", "annotate", "extra", "raw",
+ "datetimes", "dates", "values", "values_list"
+ ] and
+ result = [manager(), querySet()].getMember(name)
}
+ /**
+ * Gets a reference to a QuerySet (django.db.models.query.QuerySet).
+ *
+ * See https://docs.djangoproject.com/en/3.1/ref/models/querysets/
+ */
+ API::Node querySet() { result = querySetReturningMethod(_).getReturn() }
+
/** Gets a reference to the `django.db.models.expressions` module. */
private DataFlow::Node expressions(DataFlow::TypeTracker t) {
t.start() and
@@ -253,14 +231,13 @@ private module Django {
*
* See https://docs.djangoproject.com/en/3.1/ref/models/querysets/#annotate
*/
- private class ObjectsAnnotate extends SqlExecution::Range, DataFlow::CfgNode {
- override CallNode node;
+ private class ObjectsAnnotate extends SqlExecution::Range, DataFlow::CallCfgNode {
ControlFlowNode sql;
ObjectsAnnotate() {
- node.getFunction() = django::db::models::objects_attr("annotate").asCfgNode() and
- django::db::models::expressions::RawSQL::instance(sql).asCfgNode() in [
- node.getArg(_), node.getArgByName(_)
+ this = django::db::models::querySetReturningMethod("annotate").getACall() and
+ django::db::models::expressions::RawSQL::instance(sql) in [
+ this.getArg(_), this.getArgByName(_)
]
}
@@ -274,12 +251,10 @@ private module Django {
* - https://docs.djangoproject.com/en/3.1/topics/db/sql/#django.db.models.Manager.raw
* - https://docs.djangoproject.com/en/3.1/ref/models/querysets/#raw
*/
- private class ObjectsRaw extends SqlExecution::Range, DataFlow::CfgNode {
- override CallNode node;
+ private class ObjectsRaw extends SqlExecution::Range, DataFlow::CallCfgNode {
+ ObjectsRaw() { this = django::db::models::querySetReturningMethod("raw").getACall() }
- ObjectsRaw() { node.getFunction() = django::db::models::objects_attr("raw").asCfgNode() }
-
- override DataFlow::Node getSql() { result.asCfgNode() = node.getArg(0) }
+ override DataFlow::Node getSql() { result = this.getArg(0) }
}
/**
@@ -287,14 +262,13 @@ private module Django {
*
* See https://docs.djangoproject.com/en/3.1/ref/models/querysets/#extra
*/
- private class ObjectsExtra extends SqlExecution::Range, DataFlow::CfgNode {
- override CallNode node;
-
- ObjectsExtra() { node.getFunction() = django::db::models::objects_attr("extra").asCfgNode() }
+ private class ObjectsExtra extends SqlExecution::Range, DataFlow::CallCfgNode {
+ ObjectsExtra() { this = django::db::models::querySetReturningMethod("extra").getACall() }
override DataFlow::Node getSql() {
- result.asCfgNode() =
- [node.getArg([0, 1, 3, 4]), node.getArgByName(["select", "where", "tables", "order_by"])]
+ result in [
+ this.getArg([0, 1, 3, 4]), this.getArgByName(["select", "where", "tables", "order_by"])
+ ]
}
}
diff --git a/python/ql/test/library-tests/frameworks/django/SqlExecution.py b/python/ql/test/library-tests/frameworks/django/SqlExecution.py
index fba2ccdc73e..449983fc898 100644
--- a/python/ql/test/library-tests/frameworks/django/SqlExecution.py
+++ b/python/ql/test/library-tests/frameworks/django/SqlExecution.py
@@ -29,4 +29,4 @@ def test_model():
User.objects.annotate(val=raw) # $getSql="so raw"
# chaining QuerySet calls
- User.objects.using("db-name").exclude(username="admin").extra("some sql") # $ MISSING: getSql="some sql"
+ User.objects.using("db-name").exclude(username="admin").extra("some sql") # $ getSql="some sql"
From 993999f64fd519d358b1276d19b81215a7073e6f Mon Sep 17 00:00:00 2001
From: Marcono1234
Date: Mon, 22 Mar 2021 17:37:54 +0100
Subject: [PATCH 0065/1429] Java: Add test for negative numeric literals
---
.../literals-numeric/NumericLiterals.java | 16 ++++++++++++++++
.../negativeNumericLiteral.expected | 12 ++++++++++++
.../literals-numeric/negativeNumericLiteral.ql | 9 +++++++++
3 files changed, 37 insertions(+)
create mode 100644 java/ql/test/library-tests/literals-numeric/NumericLiterals.java
create mode 100644 java/ql/test/library-tests/literals-numeric/negativeNumericLiteral.expected
create mode 100644 java/ql/test/library-tests/literals-numeric/negativeNumericLiteral.ql
diff --git a/java/ql/test/library-tests/literals-numeric/NumericLiterals.java b/java/ql/test/library-tests/literals-numeric/NumericLiterals.java
new file mode 100644
index 00000000000..02f2fbfcc6b
--- /dev/null
+++ b/java/ql/test/library-tests/literals-numeric/NumericLiterals.java
@@ -0,0 +1,16 @@
+class NumericLiterals {
+ void negativeLiterals() {
+ float f = -1f;
+ double d = -1d;
+ int i1 = -2147483647;
+ int i2 = -2147483648; // CodeQL models minus as part of literal
+ int i3 = -0b10000000000000000000000000000000; // binary
+ int i4 = -020000000000; // octal
+ int i5 = -0x80000000; // hex
+ long l1 = -9223372036854775807L;
+ long l2 = -9223372036854775808L; // CodeQL models minus as part of literal
+ long l3 = -0b1000000000000000000000000000000000000000000000000000000000000000L; // binary
+ long l4 = -01000000000000000000000L; // octal
+ long l5 = -0x8000000000000000L; // hex
+ }
+}
diff --git a/java/ql/test/library-tests/literals-numeric/negativeNumericLiteral.expected b/java/ql/test/library-tests/literals-numeric/negativeNumericLiteral.expected
new file mode 100644
index 00000000000..95100f259dd
--- /dev/null
+++ b/java/ql/test/library-tests/literals-numeric/negativeNumericLiteral.expected
@@ -0,0 +1,12 @@
+| NumericLiterals.java:3:14:3:15 | 1f | 1.0 | NumericLiterals.java:3:13:3:15 | -... |
+| NumericLiterals.java:4:15:4:16 | 1d | 1.0 | NumericLiterals.java:4:14:4:16 | -... |
+| NumericLiterals.java:5:13:5:22 | 2147483647 | 2147483647 | NumericLiterals.java:5:12:5:22 | -... |
+| NumericLiterals.java:6:12:6:22 | -2147483648 | -2147483648 | NumericLiterals.java:6:7:6:22 | i2 |
+| NumericLiterals.java:7:13:7:46 | 0b10000000000000000000000000000000 | -2147483648 | NumericLiterals.java:7:12:7:46 | -... |
+| NumericLiterals.java:8:13:8:24 | 020000000000 | -2147483648 | NumericLiterals.java:8:12:8:24 | -... |
+| NumericLiterals.java:9:13:9:22 | 0x80000000 | -2147483648 | NumericLiterals.java:9:12:9:22 | -... |
+| NumericLiterals.java:10:14:10:33 | 9223372036854775807L | 9223372036854775807 | NumericLiterals.java:10:13:10:33 | -... |
+| NumericLiterals.java:11:13:11:33 | -9223372036854775808L | -9223372036854775808 | NumericLiterals.java:11:8:11:33 | l2 |
+| NumericLiterals.java:12:14:12:80 | 0b1000000000000000000000000000000000000000000000000000000000000000L | -9223372036854775808 | NumericLiterals.java:12:13:12:80 | -... |
+| NumericLiterals.java:13:14:13:37 | 01000000000000000000000L | -9223372036854775808 | NumericLiterals.java:13:13:13:37 | -... |
+| NumericLiterals.java:14:14:14:32 | 0x8000000000000000L | -9223372036854775808 | NumericLiterals.java:14:13:14:32 | -... |
diff --git a/java/ql/test/library-tests/literals-numeric/negativeNumericLiteral.ql b/java/ql/test/library-tests/literals-numeric/negativeNumericLiteral.ql
new file mode 100644
index 00000000000..0fbb3989c7a
--- /dev/null
+++ b/java/ql/test/library-tests/literals-numeric/negativeNumericLiteral.ql
@@ -0,0 +1,9 @@
+import java
+
+from Literal l
+where
+ l instanceof IntegerLiteral or
+ l instanceof LongLiteral or
+ l instanceof FloatingPointLiteral or
+ l instanceof DoubleLiteral
+select l, l.getValue(), l.getParent()
From 0e81fd26248875838c751ce44345aee1b8e6c36d Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Mon, 22 Mar 2021 18:41:22 +0100
Subject: [PATCH 0066/1429] Python: Move `Boolean` into `TypeTrackerPrivate`
In general, this may be defined already for other languages, so moving
it in here will avoid potential clashes.
---
python/ql/src/experimental/typetracking/TypeTracker.qll | 5 -----
.../src/experimental/typetracking/TypeTrackerPrivate.qll | 7 +++++++
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/python/ql/src/experimental/typetracking/TypeTracker.qll b/python/ql/src/experimental/typetracking/TypeTracker.qll
index f21d3a2a91b..056e7ba5cf0 100644
--- a/python/ql/src/experimental/typetracking/TypeTracker.qll
+++ b/python/ql/src/experimental/typetracking/TypeTracker.qll
@@ -78,11 +78,6 @@ module StepSummary {
}
}
-/**
- * A utility class that is equivalent to `boolean` but does not require type joining.
- */
-private class Boolean extends boolean {
- Boolean() { this = true or this = false }
}
private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalContentName content)
diff --git a/python/ql/src/experimental/typetracking/TypeTrackerPrivate.qll b/python/ql/src/experimental/typetracking/TypeTrackerPrivate.qll
index d26a76b3355..ab39e8be5be 100644
--- a/python/ql/src/experimental/typetracking/TypeTrackerPrivate.qll
+++ b/python/ql/src/experimental/typetracking/TypeTrackerPrivate.qll
@@ -95,3 +95,10 @@ predicate basicLoadStep(Node nodeFrom, Node nodeTo, string content) {
nodeTo = a
)
}
+
+/**
+ * A utility class that is equivalent to `boolean` but does not require type joining.
+ */
+class Boolean extends boolean {
+ Boolean() { this = true or this = false }
+}
From 7cdf439b83b3d166ac0b9083968178b7a1e380ca Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Mon, 22 Mar 2021 18:42:24 +0100
Subject: [PATCH 0067/1429] Python: Clean up `basicStoreStep`
Moves the `flowsTo` logic into the shared implementation, so that
`TypeTrackingPrivate` only has to define the shape of immediate store
steps.
Also cleans up the documentation to talk a bit more about what
`content` can represent, and what caveats there are.
---
.../experimental/typetracking/TypeTracker.qll | 48 +++++++++++++++++--
.../typetracking/TypeTrackerPrivate.qll | 32 ++-----------
2 files changed, 47 insertions(+), 33 deletions(-)
diff --git a/python/ql/src/experimental/typetracking/TypeTracker.qll b/python/ql/src/experimental/typetracking/TypeTracker.qll
index 056e7ba5cf0..46461d3e22e 100644
--- a/python/ql/src/experimental/typetracking/TypeTracker.qll
+++ b/python/ql/src/experimental/typetracking/TypeTracker.qll
@@ -2,7 +2,18 @@
private import TypeTrackerPrivate
-/** Any string that may appear as the name of a piece of content. */
+/**
+ * Any string that may appear as the name of a piece of content. This will usually include things like:
+ * - Attribute names (in Python)
+ * - Property names (in JavaScript)
+ *
+ * In general, this can also be used to model things like stores to specific list indices. To ensure
+ * correctness, it is important that
+ *
+ * - different types of content do not have overlapping names, and
+ * - the empty string `""` is not a valid piece of content, as it is used to indicate the absence of
+ * content instead.
+ */
class ContentName extends string {
ContentName() { this = getPossibleContentName() }
}
@@ -70,14 +81,41 @@ module StepSummary {
summary = ReturnStep()
or
exists(string content |
- basicStoreStep(nodeFrom, nodeTo, content) and
+ localSourceStoreStep(nodeFrom, nodeTo, content) and
summary = StoreStep(content)
or
basicLoadStep(nodeFrom, nodeTo, content) and summary = LoadStep(content)
)
}
-}
+ /**
+ * Holds if `nodeFrom` is being written to the `content` content of the object in `nodeTo`.
+ *
+ * Note that `nodeTo` will always be a local source node that flows to the place where the content
+ * is written in `basicStoreStep`. This may lead to the flow of information going "back in time"
+ * from the point of view of the execution of the program.
+ *
+ * For instance, if we interpret attribute writes in Python as writing to content with the same
+ * name as the attribute and consider the following snippet
+ *
+ * ```python
+ * def foo(y):
+ * x = Foo()
+ * bar(x)
+ * x.attr = y
+ * baz(x)
+ *
+ * def bar(x):
+ * z = x.attr
+ * ```
+ * for the attribute write `x.attr = y`, we will have `content` being the literal string `"attr"`,
+ * `nodeFrom` will be `y`, and `nodeTo` will be the object `Foo()` created on the first line of the
+ * function. This means we will track the fact that `x.attr` can have the type of `y` into the
+ * assignment to `z` inside `bar`, even though this attribute write happens _after_ `bar` is called.
+ */
+ predicate localSourceStoreStep(Node nodeFrom, LocalSourceNode nodeTo, string content) {
+ exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, content))
+ }
}
private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalContentName content)
@@ -92,7 +130,7 @@ private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalContentNam
*
* It is recommended that all uses of this type are written in the following form,
* for tracking some type `myType`:
- * ```
+ * ```ql
* DataFlow::LocalSourceNode myType(DataFlow::TypeTracker t) {
* t.start() and
* result = < source of myType >
@@ -253,7 +291,7 @@ private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn, Optional
* It is recommended that all uses of this type are written in the following form,
* for back-tracking some callback type `myCallback`:
*
- * ```
+ * ```ql
* DataFlow::LocalSourceNode myCallback(DataFlow::TypeBackTracker t) {
* t.start() and
* result = (< some API call >).getArgument(< n >).getALocalSource()
diff --git a/python/ql/src/experimental/typetracking/TypeTrackerPrivate.qll b/python/ql/src/experimental/typetracking/TypeTrackerPrivate.qll
index ab39e8be5be..6469eb93d98 100644
--- a/python/ql/src/experimental/typetracking/TypeTrackerPrivate.qll
+++ b/python/ql/src/experimental/typetracking/TypeTrackerPrivate.qll
@@ -13,10 +13,8 @@ predicate typePreservingStep(Node nodeFrom, Node nodeTo) {
}
/**
- * Gets the name of a possible piece of content. This will usually include things like
- *
- * - Attribute names (in Python)
- * - Property names (in JavaScript)
+ * Gets the name of a possible piece of content. For Python, this is currently only attribute names,
+ * using the name of the attribute for the corresponding content.
*/
string getPossibleContentName() { result = any(DataFlowPublic::AttrRef a).getAttributeName() }
@@ -54,34 +52,12 @@ predicate returnStep(DataFlowPrivate::ReturnNode nodeFrom, Node nodeTo) {
/**
* Holds if `nodeFrom` is being written to the `content` content of the object in `nodeTo`.
- *
- * Note that the choice of `nodeTo` does not have to make sense "chronologically".
- * All we care about is whether the `content` content of `nodeTo` can have a specific type,
- * and the assumption is that if a specific type appears here, then any access of that
- * particular content can yield something of that particular type.
- *
- * Thus, in an example such as
- *
- * ```python
- * def foo(y):
- * x = Foo()
- * bar(x)
- * x.content = y
- * baz(x)
- *
- * def bar(x):
- * z = x.content
- * ```
- * for the content write `x.content = y`, we will have `content` being the literal string `"content"`,
- * `nodeFrom` will be `y`, and `nodeTo` will be the object `Foo()` created on the first line of the
- * function. This means we will track the fact that `x.content` can have the type of `y` into the
- * assignment to `z` inside `bar`, even though this content write happens _after_ `bar` is called.
*/
-predicate basicStoreStep(Node nodeFrom, LocalSourceNode nodeTo, string content) {
+predicate basicStoreStep(Node nodeFrom, Node nodeTo, string content) {
exists(DataFlowPublic::AttrWrite a |
a.mayHaveAttributeName(content) and
nodeFrom = a.getValue() and
- nodeTo.flowsTo(a.getObject())
+ nodeTo = a.getObject()
)
}
From 08c3bf26d558d1bfaf0504fa1a0ba6837910829a Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Tue, 16 Mar 2021 03:18:26 +0000
Subject: [PATCH 0068/1429] Update the query to accommodate more cases
---
.../CWE-1004/SensitiveCookieNotHttpOnly.ql | 73 +++++++++++++++++--
.../SensitiveCookieNotHttpOnly.expected | 8 ++
.../CWE-1004/SensitiveCookieNotHttpOnly.java | 57 +++++++++++++++
3 files changed, 130 insertions(+), 8 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index 693dad68082..ac33a6cecb2 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -33,7 +33,23 @@ predicate isSensitiveCookieNameExpr(Expr expr) {
/** Holds if a string is concatenated with the `HttpOnly` flag. */
predicate hasHttpOnlyExpr(Expr expr) {
- expr.(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%") or
+ (
+ expr.(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%")
+ or
+ exists(
+ StaticMethodAccess ma // String.format("%s=%s;HttpOnly", "sessionkey", sessionKey)
+ |
+ ma.getType().getName() = "String" and
+ ma.getMethod().getName() = "format" and
+ ma.getArgument(0)
+ .(CompileTimeConstantExpr)
+ .getStringValue()
+ .toLowerCase()
+ .matches("%httponly%") and
+ expr = ma
+ )
+ )
+ or
hasHttpOnlyExpr(expr.(AddExpr).getAnOperand())
}
@@ -56,6 +72,40 @@ class CookieClass extends RefType {
}
}
+/** Holds if the `Expr` expr is evaluated to boolean true. */
+predicate isBooleanTrue(Expr expr) {
+ expr.(CompileTimeConstantExpr).getBooleanValue() = true or
+ expr.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getBooleanValue() =
+ true
+}
+
+/** Holds if the method or a wrapper method sets the `HttpOnly` flag. */
+predicate setHttpOnlyInCookie(MethodAccess ma) {
+ ma.getMethod().getName() = "setHttpOnly" and
+ (
+ isBooleanTrue(ma.getArgument(0)) // boolean literal true
+ or
+ exists(
+ MethodAccess mpa, int i // runtime assignment of boolean value true
+ |
+ TaintTracking::localTaint(DataFlow::parameterNode(mpa.getMethod().getParameter(i)),
+ DataFlow::exprNode(ma.getArgument(0))) and
+ isBooleanTrue(mpa.getArgument(i))
+ )
+ )
+ or
+ exists(MethodAccess mca |
+ ma.getMethod().calls(mca.getMethod()) and
+ setHttpOnlyInCookie(mca)
+ )
+}
+
+/** Holds if the method or a wrapper method removes a cookie. */
+predicate removeCookie(MethodAccess ma) {
+ ma.getMethod().getName() = "setMaxAge" and
+ ma.getArgument(0).(IntegerLiteral).getIntValue() = 0
+}
+
/** Sensitive cookie name used in a `Cookie` constructor or a `Set-Cookie` call. */
class SensitiveCookieNameExpr extends Expr {
SensitiveCookieNameExpr() { isSensitiveCookieNameExpr(this) }
@@ -69,11 +119,16 @@ class CookieResponseSink extends DataFlow::ExprNode {
ma.getMethod() instanceof ResponseAddCookieMethod and
this.getExpr() = ma.getArgument(0) and
not exists(
- MethodAccess ma2 // cookie.setHttpOnly(true)
+ MethodAccess ma2 // a method or wrapper method that invokes cookie.setHttpOnly(true)
|
- ma2.getMethod().getName() = "setHttpOnly" and
- ma2.getArgument(0).(BooleanLiteral).getBooleanValue() = true and
- DataFlow::localExprFlow(ma2.getQualifier(), this.getExpr())
+ (
+ setHttpOnlyInCookie(ma2) or
+ removeCookie(ma2)
+ ) and
+ (
+ DataFlow::localExprFlow(ma2.getQualifier(), this.getExpr()) or
+ DataFlow::localExprFlow(ma2, this.getExpr())
+ )
)
or
ma instanceof SetCookieMethodAccess and
@@ -92,13 +147,15 @@ class CookieResponseSink extends DataFlow::ExprNode {
predicate setHttpOnlyInNewCookie(ClassInstanceExpr cie) {
cie.getConstructedType().hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "NewCookie") and
(
- cie.getNumArgument() = 6 and cie.getArgument(5).(BooleanLiteral).getBooleanValue() = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
+ cie.getNumArgument() = 6 and
+ isBooleanTrue(cie.getArgument(5)) // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
or
cie.getNumArgument() = 8 and
cie.getArgument(6).getType() instanceof BooleanType and
- cie.getArgument(7).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
+ isBooleanTrue(cie.getArgument(7)) // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
or
- cie.getNumArgument() = 10 and cie.getArgument(9).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
+ cie.getNumArgument() = 10 and
+ isBooleanTrue(cie.getArgument(9)) // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
)
}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
index 8fa688bef2a..dae98e92e67 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
@@ -7,6 +7,9 @@ edges
| SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
| SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
| SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
+| SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie |
+| SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | SensitiveCookieNotHttpOnly.java:102:25:102:64 | createAuthenticationCookie(...) : Cookie |
+| SensitiveCookieNotHttpOnly.java:102:25:102:64 | createAuthenticationCookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie |
nodes
| SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | semmle.label | "jwt_token" : String |
| SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | semmle.label | jwtCookie |
@@ -21,6 +24,10 @@ nodes
| SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | semmle.label | ... + ... : String |
| SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | semmle.label | ... + ... : String |
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | semmle.label | secString |
+| SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | semmle.label | "Presto-UI-Token" : String |
+| SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | semmle.label | cookie : Cookie |
+| SensitiveCookieNotHttpOnly.java:102:25:102:64 | createAuthenticationCookie(...) : Cookie | semmle.label | createAuthenticationCookie(...) : Cookie |
+| SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie | semmle.label | cookie |
#select
| SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" : String | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" | This sensitive cookie |
@@ -31,3 +38,4 @@ nodes
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... | This sensitive cookie |
+| SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" | This sensitive cookie |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
index 337a99cc096..fdc831d7836 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
@@ -71,6 +71,63 @@ class SensitiveCookieNotHttpOnly {
response.addHeader("Set-Cookie", secString);
}
+ // GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set using `String.format(...)`.
+ public void addCookie10(HttpServletRequest request, HttpServletResponse response) {
+ response.addHeader("SET-COOKIE", String.format("%s=%s;HttpOnly", "sessionkey", request.getSession().getAttribute("sessionkey")));
+ }
+
+ public Cookie createHttpOnlyAuthenticationCookie(HttpServletRequest request, String jwt) {
+ String PRESTO_UI_COOKIE = "Presto-UI-Token";
+ Cookie cookie = new Cookie(PRESTO_UI_COOKIE, jwt);
+ cookie.setHttpOnly(true);
+ cookie.setPath("/ui");
+ return cookie;
+ }
+
+ public Cookie createAuthenticationCookie(HttpServletRequest request, String jwt) {
+ String PRESTO_UI_COOKIE = "Presto-UI-Token";
+ Cookie cookie = new Cookie(PRESTO_UI_COOKIE, jwt);
+ cookie.setPath("/ui");
+ return cookie;
+ }
+
+ // GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set using a wrapper method.
+ public void addCookie11(HttpServletRequest request, HttpServletResponse response, String jwt) {
+ Cookie cookie = createHttpOnlyAuthenticationCookie(request, jwt);
+ response.addCookie(cookie);
+ }
+
+ // BAD - Tests set a sensitive cookie header without the `HttpOnly` flag set using a wrapper method.
+ public void addCookie12(HttpServletRequest request, HttpServletResponse response, String jwt) {
+ Cookie cookie = createAuthenticationCookie(request, jwt);
+ response.addCookie(cookie);
+ }
+
+ private Cookie createCookie(String name, String value, Boolean httpOnly){
+ Cookie cookie = null;
+ cookie = new Cookie(name, value);
+ cookie.setDomain("/");
+ cookie.setHttpOnly(httpOnly);
+
+ //for production https
+ cookie.setSecure(true);
+
+ cookie.setMaxAge(60*60*24*30);
+ cookie.setPath("/");
+
+ return cookie;
+ }
+
+ // GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set through a boolean variable using a wrapper method.
+ public void addCookie13(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
+ response.addCookie(createCookie("refresh_token", refreshToken, true));
+ }
+
+ // BAD - Tests set a sensitive cookie header with the `HttpOnly` flag not set through a boolean variable using a wrapper method.
+ public void addCookie14(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
+ response.addCookie(createCookie("refresh_token", refreshToken, false));
+ }
+
// GOOD - CSRF token doesn't need to have the `HttpOnly` flag set.
public void addCsrfCookie(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Spring put the CSRF token in session attribute "_csrf"
From fe0e7f5eac232e2aae155fe332c57c9e30e61cdf Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Thu, 25 Mar 2021 01:45:13 +0000
Subject: [PATCH 0069/1429] Change method check to taint flow
---
.../CWE-1004/SensitiveCookieNotHttpOnly.ql | 70 ++++++++++---------
.../SensitiveCookieNotHttpOnly.expected | 10 +--
.../CWE-1004/SensitiveCookieNotHttpOnly.java | 18 ++++-
3 files changed, 58 insertions(+), 40 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index ac33a6cecb2..db9adc2ee09 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -11,6 +11,7 @@ import java
import semmle.code.java.dataflow.FlowSteps
import semmle.code.java.frameworks.Servlets
import semmle.code.java.dataflow.TaintTracking
+import semmle.code.java.dataflow.TaintTracking2
import DataFlow::PathGraph
/** Gets a regular expression for matching common names of sensitive cookies. */
@@ -31,28 +32,6 @@ predicate isSensitiveCookieNameExpr(Expr expr) {
isSensitiveCookieNameExpr(expr.(AddExpr).getAnOperand())
}
-/** Holds if a string is concatenated with the `HttpOnly` flag. */
-predicate hasHttpOnlyExpr(Expr expr) {
- (
- expr.(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%")
- or
- exists(
- StaticMethodAccess ma // String.format("%s=%s;HttpOnly", "sessionkey", sessionKey)
- |
- ma.getType().getName() = "String" and
- ma.getMethod().getName() = "format" and
- ma.getArgument(0)
- .(CompileTimeConstantExpr)
- .getStringValue()
- .toLowerCase()
- .matches("%httponly%") and
- expr = ma
- )
- )
- or
- hasHttpOnlyExpr(expr.(AddExpr).getAnOperand())
-}
-
/** The method call `Set-Cookie` of `addHeader` or `setHeader`. */
class SetCookieMethodAccess extends MethodAccess {
SetCookieMethodAccess() {
@@ -64,6 +43,22 @@ class SetCookieMethodAccess extends MethodAccess {
}
}
+/**
+ * A taint configuration tracking flow from the text `httponly` to argument 1 of
+ * `SetCookieMethodAccess`.
+ */
+class MatchesHttpOnlyConfiguration extends TaintTracking2::Configuration {
+ MatchesHttpOnlyConfiguration() { this = "MatchesHttpOnlyConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr().(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%")
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() = any(SetCookieMethodAccess ma).getArgument(1)
+ }
+}
+
/** The cookie class of Java EE. */
class CookieClass extends RefType {
CookieClass() {
@@ -79,7 +74,7 @@ predicate isBooleanTrue(Expr expr) {
true
}
-/** Holds if the method or a wrapper method sets the `HttpOnly` flag. */
+/** Holds if the method call sets the `HttpOnly` flag. */
predicate setHttpOnlyInCookie(MethodAccess ma) {
ma.getMethod().getName() = "setHttpOnly" and
(
@@ -93,14 +88,24 @@ predicate setHttpOnlyInCookie(MethodAccess ma) {
isBooleanTrue(mpa.getArgument(i))
)
)
- or
- exists(MethodAccess mca |
- ma.getMethod().calls(mca.getMethod()) and
- setHttpOnlyInCookie(mca)
- )
}
-/** Holds if the method or a wrapper method removes a cookie. */
+/**
+ * A taint configuration tracking flow of a method or a wrapper method that sets
+ * the `HttpOnly` flag.
+ */
+class SetHttpOnlyInCookieConfiguration extends TaintTracking2::Configuration {
+ SetHttpOnlyInCookieConfiguration() { this = "SetHttpOnlyInCookieConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) { any() }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() =
+ any(MethodAccess ma | ma.getMethod() instanceof ResponseAddCookieMethod).getArgument(0)
+ }
+}
+
+/** Holds if the method call removes a cookie. */
predicate removeCookie(MethodAccess ma) {
ma.getMethod().getName() = "setMaxAge" and
ma.getArgument(0).(IntegerLiteral).getIntValue() = 0
@@ -125,15 +130,14 @@ class CookieResponseSink extends DataFlow::ExprNode {
setHttpOnlyInCookie(ma2) or
removeCookie(ma2)
) and
- (
- DataFlow::localExprFlow(ma2.getQualifier(), this.getExpr()) or
- DataFlow::localExprFlow(ma2, this.getExpr())
+ exists(SetHttpOnlyInCookieConfiguration cc |
+ cc.hasFlow(DataFlow::exprNode(ma2.getQualifier()), this)
)
)
or
ma instanceof SetCookieMethodAccess and
this.getExpr() = ma.getArgument(1) and
- not hasHttpOnlyExpr(this.getExpr()) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
+ not exists(MatchesHttpOnlyConfiguration cc | cc.hasFlowToExpr(ma.getArgument(1))) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
) and
not isTestMethod(ma) // Test class or method
)
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
index dae98e92e67..a9d126ca4a9 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
@@ -8,8 +8,8 @@ edges
| SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
| SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
| SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie |
-| SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | SensitiveCookieNotHttpOnly.java:102:25:102:64 | createAuthenticationCookie(...) : Cookie |
-| SensitiveCookieNotHttpOnly.java:102:25:102:64 | createAuthenticationCookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie |
+| SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | SensitiveCookieNotHttpOnly.java:110:25:110:64 | createAuthenticationCookie(...) : Cookie |
+| SensitiveCookieNotHttpOnly.java:110:25:110:64 | createAuthenticationCookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:111:28:111:33 | cookie |
nodes
| SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | semmle.label | "jwt_token" : String |
| SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | semmle.label | jwtCookie |
@@ -26,8 +26,8 @@ nodes
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | semmle.label | secString |
| SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | semmle.label | "Presto-UI-Token" : String |
| SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | semmle.label | cookie : Cookie |
-| SensitiveCookieNotHttpOnly.java:102:25:102:64 | createAuthenticationCookie(...) : Cookie | semmle.label | createAuthenticationCookie(...) : Cookie |
-| SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie | semmle.label | cookie |
+| SensitiveCookieNotHttpOnly.java:110:25:110:64 | createAuthenticationCookie(...) : Cookie | semmle.label | createAuthenticationCookie(...) : Cookie |
+| SensitiveCookieNotHttpOnly.java:111:28:111:33 | cookie | semmle.label | cookie |
#select
| SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" : String | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" | This sensitive cookie |
@@ -38,4 +38,4 @@ nodes
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... | This sensitive cookie |
-| SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" | This sensitive cookie |
+| SensitiveCookieNotHttpOnly.java:111:28:111:33 | cookie | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:111:28:111:33 | cookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" | This sensitive cookie |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
index fdc831d7836..0bb912f6ce6 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
@@ -91,6 +91,14 @@ class SensitiveCookieNotHttpOnly {
return cookie;
}
+ public Cookie removeAuthenticationCookie(HttpServletRequest request, String jwt) {
+ String PRESTO_UI_COOKIE = "Presto-UI-Token";
+ Cookie cookie = new Cookie(PRESTO_UI_COOKIE, jwt);
+ cookie.setPath("/ui");
+ cookie.setMaxAge(0);
+ return cookie;
+ }
+
// GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set using a wrapper method.
public void addCookie11(HttpServletRequest request, HttpServletResponse response, String jwt) {
Cookie cookie = createHttpOnlyAuthenticationCookie(request, jwt);
@@ -103,6 +111,12 @@ class SensitiveCookieNotHttpOnly {
response.addCookie(cookie);
}
+ // GOOD - Tests remove a sensitive cookie header without the `HttpOnly` flag set using a wrapper method.
+ public void addCookie13(HttpServletRequest request, HttpServletResponse response, String jwt) {
+ Cookie cookie = removeAuthenticationCookie(request, jwt);
+ response.addCookie(cookie);
+ }
+
private Cookie createCookie(String name, String value, Boolean httpOnly){
Cookie cookie = null;
cookie = new Cookie(name, value);
@@ -119,12 +133,12 @@ class SensitiveCookieNotHttpOnly {
}
// GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set through a boolean variable using a wrapper method.
- public void addCookie13(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
+ public void addCookie14(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
response.addCookie(createCookie("refresh_token", refreshToken, true));
}
// BAD - Tests set a sensitive cookie header with the `HttpOnly` flag not set through a boolean variable using a wrapper method.
- public void addCookie14(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
+ public void addCookie15(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
response.addCookie(createCookie("refresh_token", refreshToken, false));
}
From 57bd3f3c1459c04aa0098a254522bcc405f86d80 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Thu, 25 Mar 2021 10:44:26 +0000
Subject: [PATCH 0070/1429] Optimize the taint flow source
---
.../CWE-1004/SensitiveCookieNotHttpOnly.ql | 19 ++++++-------------
1 file changed, 6 insertions(+), 13 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index db9adc2ee09..f1bc7879b8a 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -97,7 +97,10 @@ predicate setHttpOnlyInCookie(MethodAccess ma) {
class SetHttpOnlyInCookieConfiguration extends TaintTracking2::Configuration {
SetHttpOnlyInCookieConfiguration() { this = "SetHttpOnlyInCookieConfiguration" }
- override predicate isSource(DataFlow::Node source) { any() }
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() =
+ any(MethodAccess ma | setHttpOnlyInCookie(ma) or removeCookie(ma)).getQualifier()
+ }
override predicate isSink(DataFlow::Node sink) {
sink.asExpr() =
@@ -123,21 +126,11 @@ class CookieResponseSink extends DataFlow::ExprNode {
(
ma.getMethod() instanceof ResponseAddCookieMethod and
this.getExpr() = ma.getArgument(0) and
- not exists(
- MethodAccess ma2 // a method or wrapper method that invokes cookie.setHttpOnly(true)
- |
- (
- setHttpOnlyInCookie(ma2) or
- removeCookie(ma2)
- ) and
- exists(SetHttpOnlyInCookieConfiguration cc |
- cc.hasFlow(DataFlow::exprNode(ma2.getQualifier()), this)
- )
- )
+ not exists(SetHttpOnlyInCookieConfiguration cc | cc.hasFlowTo(this))
or
ma instanceof SetCookieMethodAccess and
this.getExpr() = ma.getArgument(1) and
- not exists(MatchesHttpOnlyConfiguration cc | cc.hasFlowToExpr(ma.getArgument(1))) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
+ not exists(MatchesHttpOnlyConfiguration cc | cc.hasFlowTo(this)) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
) and
not isTestMethod(ma) // Test class or method
)
From 2576c86ebf2e8a87a8cbe5165ae2ab95a7bec155 Mon Sep 17 00:00:00 2001
From: alexet
Date: Thu, 25 Mar 2021 16:16:16 +0000
Subject: [PATCH 0071/1429] Docs: Update the language specification for changes
to super.
---
.../ql-language-reference/ql-language-specification.rst | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/docs/codeql/ql-language-reference/ql-language-specification.rst b/docs/codeql/ql-language-reference/ql-language-specification.rst
index 0fe54210504..ad88b791019 100644
--- a/docs/codeql/ql-language-reference/ql-language-specification.rst
+++ b/docs/codeql/ql-language-reference/ql-language-specification.rst
@@ -1116,8 +1116,6 @@ A super expression may only occur in a QL program as the receiver expression for
If a super expression includes a ``type``, then that type must be a class that the enclosing class inherits from.
-If the super expression does not include a type, then the enclosing class must have a single declared base type, and that base type must be a class.
-
The value of a super expression is the same as the value of ``this`` in the named tuple.
Casts
@@ -1169,7 +1167,12 @@ A valid call with results *resolves* to a set of predicates. The ways a call can
- If the call has no receiver and the predicate name is a selection identifier, then the qualifier is resolved as a module (see "`Module resolution <#module-resolution>`__"). The identifier is then resolved in the exported predicate environment of the qualifier module.
-- If the call has a super expression as the receiver, then it resolves to a member predicate in a class the enclosing class inherits from. If the super expression is unqualified, then the super-class is the single class that the current class inherits from. If there is not exactly one such class, then the program is invalid. Otherwise the super-class is the class named by the qualifier of the super expression. The predicate is resolved by looking up its name and arity in the exported predicate environment of the super-class.
+- If the call has a super expression as the receiver, then it resolves to a member predicate in a class that the enclosing class inherits from:
+ - If the super expression is unqualified and there is a single class that the current class inherits from then the super-class is that class.
+ - If the super expression is unqualified and there is are multiple classes that the current class inherits from then the super-class is the domain type.
+ - Otherwise the super-class is the class named by the qualifier of the super expression.
+
+ The predicate is resolved by looking up its name and arity in the exported predicate environment of the super-class.
- If the type of the receiver is the same as the enclosing class, the predicate is resolved by looking up its name and arity in the visible predicate environment of the class.
From 2ca95166d916cc89bf2d7086aa664ef3757c95e4 Mon Sep 17 00:00:00 2001
From: Porcuiney Hairs
Date: Wed, 13 Jan 2021 01:32:54 +0530
Subject: [PATCH 0072/1429] Java : add query to detect insecure loading of Dex
File
---
.../CWE/CWE-094/InsecureDexLoading.qhelp | 44 ++++++++
.../CWE/CWE-094/InsecureDexLoading.ql | 20 ++++
.../CWE/CWE-094/InsecureDexLoading.qll | 100 ++++++++++++++++++
.../CWE/CWE-094/InsecureDexLoadingBad.java | 32 ++++++
.../CWE/CWE-094/InsecureDexLoadingGood.java | 23 ++++
5 files changed, 219 insertions(+)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.ql
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qll
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingBad.java
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingGood.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp
new file mode 100644
index 00000000000..feda3af3fc2
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp
@@ -0,0 +1,44 @@
+
+
+
+
+Shared world writable storage spaces are not secure to load Dex libraries from. A malicious actor can replace a dex file with a maliciously crafted file
+which when loaded by the app can lead to code execution.
+
+
+
+
+
+ Loading a file from private storage instead of a world writable one can prevent this issue.
+ As the attacker cannot access files stored by the app in its private storage.
+
+
+
+
+
+ The following example loads a Dex file from a shared world writable location. in this case,
+ since the `/sdcard` directory is on external storage, any one can read/write to the location.
+ bypassing all Android security policies. Hence, this is insecure.
+
+
+
+
+ The next example loads a Dex file stored inside the app's private storage.
+ This is not exploitable as nobody else except the app can access the data stored here.
+
+
+
+
+
+
+ Android Documentation:
+ Data and file storage overview
+ .
+
+
+ Android Documentation:
+ DexClassLoader
+ .
+
+
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.ql b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.ql
new file mode 100644
index 00000000000..58d9844d38a
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Insecure loading of an Android Dex File
+ * @description Loading a DEX library located in a world-readable/ writable location such as
+ * a SD card can cause arbitary code execution vulnerabilities.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id java/android-insecure-dex-loading
+ * @tags security
+ * external/cwe/cwe-094
+ */
+
+import java
+import InsecureDexLoading
+import DataFlow::PathGraph
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, InsecureDexConfiguration conf
+where conf.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Potential arbitary code execution due to $@.",
+ source.getNode(), "a value loaded from a world readable/writable source."
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qll b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qll
new file mode 100644
index 00000000000..2a4b387be7e
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qll
@@ -0,0 +1,100 @@
+import java
+import semmle.code.java.dataflow.FlowSources
+
+/**
+ * A taint-tracking configuration fordetecting unsafe use of a
+ * `DexClassLoader` by an Android app.
+ */
+class InsecureDexConfiguration extends TaintTracking::Configuration {
+ InsecureDexConfiguration() { this = "Insecure Dex File Load" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof InsecureDexSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof InsecureDexSink }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ flowStep(pred, succ)
+ }
+}
+
+/** A data flow source for insecure Dex class loading vulnerabilities. */
+abstract class InsecureDexSource extends DataFlow::Node { }
+
+/** A data flow sink for insecure Dex class loading vulnerabilities. */
+abstract class InsecureDexSink extends DataFlow::Node { }
+
+private predicate flowStep(DataFlow::Node pred, DataFlow::Node succ) {
+ // propagate from a `java.io.File` via the `File.getAbsolutePath` call.
+ exists(MethodAccess m |
+ m.getMethod().getDeclaringType() instanceof TypeFile and
+ m.getMethod().hasName("getAbsolutePath") and
+ m.getQualifier() = pred.asExpr() and
+ m = succ.asExpr()
+ )
+ or
+ // propagate from a `java.io.File` via the `File.toString` call.
+ exists(MethodAccess m |
+ m.getMethod().getDeclaringType() instanceof TypeFile and
+ m.getMethod().hasName("toString") and
+ m.getQualifier() = pred.asExpr() and
+ m = succ.asExpr()
+ )
+ or
+ // propagate to newly created `File` if the parent directory of the new `File` is tainted
+ exists(ConstructorCall cc |
+ cc.getConstructedType() instanceof TypeFile and
+ cc.getArgument(0) = pred.asExpr() and
+ cc = succ.asExpr()
+ )
+}
+
+/**
+ * An argument to a `DexClassLoader` call taken as a sink for
+ * insecure Dex class loading vulnerabilities.
+ */
+private class DexClassLoader extends InsecureDexSink {
+ DexClassLoader() {
+ exists(ConstructorCall cc |
+ cc.getConstructedType().hasQualifiedName("dalvik.system", "DexClassLoader")
+ |
+ this.asExpr() = cc.getArgument(0)
+ )
+ }
+}
+
+/**
+ * An `File` instance which reads from an SD card
+ * taken as a source for insecure Dex class loading vulnerabilities.
+ */
+private class ExternalFile extends InsecureDexSource {
+ ExternalFile() {
+ exists(ConstructorCall cc, Argument a |
+ cc.getConstructedType() instanceof TypeFile and
+ a = cc.getArgument(0) and
+ a.(CompileTimeConstantExpr).getStringValue().matches("%sdcard%")
+ |
+ this.asExpr() = a
+ )
+ }
+}
+
+/**
+ * A directory or file which may be stored in an world writable directory
+ * taken as a source for insecure Dex class loading vulnerabilities.
+ */
+private class ExternalStorageDirSource extends InsecureDexSource {
+ ExternalStorageDirSource() {
+ exists(Method m |
+ m.getDeclaringType().hasQualifiedName("android.os", "Environment") and
+ m.hasName("getExternalStorageDirectory")
+ or
+ m.getDeclaringType().hasQualifiedName("android.content", "Context") and
+ m.hasName([
+ "getExternalFilesDir", "getExternalFilesDirs", "getExternalMediaDirs",
+ "getExternalCacheDir", "getExternalCacheDirs"
+ ])
+ |
+ this.asExpr() = m.getAReference()
+ )
+ }
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingBad.java b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingBad.java
new file mode 100644
index 00000000000..d8fdd828f4f
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingBad.java
@@ -0,0 +1,32 @@
+
+import android.app.Application;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.Bundle;
+
+import dalvik.system.DexClassLoader;
+import dalvik.system.DexFile;
+
+public class InsecureDexLoading extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ updateChecker();
+ }
+
+ private void updateChecker() {
+ try {
+ File file = new File("/sdcard/updater.apk");
+ if (file.exists() && file.isFile() && file.length() <= 1000) {
+ DexClassLoader cl = new DexClassLoader(file.getAbsolutePath(), getCacheDir().getAbsolutePath(), null,
+ getClassLoader());
+ int version = (int) cl.loadClass("my.package.class").getDeclaredMethod("myMethod").invoke(null);
+ if (Build.VERSION.SDK_INT < version) {
+ Toast.makeText(this, "Securely loaded Dex!", Toast.LENGTH_LONG).show();
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingGood.java b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingGood.java
new file mode 100644
index 00000000000..e45e3938f7b
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingGood.java
@@ -0,0 +1,23 @@
+public class SecureDexLoading extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ updateChecker();
+ }
+
+ private void updateChecker() {
+ try {
+ File file = new File(getCacheDir() + "/updater.apk");
+ if (file.exists() && file.isFile() && file.length() <= 1000) {
+ DexClassLoader cl = new DexClassLoader(file.getAbsolutePath(), getCacheDir().getAbsolutePath(), null,
+ getClassLoader());
+ int version = (int) cl.loadClass("my.package.class").getDeclaredMethod("myMethod").invoke(null);
+ if (Build.VERSION.SDK_INT < version) {
+ Toast.makeText(this, "Securely loaded Dex!", Toast.LENGTH_LONG).show();
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+}
\ No newline at end of file
From 62a0775cf69c8bda45aeb9b0cdf60f28dafd9c72 Mon Sep 17 00:00:00 2001
From: yoff
Date: Thu, 25 Mar 2021 23:09:11 +0100
Subject: [PATCH 0073/1429] Update
python/ql/src/Security/CWE-327/examples/secure_protocol.py
Co-authored-by: Rasmus Wriedt Larsen
---
python/ql/src/Security/CWE-327/examples/secure_protocol.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/Security/CWE-327/examples/secure_protocol.py b/python/ql/src/Security/CWE-327/examples/secure_protocol.py
index 94b3557d017..e349bdae832 100644
--- a/python/ql/src/Security/CWE-327/examples/secure_protocol.py
+++ b/python/ql/src/Security/CWE-327/examples/secure_protocol.py
@@ -4,7 +4,7 @@ import ssl
hostname = 'www.python.org'
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
context.options |= ssl.OP_NO_TLSv1
-context.options |= ssl.OP_NO_TLSv1_1 # This added by me
+context.options |= ssl.OP_NO_TLSv1_1
with socket.create_connection((hostname, 443)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
From 2b257318f1c9e1d4a27f9022d94a08fc3e4cc3da Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 25 Mar 2021 23:22:24 +0100
Subject: [PATCH 0074/1429] Python: more precise comment
---
python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
index 3ff1207b527..ab80ed47dac 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
@@ -28,7 +28,7 @@ ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_2)
SSLContext(protocol=ssl.PROTOCOL_TLSv1_2)
SSL.Context(SSL.TLSv1_2_METHOD)
-# possibly secure versions specified
+# insecure versions allowed by specified range
SSLContext(protocol=ssl.PROTOCOL_SSLv23)
SSLContext(protocol=ssl.PROTOCOL_TLS)
SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT)
From 54dad57cf4a22a9d01a434d0633606fa8adf7c8f Mon Sep 17 00:00:00 2001
From: yoff
Date: Fri, 26 Mar 2021 00:25:40 +0100
Subject: [PATCH 0075/1429] Update
python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
Co-authored-by: Rasmus Wriedt Larsen
---
python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py b/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
index e4205c49824..fa771411882 100644
--- a/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
@@ -23,6 +23,8 @@ def test_fluent_no_TLSv1():
def test_fluent_safe():
hostname = 'www.python.org'
context = SSL.Context(SSL.SSLv23_METHOD)
+ context.set_options(SSL.OP_NO_SSLv2)
+ context.set_options(SSL.OP_NO_SSLv3)
context.set_options(SSL.OP_NO_TLSv1)
context.set_options(SSL.OP_NO_TLSv1_1)
From 554404575d243ffdff19559e8cbbf4c300019d32 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 00:29:40 +0100
Subject: [PATCH 0076/1429] Python: fix typo and name.
---
python/ql/src/Security/CWE-327/ReadMe.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/Security/CWE-327/ReadMe.md b/python/ql/src/Security/CWE-327/ReadMe.md
index c5330a78fda..680ae56f493 100644
--- a/python/ql/src/Security/CWE-327/ReadMe.md
+++ b/python/ql/src/Security/CWE-327/ReadMe.md
@@ -12,7 +12,7 @@ This should be kept up to date; the world is moving fast and protocols are being
- `ssl.wrap_socket` is creating insecure connections, use `SSLContext.wrap_socket` instead. [link](https://docs.python.org/3/library/ssl.html#ssl.wrap_socket)
> Deprecated since version 3.7: Since Python 3.2 and 2.7.9, it is recommended to use the `SSLContext.wrap_socket()` instead of `wrap_socket()`. The top-level function is limited and creates an insecure client socket without server name indication or hostname matching.
-- Default consteructors are fine, a sluent api is used to constrain possible protocols later.
+- Default consteructors are fine, a fluent api is used to constrain possible protocols later.
## Current recomendation
From 9488b8bb18fa89614480024700f51c3500f96725 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 00:31:56 +0100
Subject: [PATCH 0077/1429] Python: actually rename
---
python/ql/src/Security/CWE-327/{ReadMe.md => README.md} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename python/ql/src/Security/CWE-327/{ReadMe.md => README.md} (100%)
diff --git a/python/ql/src/Security/CWE-327/ReadMe.md b/python/ql/src/Security/CWE-327/README.md
similarity index 100%
rename from python/ql/src/Security/CWE-327/ReadMe.md
rename to python/ql/src/Security/CWE-327/README.md
From d33b04cd965de99180434c998f96a15f98c1f5fa Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 26 Mar 2021 02:33:40 +0000
Subject: [PATCH 0078/1429] Query to detect plaintext credentials in Java
properties files
---
.../CWE-555/CredentialsInPropertiesFile.qhelp | 45 ++++++++++
.../CWE-555/CredentialsInPropertiesFile.ql | 87 +++++++++++++++++++
.../CWE/CWE-555/configuration.properties | 26 ++++++
.../CredentialsInPropertiesFile.expected | 5 ++
.../CWE-555/CredentialsInPropertiesFile.qlref | 1 +
.../security/CWE-555/configuration.properties | 37 ++++++++
.../security/CWE-555/messages.properties | 8 ++
7 files changed, 209 insertions(+)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-555/configuration.properties
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.qlref
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/configuration.properties
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/messages.properties
diff --git a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp
new file mode 100644
index 00000000000..afbd40685ba
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp
@@ -0,0 +1,45 @@
+
+
+
+
+ Credentials management issues occur when credentials are stored in plaintext in
+ an application’s properties file. Common credentials include but are not limited
+ to LDAP, mail, database, proxy account, and so on. Storing plaintext credentials
+ in a properties file allows anyone who can read the file access to the protected
+ resource. Good credentials management guidelines require that credentials never
+ be stored in plaintext.
+
+
+
+
+
+ Credentials stored in properties files should be encrypted and recycled regularly.
+ In a Java EE deployment scenario, utilities provided by application servers like
+ keystore and password vault can be used to encrypt and manage credentials.
+
+
+
+
+
+ In the first example, the credentials of a LDAP and datasource properties are stored
+ in cleartext in the properties file.
+
+
+
+ In the second example, the credentials of a LDAP and datasource properties are stored
+ in the encrypted format.
+
+
+
+
+
+
+ OWASP:
+ Password Plaintext Storage
+
+
+ Medium (Rajeev Shukla):
+ Encrypting database password in the application.properties file
+
+
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
new file mode 100644
index 00000000000..2ab074e56d8
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
@@ -0,0 +1,87 @@
+/**
+ * @name Cleartext Credentials in Properties File
+ * @description Finds cleartext credentials in Java properties files.
+ * @kind problem
+ * @id java/credentials-in-properties
+ * @tags security
+ * external/cwe/cwe-555
+ * external/cwe/cwe-256
+ * external/cwe/cwe-260
+ */
+
+import java
+import semmle.code.configfiles.ConfigFiles
+
+private string suspicious() {
+ result = "%password%" or
+ result = "%passwd%" or
+ result = "%account%" or
+ result = "%accnt%" or
+ result = "%credential%" or
+ result = "%token%" or
+ result = "%secret%" or
+ result = "%access%key%"
+}
+
+private string nonSuspicious() {
+ result = "%hashed%" or
+ result = "%encrypted%" or
+ result = "%crypt%"
+}
+
+/** Holds if the value is not cleartext credentials. */
+bindingset[value]
+predicate isNotCleartextCredentials(string value) {
+ value = "" // Empty string
+ or
+ value.length() < 7 // Typical credentials are no less than 6 characters
+ or
+ value.matches("% %") // Sentences containing spaces
+ or
+ value.regexpMatch(".*[^a-zA-Z\\d]{3,}.*") // Contain repeated non-alphanumeric characters such as a fake password pass**** or ????
+ or
+ value.matches("@%") // Starts with the "@" sign
+ or
+ value.regexpMatch("\\$\\{.*\\}") // Variable placeholder ${credentials}
+ or
+ value.matches("%=") // A basic check of encrypted credentials ending with padding characters
+ or
+ value.matches("ENC(%)") // Encrypted value
+ or
+ value.toLowerCase().matches(suspicious()) // Could be message properties or fake passwords
+}
+
+/**
+ * Holds if the credentials are in a non-production properties file indicated by:
+ * a) in a non-production directory
+ * b) with a non-production file name
+ */
+predicate isNonProdCredentials(CredentialsConfig cc) {
+ cc.getFile().getAbsolutePath().matches(["%dev%", "%test%", "%sample%"]) and
+ not cc.getFile().getAbsolutePath().matches("%codeql%") // CodeQL test cases
+}
+
+/** The properties file with configuration key/value pairs. */
+class ConfigProperties extends ConfigPair {
+ ConfigProperties() { this.getFile().getBaseName().toLowerCase().matches("%.properties") }
+}
+
+/** The credentials configuration property. */
+class CredentialsConfig extends ConfigProperties {
+ CredentialsConfig() {
+ this.getNameElement().getName().trim().toLowerCase().matches(suspicious()) and
+ not this.getNameElement().getName().trim().toLowerCase().matches(nonSuspicious())
+ }
+
+ string getName() { result = this.getNameElement().getName().trim() }
+
+ string getValue() { result = this.getValueElement().getValue().trim() }
+}
+
+from CredentialsConfig cc
+where
+ not isNotCleartextCredentials(cc.getValue()) and
+ not isNonProdCredentials(cc)
+select cc,
+ "Plaintext credentials " + cc.getName() + " have cleartext value " + cc.getValue() +
+ " in properties file."
diff --git a/java/ql/src/experimental/Security/CWE/CWE-555/configuration.properties b/java/ql/src/experimental/Security/CWE/CWE-555/configuration.properties
new file mode 100644
index 00000000000..55e8b0d86da
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-555/configuration.properties
@@ -0,0 +1,26 @@
+#***************************** LDAP Credentials *****************************************#
+
+ldap.ldapHost = ldap.example.com
+ldap.ldapPort = 636
+ldap.loginDN = cn=Directory Manager
+
+#### BAD: LDAP credentials are stored in cleartext ####
+ldap.password = mysecpass
+
+#### GOOD: LDAP credentials are stored in the encrypted format ####
+ldap.password = eFRZ3Cqo5zDJWMYLiaEupw==
+
+ldap.domain1 = example
+ldap.domain2 = com
+ldap.url= ldaps://ldap.example.com:636/dc=example,dc=com
+
+#*************************** MS SQL Database Connection **********************************#
+datasource1.driverClassName = com.microsoft.sqlserver.jdbc.SQLServerDriver
+datasource1.url = jdbc:sqlserver://ms.example.com\\exampledb:1433;
+datasource1.username = sa
+
+#### BAD: Datasource credentials are stored in cleartext ####
+datasource1.password = Passw0rd@123
+
+#### GOOD: Datasource credentials are stored in the encrypted format ####
+datasource1.password = VvOgflYS1EUzJdVNDoBcnA==
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
new file mode 100644
index 00000000000..0ce33913932
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
@@ -0,0 +1,5 @@
+| configuration.properties:6:1:6:25 | ldap.password=mysecpass | Plaintext credentials ldap.password have cleartext value mysecpass in properties file. |
+| configuration.properties:18:1:18:35 | datasource1.password=Passw0rd@123 | Plaintext credentials datasource1.password have cleartext value Passw0rd@123 in properties file. |
+| configuration.properties:25:1:25:31 | mail.password=MysecPWxWa@1993 | Plaintext credentials mail.password have cleartext value MysecPWxWa@1993 in properties file. |
+| configuration.properties:33:1:33:50 | com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA | Plaintext credentials com.example.aws.s3.access_key have cleartext value AKMAMQPBYMCD6YSAYCBA in properties file. |
+| configuration.properties:34:1:34:70 | com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k | Plaintext credentials com.example.aws.s3.secret_key have cleartext value 8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k in properties file. |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.qlref b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.qlref
new file mode 100644
index 00000000000..e2536bfe883
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/configuration.properties b/java/ql/test/experimental/query-tests/security/CWE-555/configuration.properties
new file mode 100644
index 00000000000..a044161f097
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/configuration.properties
@@ -0,0 +1,37 @@
+#***************************** LDAP Credentials *****************************************#
+ldap.ldapHost = ldap.example.com
+ldap.ldapPort = 636
+ldap.loginDN = cn=Directory Manager
+#### BAD: LDAP credentials are stored in cleartext ####
+ldap.password = mysecpass
+#### GOOD: LDAP credentials are stored in the encrypted format ####
+ldap.password = eFRZ3Cqo5zDJWMYLiaEupw==
+ldap.domain1 = example
+ldap.domain2 = com
+ldap.url= ldaps://ldap.example.com:636/dc=example,dc=com
+
+#*************************** MS SQL Database Connection **********************************#
+datasource1.driverClassName = com.microsoft.sqlserver.jdbc.SQLServerDriver
+datasource1.url = jdbc:sqlserver://ms.example.com\\exampledb:1433;
+datasource1.username = sa
+#### BAD: Datasource credentials are stored in cleartext ####
+datasource1.password = Passw0rd@123
+#### GOOD: Datasource credentials are stored in the encrypted format ####
+datasource1.password = VvOgflYS1EUzJdVNDoBcnA==
+
+#*************************** Mail Connection **********************************#
+mail.username = test@example.com
+#### BAD: Mail credentials are stored in cleartext ####
+mail.password = MysecPWxWa@1993
+#### GOOD: Mail credentials are stored in the encrypted format ####
+mail.password = M*********@1993
+
+#*************************** AWS S3 Connection **********************************#
+com.example.aws.s3.bucket_name=com-bucket-1
+com.example.aws.s3.directory_name=com-directory-1
+#### BAD: Access keys are stored in properties file in cleartext ####
+com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA
+com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k
+#### GOOD: Access keys are not stored in properties file ####
+com.example.aws.s3.access_key=${ENV:AWS_ACCESS_KEY_ID}
+com.example.aws.s3.secret_key=${ENV:AWS_SECRET_ACCESS_KEY}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/messages.properties b/java/ql/test/experimental/query-tests/security/CWE-555/messages.properties
new file mode 100644
index 00000000000..fac63ec23e8
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/messages.properties
@@ -0,0 +1,8 @@
+prompt.username=Username
+prompt.password=Password
+
+forgot_password.error=Please enter a valid email address.
+reset_password.error=Passwords must match and not be empty.
+
+login.password_expired=Your current password has expired. Please reset your password.
+login.login_failure=Unable to verify username or password. Please try again.
From 936757b4bf385addba174a37c24f029781110553 Mon Sep 17 00:00:00 2001
From: yoff
Date: Fri, 26 Mar 2021 08:05:51 +0100
Subject: [PATCH 0079/1429] Update
python/ql/src/Security/CWE-327/FluentApiModel.qll
Co-authored-by: Rasmus Wriedt Larsen
---
python/ql/src/Security/CWE-327/FluentApiModel.qll | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index d222af70499..4d63a14bdbc 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -39,13 +39,7 @@ class InsecureContextConfiguration extends DataFlow::Configuration {
)
}
- override predicate isBarrierIn(DataFlow::Node node) {
- exists(ProtocolUnrestriction r |
- r = library.protocol_unrestriction() and
- node = r.getContext() and
- r.getUnrestriction() = tracked_version
- )
- }
+ override predicate isBarrierIn(DataFlow::Node node) { this.isSource(node) }
}
/**
From f1619f1ee8f6c5df2aa4d4b862deb46626bed2db Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 08:18:11 +0100
Subject: [PATCH 0080/1429] Python: "source" -> "contextOrigin"
---
.../ql/src/Security/CWE-327/InsecureProtocol.ql | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index 194cc1f5ec1..b945f2e609b 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -18,11 +18,11 @@ string callName(AstNode call) {
exists(Attribute a | a = call | result = callName(a.getObject()) + "." + a.getName())
}
-string sourceName(DataFlow::Node source) {
- result = "call to " + callName(source.asCfgNode().(CallNode).getFunction().getNode())
+string originName(DataFlow::Node contextOrigin) {
+ result = "call to " + callName(contextOrigin.asCfgNode().(CallNode).getFunction().getNode())
or
- not source.asCfgNode() instanceof CallNode and
- not source instanceof ContextCreation and
+ not contextOrigin.asCfgNode() instanceof CallNode and
+ not contextOrigin instanceof ContextCreation and
result = "context modification"
}
@@ -32,11 +32,12 @@ string verb(boolean specific) {
specific = false and result = "allowed"
}
-from DataFlow::Node creation, string insecure_version, DataFlow::Node source, boolean specific
+from
+ DataFlow::Node creation, string insecure_version, DataFlow::Node contextOrigin, boolean specific
where
- unsafe_connection_creation(creation, insecure_version, source, specific)
+ unsafe_connection_creation(creation, insecure_version, contextOrigin, specific)
or
- unsafe_context_creation(creation, insecure_version, source.asCfgNode()) and specific = true
+ unsafe_context_creation(creation, insecure_version, contextOrigin.asCfgNode()) and specific = true
select creation,
"Insecure SSL/TLS protocol version " + insecure_version + " " + verb(specific) + " by $@ ",
- source, sourceName(source)
+ contextOrigin, originName(contextOrigin)
From e936540863385a7ff8437f890bc7bba24b00c547 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 08:22:09 +0100
Subject: [PATCH 0081/1429] Python: remove internal import
---
python/ql/src/Security/CWE-327/Ssl.qll | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index be16138b961..88aaebf67ba 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -1,6 +1,5 @@
import python
import semmle.python.ApiGraphs
-import semmle.python.dataflow.new.internal.Attributes as Attributes
import TlsLibraryModel
class SSLContextCreation extends ContextCreation {
@@ -104,7 +103,7 @@ class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction {
ProtocolVersion restriction;
ContextSetVersion() {
- exists(Attributes::AttrWrite aw |
+ exists(DataFlow::AttrWrite aw |
aw.getObject().asCfgNode() = node and
aw.getAttributeName() = "minimum_version" and
aw.getValue() =
From b21672c81cbc9a82f56e54093652b6a741dbe3e6 Mon Sep 17 00:00:00 2001
From: Alexander Eyers-Taylor
Date: Fri, 26 Mar 2021 11:14:09 +0000
Subject: [PATCH 0082/1429] Apply suggestions from code review
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
Co-authored-by: Marcono1234
---
.../ql-language-reference/ql-language-specification.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/codeql/ql-language-reference/ql-language-specification.rst b/docs/codeql/ql-language-reference/ql-language-specification.rst
index ad88b791019..fb89100b401 100644
--- a/docs/codeql/ql-language-reference/ql-language-specification.rst
+++ b/docs/codeql/ql-language-reference/ql-language-specification.rst
@@ -1168,9 +1168,9 @@ A valid call with results *resolves* to a set of predicates. The ways a call can
- If the call has no receiver and the predicate name is a selection identifier, then the qualifier is resolved as a module (see "`Module resolution <#module-resolution>`__"). The identifier is then resolved in the exported predicate environment of the qualifier module.
- If the call has a super expression as the receiver, then it resolves to a member predicate in a class that the enclosing class inherits from:
- - If the super expression is unqualified and there is a single class that the current class inherits from then the super-class is that class.
- - If the super expression is unqualified and there is are multiple classes that the current class inherits from then the super-class is the domain type.
- - Otherwise the super-class is the class named by the qualifier of the super expression.
+ - If the super expression is unqualified and there is a single class that the current class inherits from, then the super-class is that class.
+ - If the super expression is unqualified and there are multiple classes that the current class inherits from, then the super-class is the domain type.
+ - Otherwise, the super-class is the class named by the qualifier of the super expression.
The predicate is resolved by looking up its name and arity in the exported predicate environment of the super-class.
From 1be2be843dd4a0afca316598e227d4a89aea5433 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 13:08:23 +0100
Subject: [PATCH 0083/1429] Python: update test expectations
---
.../test/query-tests/Security/CWE-327/InsecureProtocol.expected | 2 --
1 file changed, 2 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index e578a335d84..f4cd96eb82e 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -17,8 +17,6 @@
| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv2 allowed by $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
-| pyOpenSSL_fluent.py:29:27:29:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv2 allowed by $@ | pyOpenSSL_fluent.py:25:15:25:44 | ControlFlowNode for Attribute() | call to SSL.Context |
-| pyOpenSSL_fluent.py:29:27:29:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | pyOpenSSL_fluent.py:25:15:25:44 | ControlFlowNode for Attribute() | call to SSL.Context |
| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:19:14:19:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:15:15:15:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
From 2e948da3b4a5de3e0bc56d4ad0d19f4f787d5473 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 13:08:45 +0100
Subject: [PATCH 0084/1429] Python: suggested refactor
---
python/ql/src/Security/CWE-327/FluentApiModel.qll | 5 ++---
python/ql/src/Security/CWE-327/InsecureProtocol.ql | 4 +++-
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 4d63a14bdbc..543c32799bc 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -67,9 +67,8 @@ predicate unsafe_connection_creation(
}
/** A connection is created insecurely without reference to a context. */
-predicate unsafe_context_creation(DataFlow::Node node, string insecure_version, CallNode call) {
+predicate unsafe_context_creation(DataFlow::CallCfgNode call, string insecure_version) {
exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation(insecure_version) |
- cc = node and
- cc.getNode() = call
+ cc = call
)
}
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index b945f2e609b..a716b894880 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -37,7 +37,9 @@ from
where
unsafe_connection_creation(creation, insecure_version, contextOrigin, specific)
or
- unsafe_context_creation(creation, insecure_version, contextOrigin.asCfgNode()) and specific = true
+ unsafe_context_creation(creation, insecure_version) and
+ contextOrigin = creation and
+ specific = true
select creation,
"Insecure SSL/TLS protocol version " + insecure_version + " " + verb(specific) + " by $@ ",
contextOrigin, originName(contextOrigin)
From 7d7cbc49db35d0b05f12594984ac36ef529699a1 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 14:20:38 +0100
Subject: [PATCH 0085/1429] Fix comments. This induced fixing the code, since
things were wired up wrongly. Currently the only implementation of
`insecure_connection_creation` is `ssl.wrap_socket`, which is also the sole
target of py/insecure-default-protocol`, so perhaps this part should be
turned off?
---
.../src/Security/CWE-327/FluentApiModel.qll | 39 +++++++++++++------
.../src/Security/CWE-327/InsecureProtocol.ql | 18 ++++++---
2 files changed, 40 insertions(+), 17 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 543c32799bc..169996081dc 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -43,16 +43,21 @@ class InsecureContextConfiguration extends DataFlow::Configuration {
}
/**
- * A connection is created from a context allowing an insecure protocol,
- * and that protocol has not been restricted appropriately.
+ * Holds if `conectionCreation` marks the creation of a connetion based on the contex
+ * found at `contextOrigin` and allowing `insecure_version`.
+ * `specific` is true iff the context if configured for a specific protocol version rather
+ * than for a family of protocols.
*/
-predicate unsafe_connection_creation(
- DataFlow::Node creation, ProtocolVersion insecure_version, DataFlow::Node source, boolean specific
+predicate unsafe_connection_creation_with_context(
+ DataFlow::Node connectionCreation, ProtocolVersion insecure_version, DataFlow::Node contextOrigin,
+ boolean specific
) {
// Connection created from a context allowing `insecure_version`.
- exists(InsecureContextConfiguration c, ProtocolUnrestriction cc | c.hasFlow(cc, creation) |
+ exists(InsecureContextConfiguration c, ProtocolUnrestriction co |
+ c.hasFlow(co, connectionCreation)
+ |
insecure_version = c.getTrackedVersion() and
- source = cc and
+ contextOrigin = co and
specific = false
)
or
@@ -60,15 +65,27 @@ predicate unsafe_connection_creation(
exists(TlsLibrary l, DataFlow::CfgNode cc |
cc = l.insecure_connection_creation(insecure_version)
|
- creation = cc and
- source = cc and
+ connectionCreation = cc and
+ contextOrigin = cc and
specific = true
)
}
-/** A connection is created insecurely without reference to a context. */
-predicate unsafe_context_creation(DataFlow::CallCfgNode call, string insecure_version) {
+/**
+ * Holds if `conectionCreation` marks the creation of a connetion witout reference to a context
+ * and allowing `insecure_version`.
+ * `specific` is true iff the context if configured for a specific protocol version rather
+ * than for a family of protocols.
+ */
+predicate unsafe_connection_creation_without_context(
+ DataFlow::CallCfgNode connectionCreation, string insecure_version
+) {
+ exists(TlsLibrary l | connectionCreation = l.insecure_connection_creation(insecure_version))
+}
+
+/** Holds if `contextCreation` is creating a context ties to a specific insecure version. */
+predicate unsafe_context_creation(DataFlow::CallCfgNode contextCreation, string insecure_version) {
exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation(insecure_version) |
- cc = call
+ contextCreation = cc
)
}
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index a716b894880..974c36ddb0c 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -33,13 +33,19 @@ string verb(boolean specific) {
}
from
- DataFlow::Node creation, string insecure_version, DataFlow::Node contextOrigin, boolean specific
+ DataFlow::Node connectionCreation, string insecure_version, DataFlow::Node protocolConfiguration,
+ boolean specific
where
- unsafe_connection_creation(creation, insecure_version, contextOrigin, specific)
+ unsafe_connection_creation_with_context(connectionCreation, insecure_version,
+ protocolConfiguration, specific)
or
- unsafe_context_creation(creation, insecure_version) and
- contextOrigin = creation and
+ unsafe_connection_creation_without_context(connectionCreation, insecure_version) and
+ protocolConfiguration = connectionCreation and
specific = true
-select creation,
+ or
+ unsafe_context_creation(protocolConfiguration, insecure_version) and
+ connectionCreation = protocolConfiguration and
+ specific = true
+select connectionCreation,
"Insecure SSL/TLS protocol version " + insecure_version + " " + verb(specific) + " by $@ ",
- contextOrigin, originName(contextOrigin)
+ protocolConfiguration, originName(protocolConfiguration)
From 851317e34ffbf27c939d4575fa399c7227c85e6e Mon Sep 17 00:00:00 2001
From: Chris Smowton
Date: Thu, 11 Mar 2021 11:42:32 +0000
Subject: [PATCH 0086/1429] Add models for StrBuilder's fluent methods
---
.../semmle/code/java/frameworks/apache/Lang.qll | 9 +++++++++
.../apache-commons-lang3/StrBuilderTest.java | 14 ++++++++++++++
2 files changed, 23 insertions(+)
diff --git a/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll b/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll
index deab8f4a692..1ddf8876c3d 100644
--- a/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll
+++ b/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll
@@ -427,6 +427,15 @@ private class ApacheStrBuilderModel extends SummaryModelCsv {
}
}
+/**
+ * An Apache Commons-Lang StrBuilder method that returns `this`.
+ */
+private class ApacheStrBuilderFluentMethod extends FluentMethod {
+ ApacheStrBuilderFluentMethod() {
+ this.getReturnType().(RefType).hasQualifiedName("org.apache.commons.lang3.text", "StrBuilder")
+ }
+}
+
/**
* Taint-propagating models for `WordUtils`.
*/
diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrBuilderTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrBuilderTest.java
index bc8887d1150..d460184e176 100644
--- a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrBuilderTest.java
+++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrBuilderTest.java
@@ -128,6 +128,20 @@ class StrBuilderTest {
StrBuilder sb72 = new StrBuilder(); sb72.append(taint()); sink(sb72.toCharArray(0, 0)); // $hasTaintFlow
StrBuilder sb73 = new StrBuilder(); sb73.append(taint()); sink(sb73.toStringBuffer()); // $hasTaintFlow
StrBuilder sb74 = new StrBuilder(); sb74.append(taint()); sink(sb74.toStringBuilder()); // $hasTaintFlow
+
+ // Tests for fluent methods (those returning `this`):
+
+ StrBuilder fluentTest = new StrBuilder();
+ sink(fluentTest.append("Harmless").append(taint()).append("Also harmless").toString()); // $hasTaintFlow
+
+ StrBuilder fluentBackflowTest = new StrBuilder();
+ fluentBackflowTest.append("Harmless").append(taint()).append("Also harmless");
+ sink(fluentBackflowTest.toString()); // $hasTaintFlow
+
+ // Test the case where the fluent method contributing taint is at the end of a statement:
+ StrBuilder fluentBackflowTest2 = new StrBuilder();
+ fluentBackflowTest2.append("Harmless").append(taint());
+ sink(fluentBackflowTest2.toString()); // $hasTaintFlow
}
}
\ No newline at end of file
From 3a274424abc0bdc7b6d84678aa3a9697c9f2ace0 Mon Sep 17 00:00:00 2001
From: Chris Smowton
Date: Thu, 11 Mar 2021 13:45:55 +0000
Subject: [PATCH 0087/1429] Convert fluent method models to csv and generalise
to the three different variants of StrBuilder.
---
.../code/java/frameworks/apache/Lang.qll | 86 +++++++++++++++++++
.../apache-commons-lang3/StrBuilderTest.java | 62 +++++++++++++
.../StrBuilderTextTest.java | 76 ++++++++++++++++
.../TextStringBuilderTest.java | 76 ++++++++++++++++
4 files changed, 300 insertions(+)
diff --git a/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll b/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll
index 1ddf8876c3d..2422efc83d9 100644
--- a/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll
+++ b/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll
@@ -427,6 +427,92 @@ private class ApacheStrBuilderModel extends SummaryModelCsv {
}
}
+private class ApacheStrBuilderFluentMethodsModel extends SummaryModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "org.apache.commons.lang3.text;StrBuilder;false;append;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;appendAll;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;appendFixedWidthPadLeft;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;appendFixedWidthPadRight;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;appendln;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;appendNewLine;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;appendNull;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;appendPadding;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;appendSeparator;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;appendWithSeparators;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;delete;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;deleteAll;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;deleteCharAt;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;deleteFirst;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;ensureCapacity;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;insert;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;minimizeCapacity;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;replace;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;replaceAll;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;replaceFirst;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;reverse;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;setCharAt;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;setLength;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;setNewLineText;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;setNullText;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.lang3.text;StrBuilder;false;trim;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;append;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;appendAll;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;appendFixedWidthPadLeft;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;appendFixedWidthPadRight;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;appendln;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;appendNewLine;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;appendNull;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;appendPadding;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;appendSeparator;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;appendWithSeparators;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;delete;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;deleteAll;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;deleteCharAt;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;deleteFirst;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;ensureCapacity;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;insert;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;minimizeCapacity;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;replace;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;replaceAll;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;replaceFirst;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;reverse;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;setCharAt;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;setLength;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;setNewLineText;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;setNullText;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;StrBuilder;false;trim;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;append;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;appendAll;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;appendFixedWidthPadLeft;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;appendFixedWidthPadRight;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;appendln;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;appendNewLine;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;appendNull;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;appendPadding;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;appendSeparator;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;appendWithSeparators;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;delete;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;deleteAll;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;deleteCharAt;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;deleteFirst;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;ensureCapacity;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;insert;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;minimizeCapacity;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;replace;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;replaceAll;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;replaceFirst;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;reverse;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;setCharAt;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;setLength;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;setNewLineText;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;setNullText;;;Argument[-1];ReturnValue;value",
+ "org.apache.commons.text;TextStringBuilder;false;trim;;;Argument[-1];ReturnValue;value"
+ ]
+ }
+}
+
/**
* An Apache Commons-Lang StrBuilder method that returns `this`.
*/
diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrBuilderTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrBuilderTest.java
index d460184e176..0c0e386e9c2 100644
--- a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrBuilderTest.java
+++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrBuilderTest.java
@@ -142,6 +142,68 @@ class StrBuilderTest {
StrBuilder fluentBackflowTest2 = new StrBuilder();
fluentBackflowTest2.append("Harmless").append(taint());
sink(fluentBackflowTest2.toString()); // $hasTaintFlow
+
+ // Test all fluent methods are passing taint through to their result:
+ StrBuilder fluentAllMethodsTest = new StrBuilder(taint());
+ sink(fluentAllMethodsTest // $hasTaintFlow
+ .append("text")
+ .appendAll("text")
+ .appendFixedWidthPadLeft("text", 4, ' ')
+ .appendFixedWidthPadRight("text", 4, ' ')
+ .appendln("text")
+ .appendNewLine()
+ .appendNull()
+ .appendPadding(0, ' ')
+ .appendSeparator(',')
+ .appendWithSeparators(new String[] { }, ",")
+ .delete(0, 0)
+ .deleteAll(' ')
+ .deleteCharAt(0)
+ .deleteFirst("delme")
+ .ensureCapacity(100)
+ .insert(1, "insertme")
+ .minimizeCapacity()
+ .replace(0, 0, "replacement")
+ .replaceAll("find", "replace")
+ .replaceFirst("find", "replace")
+ .reverse()
+ .setCharAt(0, 'a')
+ .setLength(500)
+ .setNewLineText("newline")
+ .setNullText("NULL")
+ .trim());
+
+ // Test all fluent methods are passing taint back to their qualifier:
+ StrBuilder fluentAllMethodsTest2 = new StrBuilder();
+ fluentAllMethodsTest2
+ .append("text")
+ .appendAll("text")
+ .appendFixedWidthPadLeft("text", 4, ' ')
+ .appendFixedWidthPadRight("text", 4, ' ')
+ .appendln("text")
+ .appendNewLine()
+ .appendNull()
+ .appendPadding(0, ' ')
+ .appendSeparator(',')
+ .appendWithSeparators(new String[] { }, ",")
+ .delete(0, 0)
+ .deleteAll(' ')
+ .deleteCharAt(0)
+ .deleteFirst("delme")
+ .ensureCapacity(100)
+ .insert(1, "insertme")
+ .minimizeCapacity()
+ .replace(0, 0, "replacement")
+ .replaceAll("find", "replace")
+ .replaceFirst("find", "replace")
+ .reverse()
+ .setCharAt(0, 'a')
+ .setLength(500)
+ .setNewLineText("newline")
+ .setNullText("NULL")
+ .trim()
+ .append(taint());
+ sink(fluentAllMethodsTest2); // $hasTaintFlow
}
}
\ No newline at end of file
diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrBuilderTextTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrBuilderTextTest.java
index 796900e8a3b..74f0f1d17c9 100644
--- a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrBuilderTextTest.java
+++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrBuilderTextTest.java
@@ -128,6 +128,82 @@ class StrBuilderTextTest {
StrBuilder sb72 = new StrBuilder(); sb72.append(taint()); sink(sb72.toCharArray(0, 0)); // $hasTaintFlow
StrBuilder sb73 = new StrBuilder(); sb73.append(taint()); sink(sb73.toStringBuffer()); // $hasTaintFlow
StrBuilder sb74 = new StrBuilder(); sb74.append(taint()); sink(sb74.toStringBuilder()); // $hasTaintFlow
+
+ // Tests for fluent methods (those returning `this`):
+
+ StrBuilder fluentTest = new StrBuilder();
+ sink(fluentTest.append("Harmless").append(taint()).append("Also harmless").toString()); // $hasTaintFlow
+
+ StrBuilder fluentBackflowTest = new StrBuilder();
+ fluentBackflowTest.append("Harmless").append(taint()).append("Also harmless");
+ sink(fluentBackflowTest.toString()); // $hasTaintFlow
+
+ // Test the case where the fluent method contributing taint is at the end of a statement:
+ StrBuilder fluentBackflowTest2 = new StrBuilder();
+ fluentBackflowTest2.append("Harmless").append(taint());
+ sink(fluentBackflowTest2.toString()); // $hasTaintFlow
+
+ // Test all fluent methods are passing taint through to their result:
+ StrBuilder fluentAllMethodsTest = new StrBuilder(taint());
+ sink(fluentAllMethodsTest // $hasTaintFlow
+ .append("text")
+ .appendAll("text")
+ .appendFixedWidthPadLeft("text", 4, ' ')
+ .appendFixedWidthPadRight("text", 4, ' ')
+ .appendln("text")
+ .appendNewLine()
+ .appendNull()
+ .appendPadding(0, ' ')
+ .appendSeparator(',')
+ .appendWithSeparators(new String[] { }, ",")
+ .delete(0, 0)
+ .deleteAll(' ')
+ .deleteCharAt(0)
+ .deleteFirst("delme")
+ .ensureCapacity(100)
+ .insert(1, "insertme")
+ .minimizeCapacity()
+ .replace(0, 0, "replacement")
+ .replaceAll("find", "replace")
+ .replaceFirst("find", "replace")
+ .reverse()
+ .setCharAt(0, 'a')
+ .setLength(500)
+ .setNewLineText("newline")
+ .setNullText("NULL")
+ .trim());
+
+ // Test all fluent methods are passing taint back to their qualifier:
+ StrBuilder fluentAllMethodsTest2 = new StrBuilder();
+ fluentAllMethodsTest2
+ .append("text")
+ .appendAll("text")
+ .appendFixedWidthPadLeft("text", 4, ' ')
+ .appendFixedWidthPadRight("text", 4, ' ')
+ .appendln("text")
+ .appendNewLine()
+ .appendNull()
+ .appendPadding(0, ' ')
+ .appendSeparator(',')
+ .appendWithSeparators(new String[] { }, ",")
+ .delete(0, 0)
+ .deleteAll(' ')
+ .deleteCharAt(0)
+ .deleteFirst("delme")
+ .ensureCapacity(100)
+ .insert(1, "insertme")
+ .minimizeCapacity()
+ .replace(0, 0, "replacement")
+ .replaceAll("find", "replace")
+ .replaceFirst("find", "replace")
+ .reverse()
+ .setCharAt(0, 'a')
+ .setLength(500)
+ .setNewLineText("newline")
+ .setNullText("NULL")
+ .trim()
+ .append(taint());
+ sink(fluentAllMethodsTest2); // $hasTaintFlow
}
}
\ No newline at end of file
diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/TextStringBuilderTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/TextStringBuilderTest.java
index 69db28cb3e9..e490c11c7cb 100644
--- a/java/ql/test/library-tests/frameworks/apache-commons-lang3/TextStringBuilderTest.java
+++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/TextStringBuilderTest.java
@@ -129,6 +129,82 @@ class TextStringBuilderTest {
TextStringBuilder sb72 = new TextStringBuilder(); sb72.append(taint()); sink(sb72.toCharArray(0, 0)); // $hasTaintFlow
TextStringBuilder sb73 = new TextStringBuilder(); sb73.append(taint()); sink(sb73.toStringBuffer()); // $hasTaintFlow
TextStringBuilder sb74 = new TextStringBuilder(); sb74.append(taint()); sink(sb74.toStringBuilder()); // $hasTaintFlow
+
+ // Tests for fluent methods (those returning `this`):
+
+ TextStringBuilder fluentTest = new TextStringBuilder();
+ sink(fluentTest.append("Harmless").append(taint()).append("Also harmless").toString()); // $hasTaintFlow
+
+ TextStringBuilder fluentBackflowTest = new TextStringBuilder();
+ fluentBackflowTest.append("Harmless").append(taint()).append("Also harmless");
+ sink(fluentBackflowTest.toString()); // $hasTaintFlow
+
+ // Test the case where the fluent method contributing taint is at the end of a statement:
+ TextStringBuilder fluentBackflowTest2 = new TextStringBuilder();
+ fluentBackflowTest2.append("Harmless").append(taint());
+ sink(fluentBackflowTest2.toString()); // $hasTaintFlow
+
+ // Test all fluent methods are passing taint through to their result:
+ TextStringBuilder fluentAllMethodsTest = new TextStringBuilder(taint());
+ sink(fluentAllMethodsTest // $hasTaintFlow
+ .append("text")
+ .appendAll("text")
+ .appendFixedWidthPadLeft("text", 4, ' ')
+ .appendFixedWidthPadRight("text", 4, ' ')
+ .appendln("text")
+ .appendNewLine()
+ .appendNull()
+ .appendPadding(0, ' ')
+ .appendSeparator(',')
+ .appendWithSeparators(new String[] { }, ",")
+ .delete(0, 0)
+ .deleteAll(' ')
+ .deleteCharAt(0)
+ .deleteFirst("delme")
+ .ensureCapacity(100)
+ .insert(1, "insertme")
+ .minimizeCapacity()
+ .replace(0, 0, "replacement")
+ .replaceAll("find", "replace")
+ .replaceFirst("find", "replace")
+ .reverse()
+ .setCharAt(0, 'a')
+ .setLength(500)
+ .setNewLineText("newline")
+ .setNullText("NULL")
+ .trim());
+
+ // Test all fluent methods are passing taint back to their qualifier:
+ TextStringBuilder fluentAllMethodsTest2 = new TextStringBuilder();
+ fluentAllMethodsTest2
+ .append("text")
+ .appendAll("text")
+ .appendFixedWidthPadLeft("text", 4, ' ')
+ .appendFixedWidthPadRight("text", 4, ' ')
+ .appendln("text")
+ .appendNewLine()
+ .appendNull()
+ .appendPadding(0, ' ')
+ .appendSeparator(',')
+ .appendWithSeparators(new String[] { }, ",")
+ .delete(0, 0)
+ .deleteAll(' ')
+ .deleteCharAt(0)
+ .deleteFirst("delme")
+ .ensureCapacity(100)
+ .insert(1, "insertme")
+ .minimizeCapacity()
+ .replace(0, 0, "replacement")
+ .replaceAll("find", "replace")
+ .replaceFirst("find", "replace")
+ .reverse()
+ .setCharAt(0, 'a')
+ .setLength(500)
+ .setNewLineText("newline")
+ .setNullText("NULL")
+ .trim()
+ .append(taint());
+ sink(fluentAllMethodsTest2); // $hasTaintFlow
}
}
\ No newline at end of file
From 42b63a61ae82b19a7e61c86bebfe27365d35bf79 Mon Sep 17 00:00:00 2001
From: Chris Smowton
Date: Thu, 11 Mar 2021 18:34:11 +0000
Subject: [PATCH 0088/1429] Add change note
---
java/change-notes/2021-03-11-commons-strbuilder.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 java/change-notes/2021-03-11-commons-strbuilder.md
diff --git a/java/change-notes/2021-03-11-commons-strbuilder.md b/java/change-notes/2021-03-11-commons-strbuilder.md
new file mode 100644
index 00000000000..ce8f647ab0f
--- /dev/null
+++ b/java/change-notes/2021-03-11-commons-strbuilder.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Added support for the Apache Commons Lang and Commons Text StrBuilder class, and its successor TextStringBuilder.
From 8155334fa7b4b72015e1b75bf2563b0b96c699bb Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 15:57:07 +0100
Subject: [PATCH 0089/1429] Python: More elaborate qldoc also refactor code to
match
---
.../src/Security/CWE-327/FluentApiModel.qll | 48 +++++++++++++------
1 file changed, 33 insertions(+), 15 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 169996081dc..d4eb13a133d 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -2,9 +2,21 @@ import python
import TlsLibraryModel
/**
- * Configuration to track flow from the creation of a context to
- * that context being used to create a connection.
- * Flow is broken if the insecure protocol of interest is being restricted.
+ * Configuration to determine the state of a context being used to create
+ * a conection.
+ *
+ * The state is in terms of whether a specific protocol is allowed. This is
+ * either true or false when the context is created and can then be modified
+ * later by either restricting or unrestricting the protocol (see the predicates
+ * `isRestriction` and `isUnrestriction`).
+ *
+ * Since we are interested in the final state, we want the flow to start from
+ * the last unrestriction, so we disallow flow into unrestrictions. We also
+ * model the creation as an unrestriction of everything it allows, to account
+ * for the common case where the creation plays the role of "last unrestriction".
+ *
+ * Since we really want "the last unrestriction, not nullified by a restriction",
+ * we also disallow flow into restrictions.
*/
class InsecureContextConfiguration extends DataFlow::Configuration {
TlsLibrary library;
@@ -17,29 +29,35 @@ class InsecureContextConfiguration extends DataFlow::Configuration {
ProtocolVersion getTrackedVersion() { result = tracked_version }
- override predicate isSource(DataFlow::Node source) {
- // source = library.unspecific_context_creation()
- exists(ProtocolUnrestriction pu |
- pu = library.protocol_unrestriction() and
- pu.getUnrestriction() = tracked_version
- |
- source = pu.getContext()
- )
- }
+ override predicate isSource(DataFlow::Node source) { this.isUnrestriction(source) }
override predicate isSink(DataFlow::Node sink) {
sink = library.connection_creation().getContext()
}
- override predicate isBarrierOut(DataFlow::Node node) {
+ override predicate isBarrierIn(DataFlow::Node node) {
+ this.isRestriction(node)
+ or
+ this.isUnrestriction(node)
+ }
+
+ private predicate isRestriction(DataFlow::Node node) {
exists(ProtocolRestriction r |
r = library.protocol_restriction() and
- node = r.getContext() and
r.getRestriction() = tracked_version
+ |
+ node = r.getContext()
)
}
- override predicate isBarrierIn(DataFlow::Node node) { this.isSource(node) }
+ private predicate isUnrestriction(DataFlow::Node node) {
+ exists(ProtocolUnrestriction pu |
+ pu = library.protocol_unrestriction() and
+ pu.getUnrestriction() = tracked_version
+ |
+ node = pu.getContext()
+ )
+ }
}
/**
From 98dfe1a00a14e6dfc8c27d84cfeb2ee363515f56 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 17:27:43 +0100
Subject: [PATCH 0090/1429] Python: Elaborate qldoc and renames to match
---
python/ql/src/Security/CWE-327/Ssl.qll | 34 ++++++++++++++++++--------
1 file changed, 24 insertions(+), 10 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 88aaebf67ba..110b4bf185a 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -45,7 +45,7 @@ class OptionsAugOr extends ProtocolRestriction {
(
aa.getValue() = flag
or
- impliesValue(aa.getValue(), flag, false, false)
+ impliesBitSet(aa.getValue(), flag, false, false)
)
)
}
@@ -70,7 +70,7 @@ class OptionsAugAndNot extends ProtocolUnrestriction {
(
aa.getValue() = notFlag
or
- impliesValue(aa.getValue(), notFlag, true, true)
+ impliesBitSet(aa.getValue(), notFlag, true, true)
)
)
}
@@ -80,22 +80,36 @@ class OptionsAugAndNot extends ProtocolUnrestriction {
override ProtocolVersion getUnrestriction() { result = restriction }
}
-/** Whether `part` evaluates to `partIsTrue` if `whole` evaluates to `wholeIsTrue`. */
-predicate impliesValue(BinaryExpr whole, Expr part, boolean partIsTrue, boolean wholeIsTrue) {
+/**
+ * Holds if
+ * for every bit, _b_:
+ * `wholeHasBitSet` represents that _b_ is set in `whole`
+ * implies
+ * `partHasBitSet` represents that _b_ is set in `part`
+ *
+ * As an example take `whole` = `part1 & part2`. Then
+ * `impliesBitSet(whole, part1, true, true)` holds
+ * because for any bit in `whole`, if that bit is set it must also be set in `part1`.
+ *
+ * Similarly for `whole` = `part1 | part2`. Here
+ * `impliesBitSet(whole, part1, false, false)` holds
+ * because for any bit in `whole`, if that bit is not set, it cannot be set in `part1`.
+ */
+predicate impliesBitSet(BinaryExpr whole, Expr part, boolean partHasBitSet, boolean wholeHasBitSet) {
whole.getOp() instanceof BitAnd and
(
- wholeIsTrue = true and partIsTrue = true and part in [whole.getLeft(), whole.getRight()]
+ wholeHasBitSet = true and partHasBitSet = true and part in [whole.getLeft(), whole.getRight()]
or
- wholeIsTrue = true and
- impliesValue([whole.getLeft(), whole.getRight()], part, partIsTrue, wholeIsTrue)
+ wholeHasBitSet = true and
+ impliesBitSet([whole.getLeft(), whole.getRight()], part, partHasBitSet, wholeHasBitSet)
)
or
whole.getOp() instanceof BitOr and
(
- wholeIsTrue = false and partIsTrue = false and part in [whole.getLeft(), whole.getRight()]
+ wholeHasBitSet = false and partHasBitSet = false and part in [whole.getLeft(), whole.getRight()]
or
- wholeIsTrue = false and
- impliesValue([whole.getLeft(), whole.getRight()], part, partIsTrue, wholeIsTrue)
+ wholeHasBitSet = false and
+ impliesBitSet([whole.getLeft(), whole.getRight()], part, partHasBitSet, wholeHasBitSet)
)
}
From 470b4d86582595101439a043ab4cd4db935b11a2 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 17:35:36 +0100
Subject: [PATCH 0091/1429] Python: Add missing qldoc
---
python/ql/src/Security/CWE-327/TlsLibraryModel.qll | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
index 3ab880e8bd9..f66d663a7e2 100644
--- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -58,6 +58,10 @@ abstract class ProtocolUnrestriction extends DataFlow::CfgNode {
abstract ProtocolVersion getUnrestriction();
}
+/**
+ * A context is being created with a range of allowed protocols.
+ * This also serves as unrestricting these protocols.
+ */
abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrestriction {
TlsLibrary library;
ProtocolFamily family;
@@ -77,6 +81,7 @@ abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrest
}
}
+/** A model of a TLS library. */
abstract class TlsLibrary extends string {
TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
From 44d62df3f72719d8ba57a2519877db2a179f4ec4 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 17:51:18 +0100
Subject: [PATCH 0092/1429] Python: Fix model of `TLS` and add reference
---
python/ql/src/Security/CWE-327/TlsLibraryModel.qll | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
index f66d663a7e2..245a60b0295 100644
--- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -71,11 +71,14 @@ abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrest
override DataFlow::CfgNode getContext() { result = this }
override ProtocolVersion getUnrestriction() {
+ // see https://www.openssl.org/docs/man1.1.0/man3/TLS_method.html
family = "TLS" and
- result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
+ result in ["SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
or
// This can negotiate a TLS 1.3 connection (!)
- // see https://docs.python.org/3/library/ssl.html#ssl-contexts
+ // see
+ // - https://docs.python.org/3/library/ssl.html#ssl-contexts
+ // - https://www.openssl.org/docs/man1.0.2/man3/TLSv1_method.html
family = "SSLv23" and
result in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
}
From a72b1340eb6bb3bdbea9cabd6bdd942a9365b848 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 26 Mar 2021 16:51:43 +0000
Subject: [PATCH 0093/1429] Add a comment on how to run the query
---
.../Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql b/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql
index 3acd22e767a..772ac6cd209 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql
@@ -8,6 +8,14 @@
* external/cwe-016
*/
+/*
+ * Note this query requires properties files to be indexed before it can produce results.
+ * If creating your own database with the CodeQL CLI, you should run
+ * `codeql database index-files --language=properties ...`
+ * If using lgtm.com, you should add `properties_files: true` to the index block of your
+ * lgtm.yml file (see https://lgtm.com/help/lgtm/java-extraction)
+ */
+
import java
import semmle.code.configfiles.ConfigFiles
import semmle.code.xml.MavenPom
From e0352fe7638223a1d998bdd26752e358d2ec8e92 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 23:26:24 +0100
Subject: [PATCH 0094/1429] Python: remove deprecated section of qhelp file
---
python/ql/src/Security/CWE-327/InsecureProtocol.qhelp | 6 ------
1 file changed, 6 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp b/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp
index cfcebd0930d..9ecc7da0d60 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp
@@ -32,12 +32,6 @@
All cases should be updated to use a secure protocol, such as
PROTOCOL_TLSv1_2.