Affected Versions
- Product: SpringBlade (https://github.com/chillzhuang/SpringBlade)
- Affected Component:
blade-report module (UReport2 integration)
- Affected Versions: ≤ 4.8.0 (latest version as of disclosure)
- Affected Endpoint:
POST /ureport/datasource/testConnection
Impact
The /ureport/datasource/testConnection endpoint in the blade-report service accepts fully user-controlled JDBC connection parameters (driver, url, username, password). The server directly calls Class.forName(driver) and DriverManager.getConnection(url, username, password) without any validation or restriction on the supplied values.
This leads to three escalating attack scenarios:
- Server-Side Request Forgery (SSRF): An attacker can force the server to initiate TCP connections to arbitrary internal or external hosts and ports via JDBC URLs, enabling internal network reconnaissance and service discovery.
- Arbitrary File Read (mysql-connector-j ≥ 8.2.0, the project default): By pointing the JDBC URL to an attacker-controlled fake MySQL server with
allowLoadLocalInfile=true, the attacker can exploit the MySQL protocol's LOAD DATA LOCAL INFILE mechanism to exfiltrate arbitrary files from the blade-report server (e.g., /etc/passwd, application configuration files, private keys).
- Remote Code Execution (mysql-connector-j < 8.2.0): On older connector versions where
autoDeserialize is still available, an attacker can trigger Java deserialization of malicious objects returned by a fake MySQL server, leading to arbitrary code execution on the blade-report server.
Steps to Reproduce
- Send the following request to the blade-report service (default port 8108).
- The server attempts a real TCP connection to
127.0.0.1:13306. The response reveals whether the port is open.
- If the specified address is not an internal network address but an attacker-controlled IP, and the JDBC URL parameter
allowLoadLocalInfile=true is set, arbitrary file read on the server can be achieved. Use this tool to test.
https://github.com/4ra1n/mysql-fake-server
Root Cause Analysis
The vulnerability originates in UReport2's DatasourceServletAction class, which handles datasource-related operations for the report designer. The testConnection method directly uses user-supplied parameters to establish a JDBC connection without any validation:
// Pseudocode reconstructed from UReport2 DatasourceServletAction
public void testConnection(HttpServletRequest req, HttpServletResponse resp) {
String driver = req.getParameter("driver");
String url = req.getParameter("url");
String username = req.getParameter("username");
String password = req.getParameter("password");
Connection conn = null;
Map<String, Object> result = new HashMap<>();
try {
// VULNERABILITY 1: Arbitrary class loading
// Attacker controls the class name passed to Class.forName()
Class.forName(driver);
// VULNERABILITY 2: SSRF + Arbitrary JDBC connection
// Attacker controls the full JDBC URL, username, and password
// No whitelist, no URL parsing, no parameter filtering
conn = DriverManager.getConnection(url, username, password);
result.put("result", true);
} catch (Exception e) {
// VULNERABILITY 3: Verbose error disclosure
// Full exception message returned to attacker aids exploitation
result.put("result", false);
result.put("error", e.toString());
} finally {
if (conn != null) conn.close();
}
// Write result as JSON response
writeJson(resp, result);
}
Three flaws compound to create the vulnerability:
- No input validation on
driver: Class.forName(driver) executes with any class name. This loads the class and runs its static initializer. It also enables the attacker to probe which classes exist on the classpath.
- No input validation on
url: DriverManager.getConnection(url, ...) accepts any JDBC URL. The attacker controls the destination host/port (enabling SSRF) and all connection parameters (enabling allowLoadLocalInfile, autoDeserialize, etc.).
- No network-level restriction: The server makes outbound connections to any address specified in the URL, including internal RFC1918 addresses, localhost, and external attacker-controlled servers.
Additionally, SpringBlade's UReportAuthFilter only requires a valid session token (report-viewer permission level), providing insufficient access control for such a dangerous operation.
Remediation
Immediate Mitigation
- Disable the endpoint: If the datasource test functionality is not required in production, block access to
/ureport/datasource/testConnection at the gateway or web filter level.
Code-Level Fixes
-
Whitelist allowed JDBC drivers: Replace Class.forName(driver) with a check against a predefined set of allowed driver class names:
Set<String> ALLOWED_DRIVERS = Set.of("com.mysql.cj.jdbc.Driver", "org.postgresql.Driver");
if (!ALLOWED_DRIVERS.contains(driver)) {
throw new SecurityException("Driver not allowed: " + driver);
}
-
Sanitize JDBC URL parameters: Parse the JDBC URL and strip dangerous parameters before connecting:
List<String> BLOCKED_PARAMS = List.of(
"autoDeserialize", "allowLoadLocalInfile", "allowUrlInLocalInfile",
"queryInterceptors", "statementInterceptors", "detectCustomCollations"
);
-
Restrict connection targets: Validate that the host in the JDBC URL is not a private/loopback address (prevent SSRF to internal network):
InetAddress addr = InetAddress.getByName(extractedHost);
if (addr.isLoopbackAddress() || addr.isSiteLocalAddress() || addr.isLinkLocalAddress()) {
throw new SecurityException("Connection to internal addresses is not allowed");
}
-
Disable LOAD DATA LOCAL at the driver level by setting allowLoadLocalInfile=false as a system-wide default, regardless of user-supplied URL parameters.
Affected Versions
blade-reportmodule (UReport2 integration)POST /ureport/datasource/testConnectionImpact
The
/ureport/datasource/testConnectionendpoint in the blade-report service accepts fully user-controlled JDBC connection parameters (driver,url,username,password). The server directly callsClass.forName(driver)andDriverManager.getConnection(url, username, password)without any validation or restriction on the supplied values.This leads to three escalating attack scenarios:
allowLoadLocalInfile=true, the attacker can exploit the MySQL protocol'sLOAD DATA LOCAL INFILEmechanism to exfiltrate arbitrary files from the blade-report server (e.g.,/etc/passwd, application configuration files, private keys).autoDeserializeis still available, an attacker can trigger Java deserialization of malicious objects returned by a fake MySQL server, leading to arbitrary code execution on the blade-report server.Steps to Reproduce
127.0.0.1:13306. The response reveals whether the port is open.allowLoadLocalInfile=trueis set, arbitrary file read on the server can be achieved. Use this tool to test.https://github.com/4ra1n/mysql-fake-server
Root Cause Analysis
The vulnerability originates in UReport2's
DatasourceServletActionclass, which handles datasource-related operations for the report designer. ThetestConnectionmethod directly uses user-supplied parameters to establish a JDBC connection without any validation:Three flaws compound to create the vulnerability:
driver:Class.forName(driver)executes with any class name. This loads the class and runs its static initializer. It also enables the attacker to probe which classes exist on the classpath.url:DriverManager.getConnection(url, ...)accepts any JDBC URL. The attacker controls the destination host/port (enabling SSRF) and all connection parameters (enablingallowLoadLocalInfile,autoDeserialize, etc.).Additionally, SpringBlade's
UReportAuthFilteronly requires a valid session token (report-viewer permission level), providing insufficient access control for such a dangerous operation.Remediation
Immediate Mitigation
/ureport/datasource/testConnectionat the gateway or web filter level.Code-Level Fixes
Whitelist allowed JDBC drivers: Replace
Class.forName(driver)with a check against a predefined set of allowed driver class names:Sanitize JDBC URL parameters: Parse the JDBC URL and strip dangerous parameters before connecting:
Restrict connection targets: Validate that the host in the JDBC URL is not a private/loopback address (prevent SSRF to internal network):
Disable
LOAD DATA LOCALat the driver level by settingallowLoadLocalInfile=falseas a system-wide default, regardless of user-supplied URL parameters.