This is an automated email from the ASF dual-hosted git repository.

jackietien pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 96e8681dd51 Support the JDBC client to prevent SQL injection (#16734)
96e8681dd51 is described below

commit 96e8681dd51afdce5027a60d01f5ed555bab8ac7
Author: Le Yang <[email protected]>
AuthorDate: Wed Nov 12 20:16:33 2025 +0800

    Support the JDBC client to prevent SQL injection (#16734)
---
 .../apache/iotdb/jdbc/IoTDBPreparedStatement.java  |  17 ++-
 .../iotdb/jdbc/IoTDBPreparedStatementTest.java     | 154 ++++++++++++++++++++-
 2 files changed, 156 insertions(+), 15 deletions(-)

diff --git 
a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBPreparedStatement.java
 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBPreparedStatement.java
index bb6a7641ab5..c92b6549bf9 100644
--- 
a/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBPreparedStatement.java
+++ 
b/iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/IoTDBPreparedStatement.java
@@ -909,19 +909,18 @@ public class IoTDBPreparedStatement extends 
IoTDBStatement implements PreparedSt
 
   @Override
   public void setString(int parameterIndex, String x) {
-    // if the sql is an insert statement and the value is not a string 
literal, add single quotes
-    // The table model only supports single quotes, the tree model sql both 
single and double quotes
-    if ("table".equalsIgnoreCase(getSqlDialect())
-        || ((sql.trim().toUpperCase().startsWith("INSERT")
-            && !((x.startsWith("'") && x.endsWith("'"))
-                || ((x.startsWith("\"") && x.endsWith("\""))
-                    && "tree".equals(getSqlDialect())))))) {
-      this.parameters.put(parameterIndex, "'" + x + "'");
+    if (x == null) {
+      this.parameters.put(parameterIndex, null);
     } else {
-      this.parameters.put(parameterIndex, x);
+      this.parameters.put(parameterIndex, "'" + escapeSingleQuotes(x) + "'");
     }
   }
 
+  private String escapeSingleQuotes(String value) {
+    // Escape single quotes with double single quotes
+    return value.replace("'", "''");
+  }
+
   @Override
   public void setTime(int parameterIndex, Time x) throws SQLException {
     try {
diff --git 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBPreparedStatementTest.java
 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBPreparedStatementTest.java
index 1a523d9459f..2ae65dfed2a 100644
--- 
a/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBPreparedStatementTest.java
+++ 
b/iotdb-client/jdbc/src/test/java/org/apache/iotdb/jdbc/IoTDBPreparedStatementTest.java
@@ -200,7 +200,7 @@ public class IoTDBPreparedStatementTest {
         ArgumentCaptor.forClass(TSExecuteStatementReq.class);
     verify(client).executeStatementV2(argument.capture());
     assertEquals(
-        "SELECT status, temperature FROM root.ln.wf01.wt01 WHERE temperature < 
'abcde' and time > 2017-11-1 0:13:00",
+        "SELECT status, temperature FROM root.ln.wf01.wt01 WHERE temperature < 
'''abcde''' and time > 2017-11-1 0:13:00",
         argument.getValue().getStatement());
   }
 
@@ -217,7 +217,7 @@ public class IoTDBPreparedStatementTest {
         ArgumentCaptor.forClass(TSExecuteStatementReq.class);
     verify(client).executeStatementV2(argument.capture());
     assertEquals(
-        "SELECT status, temperature FROM root.ln.wf01.wt01 WHERE temperature < 
\"abcde\" and time > 2017-11-1 0:13:00",
+        "SELECT status, temperature FROM root.ln.wf01.wt01 WHERE temperature < 
'\"abcde\"' and time > 2017-11-1 0:13:00",
         argument.getValue().getStatement());
   }
 
@@ -233,7 +233,7 @@ public class IoTDBPreparedStatementTest {
         ArgumentCaptor.forClass(TSExecuteStatementReq.class);
     verify(client).executeStatementV2(argument.capture());
     assertEquals(
-        "SELECT status, temperature FROM root.ln.wf01.wt01", 
argument.getValue().getStatement());
+        "SELECT status, 'temperature' FROM root.ln.wf01.wt01", 
argument.getValue().getStatement());
   }
 
   @SuppressWarnings("resource")
@@ -325,7 +325,7 @@ public class IoTDBPreparedStatementTest {
         ArgumentCaptor.forClass(TSExecuteStatementReq.class);
     verify(client).executeStatementV2(argument.capture());
     assertEquals(
-        "INSERT INTO root.ln.wf01.wt01(time,a,b,c,d,e,f) 
VALUES(12324,false,123,123234345,123.423,-1323.0,'abc')",
+        "INSERT INTO root.ln.wf01.wt01(time,a,b,c,d,e,f) 
VALUES(12324,false,123,123234345,123.423,-1323.0,'''abc''')",
         argument.getValue().getStatement());
   }
 
@@ -351,7 +351,7 @@ public class IoTDBPreparedStatementTest {
         ArgumentCaptor.forClass(TSExecuteStatementReq.class);
     verify(client).executeStatementV2(argument.capture());
     assertEquals(
-        "INSERT INTO root.ln.wf01.wt01(time,a,b,c,d,e,f,g,h) 
VALUES(2017-11-01T00:13:00,false,123,123234345,123.423,-1323.0,\"abc\",'abc','abc')",
+        "INSERT INTO root.ln.wf01.wt01(time,a,b,c,d,e,f,g,h) 
VALUES(2017-11-01T00:13:00,false,123,123234345,123.423,-1323.0,'\"abc\"','abc','''abc''')",
         argument.getValue().getStatement());
   }
 
@@ -374,7 +374,7 @@ public class IoTDBPreparedStatementTest {
         ArgumentCaptor.forClass(TSExecuteStatementReq.class);
     verify(client).executeStatementV2(argument.capture());
     assertEquals(
-        "INSERT INTO root.ln.wf01.wt02(time,a,b,c,d,e,f) 
VALUES(2020-01-01T10:10:10,false,123,123234345,123.423,-1323.0,\"abc\")",
+        "INSERT INTO root.ln.wf01.wt02(time,a,b,c,d,e,f) 
VALUES(2020-01-01T10:10:10,false,123,123234345,123.423,-1323.0,'\"abc\"')",
         argument.getValue().getStatement());
   }
 
@@ -400,4 +400,146 @@ public class IoTDBPreparedStatementTest {
         "INSERT INTO root.ln.wf01.wt02(time,a,b,c,d,e,f) 
VALUES(2020-01-01T10:10:10,false,123,123234345,123.423,-1323.0,'abc')",
         argument.getValue().getStatement());
   }
+
+  // ========== Table Model SQL Injection Prevention Tests ==========
+
+  @SuppressWarnings("resource")
+  @Test
+  public void testTableModelLoginInjectionWithComment() throws Exception {
+    // Login interface SQL injection attack 1: Using -- comments to bypass 
password checks
+    when(connection.getSqlDialect()).thenReturn("table");
+    String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
+    IoTDBPreparedStatement ps =
+        new IoTDBPreparedStatement(connection, client, sessionId, sql, zoneId);
+    ps.setString(1, "admin' --");
+    ps.setString(2, "password");
+    ps.execute();
+
+    ArgumentCaptor<TSExecuteStatementReq> argument =
+        ArgumentCaptor.forClass(TSExecuteStatementReq.class);
+    verify(client).executeStatementV2(argument.capture());
+    assertEquals(
+        "SELECT * FROM users WHERE username = 'admin'' --' AND password = 
'password'",
+        argument.getValue().getStatement());
+  }
+
+  @SuppressWarnings("resource")
+  @Test
+  public void testTableModelLoginInjectionWithORCondition() throws Exception {
+    // Login interface SQL injection attack 2: Bypassing authentication by 
using 'OR '1'='1
+    when(connection.getSqlDialect()).thenReturn("table");
+    String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
+    IoTDBPreparedStatement ps =
+        new IoTDBPreparedStatement(connection, client, sessionId, sql, zoneId);
+    ps.setString(1, "admin");
+    ps.setString(2, "' OR '1'='1");
+    ps.execute();
+
+    ArgumentCaptor<TSExecuteStatementReq> argument =
+        ArgumentCaptor.forClass(TSExecuteStatementReq.class);
+    verify(client).executeStatementV2(argument.capture());
+    assertEquals(
+        "SELECT * FROM users WHERE username = 'admin' AND password = ''' OR 
''1''=''1'",
+        argument.getValue().getStatement());
+  }
+
+  @SuppressWarnings("resource")
+  @Test
+  public void testTableModelQueryWithMultipleInjectionVectors() throws 
Exception {
+    when(connection.getSqlDialect()).thenReturn("table");
+    String sql = "SELECT * FROM users WHERE email = ?";
+    IoTDBPreparedStatement ps =
+        new IoTDBPreparedStatement(connection, client, sessionId, sql, zoneId);
+    ps.setString(1, "'; DROP TABLE users;");
+    ps.execute();
+
+    ArgumentCaptor<TSExecuteStatementReq> argument =
+        ArgumentCaptor.forClass(TSExecuteStatementReq.class);
+    verify(client).executeStatementV2(argument.capture());
+    assertEquals(
+        "SELECT * FROM users WHERE email = '''; DROP TABLE users;'",
+        argument.getValue().getStatement());
+  }
+
+  @SuppressWarnings("resource")
+  @Test
+  public void testTableModelString1() throws Exception {
+    when(connection.getSqlDialect()).thenReturn("table");
+    String sql = "SELECT * FROM users WHERE password = ?";
+    IoTDBPreparedStatement ps =
+        new IoTDBPreparedStatement(connection, client, sessionId, sql, zoneId);
+    ps.setString(1, "a'b");
+    ps.execute();
+
+    ArgumentCaptor<TSExecuteStatementReq> argument =
+        ArgumentCaptor.forClass(TSExecuteStatementReq.class);
+    verify(client).executeStatementV2(argument.capture());
+    assertEquals("SELECT * FROM users WHERE password = 'a''b'", 
argument.getValue().getStatement());
+  }
+
+  @SuppressWarnings("resource")
+  @Test
+  public void testTableModelString2() throws Exception {
+    when(connection.getSqlDialect()).thenReturn("table");
+    String sql = "SELECT * FROM users WHERE password = ?";
+    IoTDBPreparedStatement ps =
+        new IoTDBPreparedStatement(connection, client, sessionId, sql, zoneId);
+    ps.setString(1, "a\'b");
+    ps.execute();
+
+    ArgumentCaptor<TSExecuteStatementReq> argument =
+        ArgumentCaptor.forClass(TSExecuteStatementReq.class);
+    verify(client).executeStatementV2(argument.capture());
+    assertEquals("SELECT * FROM users WHERE password = 'a''b'", 
argument.getValue().getStatement());
+  }
+
+  @SuppressWarnings("resource")
+  @Test
+  public void testTableModelString3() throws Exception {
+    when(connection.getSqlDialect()).thenReturn("table");
+    String sql = "SELECT * FROM users WHERE password = ?";
+    IoTDBPreparedStatement ps =
+        new IoTDBPreparedStatement(connection, client, sessionId, sql, zoneId);
+    ps.setString(1, "a\\'b");
+    ps.execute();
+
+    ArgumentCaptor<TSExecuteStatementReq> argument =
+        ArgumentCaptor.forClass(TSExecuteStatementReq.class);
+    verify(client).executeStatementV2(argument.capture());
+    assertEquals(
+        "SELECT * FROM users WHERE password = 'a\\''b'", 
argument.getValue().getStatement());
+  }
+
+  @SuppressWarnings("resource")
+  @Test
+  public void testTableModelString4() throws Exception {
+    when(connection.getSqlDialect()).thenReturn("table");
+    String sql = "SELECT * FROM users WHERE password = ?";
+    IoTDBPreparedStatement ps =
+        new IoTDBPreparedStatement(connection, client, sessionId, sql, zoneId);
+    ps.setString(1, "a\\\'b");
+    ps.execute();
+
+    ArgumentCaptor<TSExecuteStatementReq> argument =
+        ArgumentCaptor.forClass(TSExecuteStatementReq.class);
+    verify(client).executeStatementV2(argument.capture());
+    assertEquals(
+        "SELECT * FROM users WHERE password = 'a\\''b'", 
argument.getValue().getStatement());
+  }
+
+  @SuppressWarnings("resource")
+  @Test
+  public void testTableModelStringWithNull() throws Exception {
+    when(connection.getSqlDialect()).thenReturn("table");
+    String sql = "SELECT * FROM users WHERE email = ?";
+    IoTDBPreparedStatement ps =
+        new IoTDBPreparedStatement(connection, client, sessionId, sql, zoneId);
+    ps.setString(1, null);
+    ps.execute();
+
+    ArgumentCaptor<TSExecuteStatementReq> argument =
+        ArgumentCaptor.forClass(TSExecuteStatementReq.class);
+    verify(client).executeStatementV2(argument.capture());
+    assertEquals("SELECT * FROM users WHERE email = null", 
argument.getValue().getStatement());
+  }
 }

Reply via email to