diff --git a/java/ql/lib/change-notes/2022-03-14-new-jdbc-ssrf-sinks.md b/java/ql/lib/change-notes/2022-03-14-new-jdbc-ssrf-sinks.md new file mode 100644 index 00000000000..c154b12cfad --- /dev/null +++ b/java/ql/lib/change-notes/2022-03-14-new-jdbc-ssrf-sinks.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- + * Added support for detection of SSRF via JDBC database URLs, including connections made using the standard library (`java.sql`), Hikari Connection Pool, JDBI and Spring JDBC. diff --git a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll index c8f1ff47bd2..631c47583e6 100644 --- a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll @@ -131,6 +131,8 @@ private module Frameworks { private import semmle.code.java.security.XPath private import semmle.code.java.security.XsltInjection private import semmle.code.java.frameworks.Jdbc + private import semmle.code.java.frameworks.Jdbi + private import semmle.code.java.frameworks.HikariCP private import semmle.code.java.frameworks.SpringJdbc private import semmle.code.java.frameworks.MyBatis private import semmle.code.java.frameworks.Hibernate diff --git a/java/ql/lib/semmle/code/java/frameworks/HikariCP.qll b/java/ql/lib/semmle/code/java/frameworks/HikariCP.qll new file mode 100644 index 00000000000..30b4ba63405 --- /dev/null +++ b/java/ql/lib/semmle/code/java/frameworks/HikariCP.qll @@ -0,0 +1,17 @@ +/** + * Definitions of sinks in the Hikari Connection Pool library. + */ + +import java +import semmle.code.java.dataflow.ExternalFlow + +private class SsrfSinkCsv extends SinkModelCsv { + override predicate row(string row) { + row = + [ + //"package;type;overrides;name;signature;ext;spec;kind" + "com.zaxxer.hikari;HikariConfig;false;HikariConfig;(Properties);;Argument[0];jdbc-url", + "com.zaxxer.hikari;HikariConfig;false;setJdbcUrl;(String);;Argument[0];jdbc-url" + ] + } +} diff --git a/java/ql/lib/semmle/code/java/frameworks/Jdbc.qll b/java/ql/lib/semmle/code/java/frameworks/Jdbc.qll index b851842dfd3..5a20399f9db 100644 --- a/java/ql/lib/semmle/code/java/frameworks/Jdbc.qll +++ b/java/ql/lib/semmle/code/java/frameworks/Jdbc.qll @@ -52,3 +52,16 @@ private class SqlSinkCsv extends SinkModelCsv { ] } } + +private class SsrfSinkCsv extends SinkModelCsv { + override predicate row(string row) { + row = + [ + //"package;type;overrides;name;signature;ext;spec;kind" + "java.sql;DriverManager;false;getConnection;(String);;Argument[0];jdbc-url", + "java.sql;DriverManager;false;getConnection;(String,Properties);;Argument[0];jdbc-url", + "java.sql;DriverManager;false;getConnection;(String,String,String);;Argument[0];jdbc-url", + "java.sql;Driver;false;connect;(String,Properties);;Argument[0];jdbc-url" + ] + } +} diff --git a/java/ql/lib/semmle/code/java/frameworks/Jdbi.qll b/java/ql/lib/semmle/code/java/frameworks/Jdbi.qll new file mode 100644 index 00000000000..a2984805ce7 --- /dev/null +++ b/java/ql/lib/semmle/code/java/frameworks/Jdbi.qll @@ -0,0 +1,21 @@ +/** + * Definitions of sinks in the JDBI library. + */ + +import java +import semmle.code.java.dataflow.ExternalFlow + +private class SsrfSinkCsv extends SinkModelCsv { + override predicate row(string row) { + row = + [ + //"package;type;overrides;name;signature;ext;spec;kind" + "org.jdbi.v3.core;Jdbi;false;create;(String);;Argument[0];jdbc-url", + "org.jdbi.v3.core;Jdbi;false;create;(String,Properties);;Argument[0];jdbc-url", + "org.jdbi.v3.core;Jdbi;false;create;(String,String,String);;Argument[0];jdbc-url", + "org.jdbi.v3.core;Jdbi;false;open;(String);;Argument[0];jdbc-url", + "org.jdbi.v3.core;Jdbi;false;open;(String,Properties);;Argument[0];jdbc-url", + "org.jdbi.v3.core;Jdbi;false;open;(String,String,String);;Argument[0];jdbc-url" + ] + } +} diff --git a/java/ql/lib/semmle/code/java/frameworks/SpringJdbc.qll b/java/ql/lib/semmle/code/java/frameworks/SpringJdbc.qll index ed7cdfd527f..067c686b912 100644 --- a/java/ql/lib/semmle/code/java/frameworks/SpringJdbc.qll +++ b/java/ql/lib/semmle/code/java/frameworks/SpringJdbc.qll @@ -37,3 +37,17 @@ private class SqlSinkCsv extends SinkModelCsv { ] } } + +private class SsrfSinkCsv extends SinkModelCsv { + override predicate row(string row) { + row = + [ + //"package;type;overrides;name;signature;ext;spec;kind" + "org.springframework.boot.jdbc;DataSourceBuilder;false;url;(String);;Argument[0];jdbc-url", + "org.springframework.jdbc.datasource;AbstractDriverBasedDataSource;false;setUrl;(String);;Argument[0];jdbc-url", + "org.springframework.jdbc.datasource;DriverManagerDataSource;false;DriverManagerDataSource;(String);;Argument[0];jdbc-url", + "org.springframework.jdbc.datasource;DriverManagerDataSource;false;DriverManagerDataSource;(String,String,String);;Argument[0];jdbc-url", + "org.springframework.jdbc.datasource;DriverManagerDataSource;false;DriverManagerDataSource;(String,Properties);;Argument[0];jdbc-url" + ] + } +} diff --git a/java/ql/lib/semmle/code/java/security/RequestForgery.qll b/java/ql/lib/semmle/code/java/security/RequestForgery.qll index b7d3e4a8a77..e6efc13c8a5 100644 --- a/java/ql/lib/semmle/code/java/security/RequestForgery.qll +++ b/java/ql/lib/semmle/code/java/security/RequestForgery.qll @@ -7,6 +7,7 @@ import semmle.code.java.frameworks.spring.Spring import semmle.code.java.frameworks.JaxWS import semmle.code.java.frameworks.javase.Http import semmle.code.java.dataflow.DataFlow +import semmle.code.java.frameworks.Properties private import semmle.code.java.dataflow.StringPrefixes private import semmle.code.java.dataflow.ExternalFlow @@ -33,6 +34,20 @@ private class DefaultRequestForgeryAdditionalTaintStep extends RequestForgeryAdd } } +private class TypePropertiesRequestForgeryAdditionalTaintStep extends RequestForgeryAdditionalTaintStep { + override predicate propagatesTaint(DataFlow::Node pred, DataFlow::Node succ) { + exists(MethodAccess ma | + // Properties props = new Properties(); + // props.setProperty("jdbcUrl", tainted); + // Propagate tainted value to the qualifier `props` + ma.getMethod() instanceof PropertiesSetPropertyMethod and + ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "jdbcUrl" and + pred.asExpr() = ma.getArgument(1) and + succ.asExpr() = ma.getQualifier() + ) + } +} + /** A data flow sink for server-side request forgery (SSRF) vulnerabilities. */ abstract class RequestForgerySink extends DataFlow::Node { } @@ -40,6 +55,10 @@ private class UrlOpenSinkAsRequestForgerySink extends RequestForgerySink { UrlOpenSinkAsRequestForgerySink() { sinkNode(this, "open-url") } } +private class JdbcUrlSinkAsRequestForgerySink extends RequestForgerySink { + JdbcUrlSinkAsRequestForgerySink() { sinkNode(this, "jdbc-url") } +} + /** A sanitizer for request forgery vulnerabilities. */ abstract class RequestForgerySanitizer extends DataFlow::Node { } diff --git a/java/ql/test/query-tests/security/CWE-918/JdbcUrlSSRF.java b/java/ql/test/query-tests/security/CWE-918/JdbcUrlSSRF.java new file mode 100644 index 00000000000..fa202ef0373 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-918/JdbcUrlSSRF.java @@ -0,0 +1,91 @@ +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.sql.DriverManager; +import java.sql.Driver; +import java.sql.SQLException; +import java.io.IOException; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import java.util.*; +import org.springframework.jdbc.datasource.*; +import org.jdbi.v3.core.Jdbi; +import org.springframework.boot.jdbc.DataSourceBuilder; + +public class JdbcUrlSSRF extends HttpServlet { + + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + String jdbcUrl = request.getParameter("jdbcUrl"); + Driver driver = new org.postgresql.Driver(); + DataSourceBuilder dsBuilder = new DataSourceBuilder(); + + try { + driver.connect(jdbcUrl, null); // $ SSRF + + DriverManager.getConnection(jdbcUrl); // $ SSRF + DriverManager.getConnection(jdbcUrl, "user", "password"); // $ SSRF + DriverManager.getConnection(jdbcUrl, null); // $ SSRF + + dsBuilder.url(jdbcUrl); // $ SSRF + } + catch(SQLException e) {} + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + String jdbcUrl = request.getParameter("jdbcUrl"); + HikariConfig config = new HikariConfig(); + + config.setJdbcUrl(jdbcUrl); // $ SSRF + config.setUsername("database_username"); + config.setPassword("database_password"); + + HikariDataSource ds = new HikariDataSource(); + ds.setJdbcUrl(jdbcUrl); // $ SSRF + + Properties props = new Properties(); + props.setProperty("driverClassName", "org.postgresql.Driver"); + props.setProperty("jdbcUrl", jdbcUrl); + + HikariConfig config2 = new HikariConfig(props); // $ SSRF + } + + protected void doPut(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + String jdbcUrl = request.getParameter("jdbcUrl"); + + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + + dataSource.setDriverClassName("org.postgresql.Driver"); + dataSource.setUrl(jdbcUrl); // $ SSRF + + DriverManagerDataSource dataSource2 = new DriverManagerDataSource(jdbcUrl); // $ SSRF + dataSource2.setDriverClassName("org.postgresql.Driver"); + + DriverManagerDataSource dataSource3 = new DriverManagerDataSource(jdbcUrl, "user", "pass"); // $ SSRF + dataSource3.setDriverClassName("org.postgresql.Driver"); + + DriverManagerDataSource dataSource4 = new DriverManagerDataSource(jdbcUrl, null); // $ SSRF + dataSource4.setDriverClassName("org.postgresql.Driver"); + } + + protected void doDelete(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + String jdbcUrl = request.getParameter("jdbcUrl"); + + Jdbi.create(jdbcUrl); // $ SSRF + Jdbi.create(jdbcUrl, null); // $ SSRF + Jdbi.create(jdbcUrl, "user", "pass"); // $ SSRF + + Jdbi.open(jdbcUrl); // $ SSRF + Jdbi.open(jdbcUrl, null); // $ SSRF + Jdbi.open(jdbcUrl, "user", "pass"); // $ SSRF + } + +} \ No newline at end of file diff --git a/java/ql/test/query-tests/security/CWE-918/options b/java/ql/test/query-tests/security/CWE-918/options index 87db9eacec3..5776b35fe9f 100644 --- a/java/ql/test/query-tests/security/CWE-918/options +++ b/java/ql/test/query-tests/security/CWE-918/options @@ -1,2 +1,2 @@ -//semmle-extractor-options: --javac-args -source 11 -target 11 -cp ${testdir}/../../../stubs/springframework-5.3.8:${testdir}/../../../stubs/javax-ws-rs-api-2.1.1:${testdir}/../../../stubs/javax-ws-rs-api-3.0.0:${testdir}/../../../stubs/apache-http-4.4.13/:${testdir}/../../../stubs/servlet-api-2.4/:${testdir}/../../../stubs/projectreactor-3.4.3/ +//semmle-extractor-options: --javac-args -source 11 -target 11 -cp ${testdir}/../../../stubs/springframework-5.3.8:${testdir}/../../../stubs/javax-ws-rs-api-2.1.1:${testdir}/../../../stubs/javax-ws-rs-api-3.0.0:${testdir}/../../../stubs/apache-http-4.4.13/:${testdir}/../../../stubs/servlet-api-2.4/:${testdir}/../../../stubs/projectreactor-3.4.3/:${testdir}/../../../stubs/postgresql-42.3.3/:${testdir}/../../../stubs/HikariCP-3.4.5/:${testdir}/../../../stubs/spring-jdbc-5.3.8/:${testdir}/../../../stubs/jdbi3-core-3.27.2/ diff --git a/java/ql/test/stubs/HikariCP-3.4.5/com/zaxxer/hikari/HikariConfig.java b/java/ql/test/stubs/HikariCP-3.4.5/com/zaxxer/hikari/HikariConfig.java new file mode 100644 index 00000000000..f8ae9aaccb9 --- /dev/null +++ b/java/ql/test/stubs/HikariCP-3.4.5/com/zaxxer/hikari/HikariConfig.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2013, 2014 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zaxxer.hikari; + + +public class HikariConfig implements HikariConfigMXBean { + + private String jdbcUrl; + + public HikariConfig() { + } + + public HikariConfig(java.util.Properties properties) { + + } + + public HikariConfig(String propertyFileName) { + } + + public String getJdbcUrl() { + return jdbcUrl; + } + + public void setJdbcUrl(String jdbcUrl) { + this.jdbcUrl = jdbcUrl; + } + + public long getConnectionTimeout() { return 0; } + + public void setConnectionTimeout(long connectionTimeoutMs) {} + + public long getValidationTimeout() { return 0; } + + public void setValidationTimeout(long validationTimeoutMs) {} + + public long getIdleTimeout() { return 0; } + + public void setIdleTimeout(long idleTimeoutMs) {} + + public long getLeakDetectionThreshold() { return 0; } + + public void setLeakDetectionThreshold(long leakDetectionThresholdMs) {} + + public long getMaxLifetime() { return 0; } + + public void setMaxLifetime(long maxLifetimeMs) {} + + public int getMinimumIdle() { return 0; } + + public void setMinimumIdle(int minIdle) {} + + public int getMaximumPoolSize() { return 0; } + + public void setMaximumPoolSize(int maxPoolSize) {} + + public void setPassword(String password) {} + + public void setUsername(String username) {} + + public String getPoolName() {return "";} + + public String getCatalog() {return "";} + + public void setCatalog(String catalog) {} +} \ No newline at end of file diff --git a/java/ql/test/stubs/HikariCP-3.4.5/com/zaxxer/hikari/HikariConfigMXBean.java b/java/ql/test/stubs/HikariCP-3.4.5/com/zaxxer/hikari/HikariConfigMXBean.java new file mode 100644 index 00000000000..2e510d53360 --- /dev/null +++ b/java/ql/test/stubs/HikariCP-3.4.5/com/zaxxer/hikari/HikariConfigMXBean.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2013 Brett Wooldridge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zaxxer.hikari; + +/** + * The javax.management MBean for a Hikari pool configuration. + * + * @author Brett Wooldridge + */ +public interface HikariConfigMXBean +{ + /** + * Get the maximum number of milliseconds that a client will wait for a connection from the pool. If this + * time is exceeded without a connection becoming available, a SQLException will be thrown from + * {@link javax.sql.DataSource#getConnection()}. + * + * @return the connection timeout in milliseconds + */ + long getConnectionTimeout(); + + /** + * Set the maximum number of milliseconds that a client will wait for a connection from the pool. If this + * time is exceeded without a connection becoming available, a SQLException will be thrown from + * {@link javax.sql.DataSource#getConnection()}. + * + * @param connectionTimeoutMs the connection timeout in milliseconds + */ + void setConnectionTimeout(long connectionTimeoutMs); + + /** + * Get the maximum number of milliseconds that the pool will wait for a connection to be validated as + * alive. + * + * @return the validation timeout in milliseconds + */ + long getValidationTimeout(); + + /** + * Sets the maximum number of milliseconds that the pool will wait for a connection to be validated as + * alive. + * + * @param validationTimeoutMs the validation timeout in milliseconds + */ + void setValidationTimeout(long validationTimeoutMs); + + /** + * This property controls the maximum amount of time (in milliseconds) that a connection is allowed to sit + * idle in the pool. Whether a connection is retired as idle or not is subject to a maximum variation of +30 + * seconds, and average variation of +15 seconds. A connection will never be retired as idle before this timeout. + * A value of 0 means that idle connections are never removed from the pool. + * + * @return the idle timeout in milliseconds + */ + long getIdleTimeout(); + + /** + * This property controls the maximum amount of time (in milliseconds) that a connection is allowed to sit + * idle in the pool. Whether a connection is retired as idle or not is subject to a maximum variation of +30 + * seconds, and average variation of +15 seconds. A connection will never be retired as idle before this timeout. + * A value of 0 means that idle connections are never removed from the pool. + * + * @param idleTimeoutMs the idle timeout in milliseconds + */ + void setIdleTimeout(long idleTimeoutMs); + + /** + * This property controls the amount of time that a connection can be out of the pool before a message is + * logged indicating a possible connection leak. A value of 0 means leak detection is disabled. + * + * @return the connection leak detection threshold in milliseconds + */ + long getLeakDetectionThreshold(); + + /** + * This property controls the amount of time that a connection can be out of the pool before a message is + * logged indicating a possible connection leak. A value of 0 means leak detection is disabled. + * + * @param leakDetectionThresholdMs the connection leak detection threshold in milliseconds + */ + void setLeakDetectionThreshold(long leakDetectionThresholdMs); + + /** + * This property controls the maximum lifetime of a connection in the pool. When a connection reaches this + * timeout, even if recently used, it will be retired from the pool. An in-use connection will never be + * retired, only when it is idle will it be removed. + * + * @return the maximum connection lifetime in milliseconds + */ + long getMaxLifetime(); + + /** + * This property controls the maximum lifetime of a connection in the pool. When a connection reaches this + * timeout, even if recently used, it will be retired from the pool. An in-use connection will never be + * retired, only when it is idle will it be removed. + * + * @param maxLifetimeMs the maximum connection lifetime in milliseconds + */ + void setMaxLifetime(long maxLifetimeMs); + + /** + * The property controls the minimum number of idle connections that HikariCP tries to maintain in the pool, + * including both idle and in-use connections. If the idle connections dip below this value, HikariCP will + * make a best effort to restore them quickly and efficiently. + * + * @return the minimum number of connections in the pool + */ + int getMinimumIdle(); + + /** + * The property controls the minimum number of idle connections that HikariCP tries to maintain in the pool, + * including both idle and in-use connections. If the idle connections dip below this value, HikariCP will + * make a best effort to restore them quickly and efficiently. + * + * @param minIdle the minimum number of idle connections in the pool to maintain + */ + void setMinimumIdle(int minIdle); + + /** + * The property controls the maximum number of connections that HikariCP will keep in the pool, + * including both idle and in-use connections. + * + * @return the maximum number of connections in the pool + */ + int getMaximumPoolSize(); + + /** + * The property controls the maximum size that the pool is allowed to reach, including both idle and in-use + * connections. Basically this value will determine the maximum number of actual connections to the database + * backend. + *
+ * When the pool reaches this size, and no idle connections are available, calls to getConnection() will + * block for up to connectionTimeout milliseconds before timing out. + * + * @param maxPoolSize the maximum number of connections in the pool + */ + void setMaximumPoolSize(int maxPoolSize); + + /** + * Set the password used for authentication. Changing this at runtime will apply to new connections only. + * Altering this at runtime only works for DataSource-based connections, not Driver-class or JDBC URL-based + * connections. + * + * @param password the database password + */ + void setPassword(String password); + + /** + * Set the username used for authentication. Changing this at runtime will apply to new connections only. + * Altering this at runtime only works for DataSource-based connections, not Driver-class or JDBC URL-based + * connections. + * + * @param username the database username + */ + void setUsername(String username); + + + /** + * The name of the connection pool. + * + * @return the name of the connection pool + */ + String getPoolName(); + + /** + * Get the default catalog name to be set on connections. + * + * @return the default catalog name + */ + String getCatalog(); + + /** + * Set the default catalog name to be set on connections. + *
+ * WARNING: THIS VALUE SHOULD ONLY BE CHANGED WHILE THE POOL IS SUSPENDED, AFTER CONNECTIONS HAVE BEEN EVICTED.
+ *
+ * @param catalog the catalog name, or null
+ */
+ void setCatalog(String catalog);
+}
diff --git a/java/ql/test/stubs/HikariCP-3.4.5/com/zaxxer/hikari/HikariDataSource.java b/java/ql/test/stubs/HikariCP-3.4.5/com/zaxxer/hikari/HikariDataSource.java
new file mode 100644
index 00000000000..2823f6dc3ce
--- /dev/null
+++ b/java/ql/test/stubs/HikariCP-3.4.5/com/zaxxer/hikari/HikariDataSource.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari;
+
+import javax.sql.DataSource;
+import java.io.Closeable;
+import java.sql.*;
+import java.util.logging.Logger;
+
+
+public class HikariDataSource extends HikariConfig implements DataSource, Closeable {
+
+ public HikariDataSource() {
+ }
+
+ public HikariDataSource(HikariConfig configuration) {
+ }
+
+ public Connection getConnection() throws SQLException {
+ return null;
+ }
+
+ public Connection getConnection(String username, String password)
+ throws SQLException {
+ return null;
+ }
+
+ public java.io.PrintWriter getLogWriter() throws SQLException {
+ return null;
+ }
+
+ public void setLogWriter(java.io.PrintWriter out) throws SQLException {
+ }
+
+ public void setLoginTimeout(int seconds) throws SQLException {
+ }
+
+ public int getLoginTimeout() throws SQLException {
+ return 0;
+ }
+
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+ return null;
+ }
+
+ public Can also contain "user" and "password" properties. However,
+ * any "username" and "password" bean properties specified on this
+ * DataSource will override the corresponding connection properties.
+ * @see java.sql.Driver#connect(String, java.util.Properties)
+ */
+ public void setConnectionProperties(Properties connectionProperties) {
+ this.connectionProperties = connectionProperties;
+ }
+
+ /**
+ * Return the connection properties to be passed to the Driver, if any.
+ */
+ public Properties getConnectionProperties() {
+ return this.connectionProperties;
+ }
+
+
+ /**
+ * This implementation delegates to {@code getConnectionFromDriver},
+ * using the default username and password of this DataSource.
+ * @see #getConnectionFromDriver(String, String)
+ * @see #setUsername
+ * @see #setPassword
+ */
+ @Override
+ public Connection getConnection() throws SQLException {
+ return getConnectionFromDriver(getUsername(), getPassword());
+ }
+
+ /**
+ * This implementation delegates to {@code getConnectionFromDriver},
+ * using the given username and password.
+ * @see #getConnectionFromDriver(String, String)
+ */
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException {
+ return getConnectionFromDriver(username, password);
+ }
+
+
+ /**
+ * Build properties for the Driver, including the given username and password (if any),
+ * and obtain a corresponding Connection.
+ * @param username the name of the user
+ * @param password the password to use
+ * @return the obtained Connection
+ * @throws SQLException in case of failure
+ * @see java.sql.Driver#connect(String, java.util.Properties)
+ */
+ protected Connection getConnectionFromDriver(String username, String password) throws SQLException {
+ Properties mergedProps = new Properties();
+ Properties connProps = getConnectionProperties();
+ if (connProps != null) {
+ mergedProps.putAll(connProps);
+ }
+ if (username != null) {
+ mergedProps.setProperty("user", username);
+ }
+ if (password != null) {
+ mergedProps.setProperty("password", password);
+ }
+
+ Connection con = getConnectionFromDriver(mergedProps);
+ if (this.catalog != null) {
+ con.setCatalog(this.catalog);
+ }
+ if (this.schema != null) {
+ con.setSchema(this.schema);
+ }
+ return con;
+ }
+
+ /**
+ * Obtain a Connection using the given properties.
+ * Template method to be implemented by subclasses.
+ * @param props the merged connection properties
+ * @return the obtained Connection
+ * @throws SQLException in case of failure
+ */
+ protected abstract Connection getConnectionFromDriver(Properties props) throws SQLException;
+
+}
diff --git a/java/ql/test/stubs/spring-jdbc-5.3.8/org/springframework/jdbc/datasource/DriverManagerDataSource.java b/java/ql/test/stubs/spring-jdbc-5.3.8/org/springframework/jdbc/datasource/DriverManagerDataSource.java
new file mode 100644
index 00000000000..d4fb9545347
--- /dev/null
+++ b/java/ql/test/stubs/spring-jdbc-5.3.8/org/springframework/jdbc/datasource/DriverManagerDataSource.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2002-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.datasource;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Properties;
+
+public class DriverManagerDataSource extends AbstractDriverBasedDataSource {
+
+ /**
+ * Constructor for bean-style configuration.
+ */
+ public DriverManagerDataSource() {
+ }
+
+ /**
+ * Create a new DriverManagerDataSource with the given JDBC URL,
+ * not specifying a username or password for JDBC access.
+ * @param url the JDBC URL to use for accessing the DriverManager
+ * @see java.sql.DriverManager#getConnection(String)
+ */
+ public DriverManagerDataSource(String url) {
+ }
+
+ /**
+ * Create a new DriverManagerDataSource with the given standard
+ * DriverManager parameters.
+ * @param url the JDBC URL to use for accessing the DriverManager
+ * @param username the JDBC username to use for accessing the DriverManager
+ * @param password the JDBC password to use for accessing the DriverManager
+ * @see java.sql.DriverManager#getConnection(String, String, String)
+ */
+ public DriverManagerDataSource(String url, String username, String password) {
+ }
+
+ /**
+ * Create a new DriverManagerDataSource with the given JDBC URL,
+ * not specifying a username or password for JDBC access.
+ * @param url the JDBC URL to use for accessing the DriverManager
+ * @param conProps the JDBC connection properties
+ * @see java.sql.DriverManager#getConnection(String)
+ */
+ public DriverManagerDataSource(String url, Properties conProps) {
+ }
+
+
+ /**
+ * Set the JDBC driver class name. This driver will get initialized
+ * on startup, registering itself with the JDK's DriverManager.
+ * NOTE: DriverManagerDataSource is primarily intended for accessing
+ * pre-registered JDBC drivers. If you need to register a new driver,
+ * consider using {@link SimpleDriverDataSource} instead. Alternatively, consider
+ * initializing the JDBC driver yourself before instantiating this DataSource.
+ * The "driverClassName" property is mainly preserved for backwards compatibility,
+ * as well as for migrating between Commons DBCP and this DataSource.
+ * @see java.sql.DriverManager#registerDriver(java.sql.Driver)
+ * @see SimpleDriverDataSource
+ */
+ public void setDriverClassName(String driverClassName) {
+ }
+
+
+ @Override
+ protected Connection getConnectionFromDriver(Properties props) throws SQLException {
+ String url = getUrl();
+ return getConnectionFromDriverManager(url, props);
+ }
+
+ /**
+ * Getting a Connection using the nasty static from DriverManager is extracted
+ * into a protected method to allow for easy unit testing.
+ * @see java.sql.DriverManager#getConnection(String, java.util.Properties)
+ */
+ protected Connection getConnectionFromDriverManager(String url, Properties props) throws SQLException {
+ return DriverManager.getConnection(url, props);
+ }
+
+}
diff --git a/java/ql/test/stubs/springframework-5.3.8/org/springframework/boot/jdbc/DataSourceBuilder.java b/java/ql/test/stubs/springframework-5.3.8/org/springframework/boot/jdbc/DataSourceBuilder.java
new file mode 100644
index 00000000000..d85bb46dfd7
--- /dev/null
+++ b/java/ql/test/stubs/springframework-5.3.8/org/springframework/boot/jdbc/DataSourceBuilder.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012-2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.jdbc;
+
+import javax.sql.DataSource;
+
+public final class DataSourceBuilder