diff --git a/src/main/java/com/alibaba/druid/filter/wall/IllegalStatementViolation.java b/src/main/java/com/alibaba/druid/filter/wall/IllegalSQLObjectViolation.java similarity index 64% rename from src/main/java/com/alibaba/druid/filter/wall/IllegalStatementViolation.java rename to src/main/java/com/alibaba/druid/filter/wall/IllegalSQLObjectViolation.java index 05bcabbb80..14ff196e4d 100644 --- a/src/main/java/com/alibaba/druid/filter/wall/IllegalStatementViolation.java +++ b/src/main/java/com/alibaba/druid/filter/wall/IllegalSQLObjectViolation.java @@ -1,14 +1,14 @@ package com.alibaba.druid.filter.wall; -public class IllegalStatementViolation implements Violation { +public class IllegalSQLObjectViolation implements Violation { private String condition; - public IllegalStatementViolation(){ + public IllegalSQLObjectViolation(){ } - public IllegalStatementViolation(String condition){ + public IllegalSQLObjectViolation(String condition){ this.condition = condition; } diff --git a/src/main/java/com/alibaba/druid/filter/wall/WallFilter.java b/src/main/java/com/alibaba/druid/filter/wall/WallFilter.java index 0711d01f55..3bcf43c65e 100644 --- a/src/main/java/com/alibaba/druid/filter/wall/WallFilter.java +++ b/src/main/java/com/alibaba/druid/filter/wall/WallFilter.java @@ -1,25 +1,30 @@ package com.alibaba.druid.filter.wall; import java.sql.SQLException; -import java.util.List; import java.util.Properties; import com.alibaba.druid.filter.FilterAdapter; import com.alibaba.druid.filter.FilterChain; +import com.alibaba.druid.filter.wall.spi.MySqlWallProvider; +import com.alibaba.druid.filter.wall.spi.MySqlWallVisitor; +import com.alibaba.druid.filter.wall.spi.OracleWallVisitor; import com.alibaba.druid.proxy.jdbc.CallableStatementProxy; import com.alibaba.druid.proxy.jdbc.ConnectionProxy; +import com.alibaba.druid.proxy.jdbc.DataSourceProxy; import com.alibaba.druid.proxy.jdbc.PreparedStatementProxy; -import com.alibaba.druid.sql.ast.SQLStatement; -import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; -import com.alibaba.druid.sql.dialect.oracle.parser.OracleStatementParser; -import com.alibaba.druid.sql.dialect.postgresql.parser.PGSQLStatementParser; -import com.alibaba.druid.sql.dialect.sqlserver.parser.SQLServerStatementParser; -import com.alibaba.druid.sql.parser.SQLStatementParser; -import com.alibaba.druid.sql.visitor.SQLASTVisitor; import com.alibaba.druid.util.JdbcUtils; public class WallFilter extends FilterAdapter { + private WallProvider provider; + + @Override + public void init(DataSourceProxy dataSource) { + this.dataSource = dataSource; + + provider = createWallProvider(dataSource.getDbType()); + } + @Override public ConnectionProxy connection_connect(FilterChain chain, Properties info) throws SQLException { return chain.connection_connect(info); @@ -94,44 +99,27 @@ public class WallFilter extends FilterAdapter { } public void check(ConnectionProxy connection, String sql) throws SQLException { - String dbType = connection.getDirectDataSource().getDbType(); + provider.check(sql, true); + } - SQLStatementParser parser = createParser(dbType, sql); - parser.getLexer().setAllowComment(false); // permit comment - - List statementList = parser.parseStatementList(); - if (statementList.size() > 1) { - throw new SQLException("multi-statement : " + sql); + public WallProvider createWallProvider(String dbType) { + if (JdbcUtils.MYSQL.equals(dbType)) { + return new MySqlWallProvider(); } - SQLStatement stmt = statementList.get(0); - - SQLASTVisitor visitor = createWallVisitor(dbType); - - stmt.accept(visitor); + throw new IllegalStateException("dbType not support : " + dbType); } - public SQLASTVisitor createWallVisitor(String dbType) throws SQLException { - throw new SQLException("dbType not support : " + dbType); - } - - public SQLStatementParser createParser(String dbType, String sql) throws SQLException { + public WallVisitor createWallVisitor(String dbType) throws SQLException { if (JdbcUtils.ORACLE.equals(dbType)) { - return new OracleStatementParser(sql); + return new OracleWallVisitor(); } if (JdbcUtils.MYSQL.equals(dbType)) { - return new MySqlStatementParser(sql); - } - - if (JdbcUtils.POSTGRESQL.equals(dbType)) { - return new PGSQLStatementParser(sql); - } - - if (JdbcUtils.SQL_SERVER.equals(dbType)) { - return new SQLServerStatementParser(sql); + return new MySqlWallVisitor(); } throw new SQLException("dbType not support : " + dbType); } + } diff --git a/src/main/java/com/alibaba/druid/filter/wall/WallProvider.java b/src/main/java/com/alibaba/druid/filter/wall/WallProvider.java new file mode 100644 index 0000000000..90030ec8c5 --- /dev/null +++ b/src/main/java/com/alibaba/druid/filter/wall/WallProvider.java @@ -0,0 +1,79 @@ +package com.alibaba.druid.filter.wall; + +import java.util.LinkedHashMap; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.parser.SQLStatementParser; + +public abstract class WallProvider { + + // Dummy value to associate with an Object in the backing Map + private static final Object PRESENT = new Object(); + + private LinkedHashMap whiteList; + + private int whileListMaxSize = 1024; + + private int whiteSqlMaxLength = 1024; // 1k + + public synchronized void addWhiteSql(String sql) { + if (whiteList == null) { + whiteList = new LinkedHashMap(whileListMaxSize, 0.75f, true); + } + + whiteList.put(sql, PRESENT); + } + + public synchronized boolean whiteContains(String sql) { + if (whiteList == null) { + return false; + } + + return whiteList.get(sql) != null; + } + + public abstract SQLStatementParser createParser(String sql); + + public abstract WallVisitor createWallVisitor(); + + public boolean check(String sql, boolean throwException) throws WallRuntimeException { + // first step, check whiteList + boolean isWhite = whiteContains(sql); + if (isWhite) { + return true; + } + + SQLStatementParser parser = createParser(sql); + parser.getLexer().setAllowComment(false); // permit comment + + List statementList = parser.parseStatementList(); + if (statementList.size() > 1) { + if (throwException) { + throw new WallRuntimeException("multi-statement : " + sql); + } + + return false; + } + + SQLStatement stmt = statementList.get(0); + + WallVisitor visitor = createWallVisitor(); + + stmt.accept(visitor); + + if (visitor.getViolations().size() > 0) { + if (throwException) { + throw new WallRuntimeException(); + } else { + return false; + } + } + + if (sql.length() < whiteSqlMaxLength) { + this.addWhiteSql(sql); + } + + return true; + } +} diff --git a/src/main/java/com/alibaba/druid/filter/wall/WallRuntimeException.java b/src/main/java/com/alibaba/druid/filter/wall/WallRuntimeException.java new file mode 100644 index 0000000000..9853920f84 --- /dev/null +++ b/src/main/java/com/alibaba/druid/filter/wall/WallRuntimeException.java @@ -0,0 +1,24 @@ +package com.alibaba.druid.filter.wall; + + +public class WallRuntimeException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public WallRuntimeException(){ + super(); + } + + public WallRuntimeException(String reason, Throwable cause){ + super(reason, cause); + } + + public WallRuntimeException(String reason){ + super(reason); + } + + public WallRuntimeException(Throwable cause){ + super(cause); + } + +} diff --git a/src/main/java/com/alibaba/druid/filter/wall/WallSQLException.java b/src/main/java/com/alibaba/druid/filter/wall/WallSQLException.java new file mode 100644 index 0000000000..e07357a9d4 --- /dev/null +++ b/src/main/java/com/alibaba/druid/filter/wall/WallSQLException.java @@ -0,0 +1,25 @@ +package com.alibaba.druid.filter.wall; + +import java.sql.SQLException; + +public class WallSQLException extends SQLException { + + private static final long serialVersionUID = 1L; + + public WallSQLException(){ + super(); + } + + public WallSQLException(String reason, Throwable cause){ + super(reason, cause); + } + + public WallSQLException(String reason){ + super(reason); + } + + public WallSQLException(Throwable cause){ + super(cause); + } + +} diff --git a/src/main/java/com/alibaba/druid/filter/wall/WallUtils.java b/src/main/java/com/alibaba/druid/filter/wall/WallUtils.java new file mode 100644 index 0000000000..af188ce277 --- /dev/null +++ b/src/main/java/com/alibaba/druid/filter/wall/WallUtils.java @@ -0,0 +1,11 @@ +package com.alibaba.druid.filter.wall; + +import com.alibaba.druid.filter.wall.spi.MySqlWallProvider; + +public class WallUtils { + + public static boolean isValidateMySql(String sql) { + MySqlWallProvider provider = new MySqlWallProvider(); + return provider.check(sql, false); + } +} diff --git a/src/main/java/com/alibaba/druid/filter/wall/spi/MySqlWallProvider.java b/src/main/java/com/alibaba/druid/filter/wall/spi/MySqlWallProvider.java new file mode 100644 index 0000000000..5b5b35cbe3 --- /dev/null +++ b/src/main/java/com/alibaba/druid/filter/wall/spi/MySqlWallProvider.java @@ -0,0 +1,21 @@ +package com.alibaba.druid.filter.wall.spi; + +import com.alibaba.druid.filter.wall.WallProvider; +import com.alibaba.druid.filter.wall.WallVisitor; +import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; +import com.alibaba.druid.sql.parser.SQLStatementParser; + + +public class MySqlWallProvider extends WallProvider { + + @Override + public SQLStatementParser createParser(String sql) { + return new MySqlStatementParser(sql); + } + + @Override + public WallVisitor createWallVisitor() { + return new MySqlWallVisitor(); + } + +} diff --git a/src/main/java/com/alibaba/druid/filter/wall/spi/MySqlWallVisitor.java b/src/main/java/com/alibaba/druid/filter/wall/spi/MySqlWallVisitor.java index 7ae353da6e..285cab69ff 100644 --- a/src/main/java/com/alibaba/druid/filter/wall/spi/MySqlWallVisitor.java +++ b/src/main/java/com/alibaba/druid/filter/wall/spi/MySqlWallVisitor.java @@ -1,14 +1,22 @@ package com.alibaba.druid.filter.wall.spi; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; -import com.alibaba.druid.filter.wall.IllegalStatementViolation; +import com.alibaba.druid.filter.wall.IllegalSQLObjectViolation; import com.alibaba.druid.filter.wall.Violation; import com.alibaba.druid.filter.wall.WallVisitor; import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; import com.alibaba.druid.sql.ast.statement.SQLDropTableStatement; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlExecuteStatement; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; @@ -19,14 +27,21 @@ import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter; public class MySqlWallVisitor extends MySqlASTVisitorAdapter implements WallVisitor { + private Set permitFunctions = new HashSet(); + private Set permitSchemas = new HashSet(); + private final List violations; - public MySqlWallVisitor() { + public MySqlWallVisitor(){ this(new ArrayList()); } - public MySqlWallVisitor(List violations) { + public MySqlWallVisitor(List violations){ this.violations = violations; + +permitFunctions.add("load_file"); + +permitSchemas.add("information_schema"); } public List getViolations() { @@ -34,50 +49,92 @@ public class MySqlWallVisitor extends MySqlASTVisitorAdapter implements WallVisi } public boolean visit(MySqlExecuteStatement x) { - violations.add(new IllegalStatementViolation(SQLUtils.toMySqlString(x))); + violations.add(new IllegalSQLObjectViolation(SQLUtils.toMySqlString(x))); return false; } - + @Override public boolean visit(MySqlShowTablesStatement x) { - violations.add(new IllegalStatementViolation(SQLUtils.toMySqlString(x))); + violations.add(new IllegalSQLObjectViolation(SQLUtils.toMySqlString(x))); return false; } - + @Override public boolean visit(MySqlShowDatabasesStatement x) { - violations.add(new IllegalStatementViolation(SQLUtils.toMySqlString(x))); + violations.add(new IllegalSQLObjectViolation(SQLUtils.toMySqlString(x))); return false; } - + @Override public boolean visit(MySqlShowColumnsStatement x) { - violations.add(new IllegalStatementViolation(SQLUtils.toMySqlString(x))); + violations.add(new IllegalSQLObjectViolation(SQLUtils.toMySqlString(x))); return false; } - + public boolean visit(SQLBinaryOpExpr x) { WallVisitorUtils.check(this, x); return true; } - + @Override public boolean visit(SQLSelectQueryBlock x) { if (x.getWhere() != null) { x.getWhere().setParent(x); } - + return true; } - + @Override public boolean visit(MySqlSelectQueryBlock x) { return visit((SQLSelectQueryBlock) x); } - + public boolean visit(SQLDropTableStatement x) { - violations.add(new IllegalStatementViolation(SQLUtils.toMySqlString(x))); + violations.add(new IllegalSQLObjectViolation(SQLUtils.toMySqlString(x))); return false; } + + public boolean visit(SQLVariantRefExpr x) { + String varName = x.getName(); + if (varName == null) { + return false; + } + + if (varName.startsWith("@@")) { + violations.add(new IllegalSQLObjectViolation(SQLUtils.toMySqlString(x))); + } + + return false; + } + + @Override + public boolean visit(SQLMethodInvokeExpr x) { + String methodName = x.getMethodName(); + + if (permitFunctions.contains(methodName.toLowerCase())) { + violations.add(new IllegalSQLObjectViolation(SQLUtils.toMySqlString(x))); + } + + return true; + } + + public boolean visit(SQLExprTableSource x) { + SQLExpr expr = x.getExpr(); + + if (expr instanceof SQLPropertyExpr) { + SQLPropertyExpr propExpr = (SQLPropertyExpr) expr; + + if (propExpr.getOwner() instanceof SQLIdentifierExpr) { + String ownerName = ((SQLIdentifierExpr) propExpr.getOwner()).getName(); + + if (permitSchemas.contains(ownerName.toLowerCase())) { + violations.add(new IllegalSQLObjectViolation(SQLUtils.toMySqlString(x))); + } + } + } + + return true; + } } diff --git a/src/main/java/com/alibaba/druid/filter/wall/spi/OracleWallVisitor.java b/src/main/java/com/alibaba/druid/filter/wall/spi/OracleWallVisitor.java index 6dc951b036..4bfb9e98f0 100644 --- a/src/main/java/com/alibaba/druid/filter/wall/spi/OracleWallVisitor.java +++ b/src/main/java/com/alibaba/druid/filter/wall/spi/OracleWallVisitor.java @@ -3,7 +3,7 @@ package com.alibaba.druid.filter.wall.spi; import java.util.ArrayList; import java.util.List; -import com.alibaba.druid.filter.wall.IllegalStatementViolation; +import com.alibaba.druid.filter.wall.IllegalSQLObjectViolation; import com.alibaba.druid.filter.wall.Violation; import com.alibaba.druid.filter.wall.WallVisitor; import com.alibaba.druid.sql.SQLUtils; @@ -34,7 +34,7 @@ public class OracleWallVisitor extends OracleASTVIsitorAdapter implements WallVi } public boolean visit(SQLDropTableStatement x) { - violations.add(new IllegalStatementViolation(SQLUtils.toOracleString(x))); + violations.add(new IllegalSQLObjectViolation(SQLUtils.toOracleString(x))); return false; } } diff --git a/src/test/java/com/alibaba/druid/bvt/filter/wall/MySqlWallTest.java b/src/test/java/com/alibaba/druid/bvt/filter/wall/MySqlWallTest.java new file mode 100644 index 0000000000..14e476f05c --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/filter/wall/MySqlWallTest.java @@ -0,0 +1,21 @@ +package com.alibaba.druid.bvt.filter.wall; + +import junit.framework.TestCase; + +import org.junit.Assert; + +import com.alibaba.druid.filter.wall.WallUtils; + +public class MySqlWallTest extends TestCase { + + public void testWall() throws Exception { + Assert.assertFalse(WallUtils.isValidateMySql("select 14,13,12,11,10,@@version_compile_os,8,7,6,5,4,3,2,1")); + Assert.assertTrue(WallUtils.isValidateMySql("select '@@version_compile_os'")); + + Assert.assertFalse(WallUtils.isValidateMySql("select hex(load_file(0x633A2F77696E646F77732F7265706169722F73616D))")); + Assert.assertTrue(WallUtils.isValidateMySql("select 'hex(load_file(0x633A2F77696E646F77732F7265706169722F73616D))'")); + + Assert.assertFalse(WallUtils.isValidateMySql("select 1 from information_schema.columns")); + Assert.assertTrue(WallUtils.isValidateMySql("select 'information_schema.columns'")); + } +} diff --git a/src/test/java/com/alibaba/druid/bvt/filter/wall/WallVisitorUtilsTest.java b/src/test/java/com/alibaba/druid/bvt/filter/wall/WallVisitorUtilsTest.java index f4fa19cf36..49cc7a0ce2 100644 --- a/src/test/java/com/alibaba/druid/bvt/filter/wall/WallVisitorUtilsTest.java +++ b/src/test/java/com/alibaba/druid/bvt/filter/wall/WallVisitorUtilsTest.java @@ -34,5 +34,7 @@ public class WallVisitorUtilsTest extends TestCase { Assert.assertEquals(Boolean.TRUE, WallVisitorUtils.getValue(SQLUtils.toSQLExpr("(select count(*) from t) >= 0"))); Assert.assertEquals(Boolean.FALSE, WallVisitorUtils.getValue(SQLUtils.toSQLExpr("(select count(*) from t) < 0"))); Assert.assertEquals(Boolean.TRUE, WallVisitorUtils.getValue(SQLUtils.toSQLExpr("NOT (select count(*) from t) < 0"))); + + // } }