diff --git a/src/main/java/com/alibaba/druid/wall/WallProvider.java b/src/main/java/com/alibaba/druid/wall/WallProvider.java index dd140d9f2d..5dfba33679 100644 --- a/src/main/java/com/alibaba/druid/wall/WallProvider.java +++ b/src/main/java/com/alibaba/druid/wall/WallProvider.java @@ -19,6 +19,8 @@ import java.security.PrivilegedAction; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReentrantReadWriteLock; import com.alibaba.druid.sql.ast.SQLStatement; @@ -27,22 +29,26 @@ import com.alibaba.druid.sql.parser.SQLStatementParser; import com.alibaba.druid.sql.parser.Token; import com.alibaba.druid.sql.visitor.ExportParameterVisitor; import com.alibaba.druid.util.LRUCache; +import com.alibaba.druid.wall.spi.WallVisitorUtils; import com.alibaba.druid.wall.violation.IllegalSQLObjectViolation; import com.alibaba.druid.wall.violation.SyntaxErrorViolation; public abstract class WallProvider { - private LinkedHashMap whiteList; + private LinkedHashMap whiteList; - private int whileListMaxSize = 1024; + private int whileListMaxSize = 1024; - private int whiteSqlMaxLength = 1024; // 1k + private int whiteSqlMaxLength = 1024; // 1k - protected final WallConfig config; + protected final WallConfig config; - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - private static final ThreadLocal privileged = new ThreadLocal(); + private static final ThreadLocal privileged = new ThreadLocal(); + + private final ConcurrentMap deniedTables = new ConcurrentHashMap(); + private final ConcurrentMap deniedFunctions = new ConcurrentHashMap(); public WallProvider(WallConfig config){ this.config = config; @@ -78,7 +84,7 @@ public abstract class WallProvider { return hashSet; } - + public void clearCache() { clearWhiteList(); } @@ -93,15 +99,15 @@ public abstract class WallProvider { lock.writeLock().unlock(); } } - + public WallSqlStat getWhiteSql(String sql) { lock.readLock().lock(); try { if (whiteList == null) { return null; } - - return whiteList.get(sql); + + return whiteList.get(sql); } finally { lock.readLock().unlock(); } @@ -122,6 +128,72 @@ public abstract class WallProvider { return result.getViolations().isEmpty(); } + public boolean checkDenyFunction(String functionName) { + if (functionName == null) { + return true; + } + + functionName = functionName.toLowerCase(); + if (getConfig().getDenyFunctions().contains(functionName)) { + WallDenyStat denyStat = this.deniedFunctions.get(functionName); + if (denyStat == null) { + this.deniedFunctions.putIfAbsent(functionName, new WallDenyStat()); + denyStat = this.deniedFunctions.get(functionName); + } + denyStat.incrementAndGetDenyCount(); + return false; + } + + return true; + } + + public boolean checkDenyTable(String tableName) { + if (tableName == null) { + return true; + } + + tableName = WallVisitorUtils.form(tableName); + if (getConfig().getDenyTables().contains(tableName)) { + WallDenyStat denyStat = this.deniedTables.get(tableName); + if (denyStat == null) { + this.deniedTables.putIfAbsent(tableName, new WallDenyStat()); + denyStat = this.deniedTables.get(tableName); + } + denyStat.incrementAndGetDenyCount(); + return false; + } + + return true; + } + + public boolean checkReadOnlyTable(String tableName) { + if (tableName == null) { + return true; + } + + tableName = WallVisitorUtils.form(tableName); + if (getConfig().isReadOnly(tableName)) { + WallDenyStat denyStat = this.deniedTables.get(tableName); + if (denyStat == null) { + this.deniedTables.putIfAbsent(tableName, new WallDenyStat()); + denyStat = this.deniedTables.get(tableName); + } + denyStat.incrementAndGetDenyCount(); + return false; + } + + return true; + } + + public WallDenyStat getDenniedTableStat(String tableName) { + if (tableName == null) { + return null; + } + + String formName = WallVisitorUtils.form(tableName); + return deniedTables.get(formName); + } + public WallCheckResult check(String sql) { WallCheckResult result = new WallCheckResult(); diff --git a/src/main/java/com/alibaba/druid/wall/spi/MySqlWallVisitor.java b/src/main/java/com/alibaba/druid/wall/spi/MySqlWallVisitor.java index e569976fbd..201fe6957c 100644 --- a/src/main/java/com/alibaba/druid/wall/spi/MySqlWallVisitor.java +++ b/src/main/java/com/alibaba/druid/wall/spi/MySqlWallVisitor.java @@ -242,8 +242,7 @@ public class MySqlWallVisitor extends MySqlASTVisitorAdapter implements WallVisi return false; } - name = WallVisitorUtils.form(name); - return config.getDenyTables().contains(name); + return !this.provider.checkDenyTable(name); } public void preVisit(SQLObject x) { diff --git a/src/main/java/com/alibaba/druid/wall/spi/OracleWallVisitor.java b/src/main/java/com/alibaba/druid/wall/spi/OracleWallVisitor.java index 0952403628..e1137b6079 100644 --- a/src/main/java/com/alibaba/druid/wall/spi/OracleWallVisitor.java +++ b/src/main/java/com/alibaba/druid/wall/spi/OracleWallVisitor.java @@ -167,7 +167,7 @@ public class OracleWallVisitor extends OracleASTVisitorAdapter implements WallVi if (name.startsWith("v$") || name.startsWith("v_$")) { return true; } - return config.getDenyTables().contains(name); + return !this.provider.checkDenyTable(name); } public void preVisit(SQLObject x) { diff --git a/src/main/java/com/alibaba/druid/wall/spi/SQLServerWallVisitor.java b/src/main/java/com/alibaba/druid/wall/spi/SQLServerWallVisitor.java index 5ebfd98270..87d32ee7b3 100644 --- a/src/main/java/com/alibaba/druid/wall/spi/SQLServerWallVisitor.java +++ b/src/main/java/com/alibaba/druid/wall/spi/SQLServerWallVisitor.java @@ -85,8 +85,7 @@ public class SQLServerWallVisitor extends SQLServerASTVisitorAdapter implements return false; } - name = WallVisitorUtils.form(name); - return config.getDenyTables().contains(name); + return !this.provider.checkDenyTable(name); } @Override diff --git a/src/main/java/com/alibaba/druid/wall/spi/WallVisitorUtils.java b/src/main/java/com/alibaba/druid/wall/spi/WallVisitorUtils.java index ce28b5d4f6..9ce4d37728 100644 --- a/src/main/java/com/alibaba/druid/wall/spi/WallVisitorUtils.java +++ b/src/main/java/com/alibaba/druid/wall/spi/WallVisitorUtils.java @@ -69,7 +69,7 @@ import com.alibaba.druid.wall.violation.IllegalSQLObjectViolation; public class WallVisitorUtils { - private final static Log LOG = LogFactory.getLog(WallVisitorUtils.class); + private final static Log LOG = LogFactory.getLog(WallVisitorUtils.class); public static void check(WallVisitor visitor, SQLInListExpr x) { @@ -88,7 +88,7 @@ public class WallVisitorUtils { && x.getParent() instanceof SQLSelectQueryBlock) { SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) x.getParent(); SQLTableSource from = queryBlock.getFrom(); - + if (from instanceof SQLExprTableSource) { addViolation(visitor, x); } @@ -348,11 +348,9 @@ public class WallVisitorUtils { tableName = ((SQLName) tableNameExpr).getSimleName(); } - if (tableName != null) { - tableName = form(tableName); - if (visitor.getConfig().isReadOnly(tableName)) { - addViolation(visitor, tableSource); - } + boolean readOnlyValid = visitor.getProvider().checkReadOnlyTable(tableName); + if (!readOnlyValid) { + addViolation(visitor, tableSource); } } else if (tableSource instanceof SQLJoinTableSource) { SQLJoinTableSource join = (SQLJoinTableSource) tableSource; @@ -737,7 +735,7 @@ public class WallVisitorUtils { String methodName = x.getMethodName(); - if (visitor.getConfig().isDenyFunction(methodName.toLowerCase())) { + if (!visitor.getProvider().checkDenyFunction(methodName)) { addViolation(visitor, x); } diff --git a/src/test/java/com/alibaba/druid/bvt/filter/wall/WallDenyTableTest.java b/src/test/java/com/alibaba/druid/bvt/filter/wall/WallDenyTableTest.java new file mode 100644 index 0000000000..a3f253989d --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/filter/wall/WallDenyTableTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2011 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.druid.bvt.filter.wall; + +import junit.framework.TestCase; + +import org.junit.Assert; + +import com.alibaba.druid.wall.spi.OracleWallProvider; + +/** + * @author wenshao + * + */ +public class WallDenyTableTest extends TestCase { + + public void testORACLE() throws Exception { + String sql = "SELECT F1, F2 FROM ALL_ALL_TABLES"; + + OracleWallProvider provider = new OracleWallProvider(); + Assert.assertFalse(provider.checkValid(sql)); + + Assert.assertEquals(1, provider.getDenniedTableStat("ALL_ALL_TABLES").getDenyCount()); + } +} diff --git a/src/test/java/com/alibaba/druid/bvt/filter/wall/WallReadOnlyTest2.java b/src/test/java/com/alibaba/druid/bvt/filter/wall/WallReadOnlyTest2.java new file mode 100644 index 0000000000..7c189e049f --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/filter/wall/WallReadOnlyTest2.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2011 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.druid.bvt.filter.wall; + +import junit.framework.TestCase; + +import org.junit.Assert; + +import com.alibaba.druid.wall.WallConfig; +import com.alibaba.druid.wall.spi.MySqlWallProvider; +import com.alibaba.druid.wall.spi.OracleWallProvider; + +/** + * 这个场景,检测可疑的Having条件 + * @author wenshao + * + */ +public class WallReadOnlyTest2 extends TestCase { + private WallConfig config = new WallConfig(); + + protected void setUp() throws Exception { + config.addReadOnlyTable("members"); + } + private String sql = "SELECT F1, F2 members"; + private String insert_sql = "INSERT INTO members (FID, FNAME) VALUES (?, ?)"; + private String update_sql = "UPDATE members SET FNAME = ? WHERe FID = ?"; + private String delete_sql = "DELETE members WHERE FID = ?"; + + public void testMySql() throws Exception { + MySqlWallProvider provider = new MySqlWallProvider(config); + Assert.assertTrue(provider.checkValid(sql)); + + Assert.assertNull(provider.getDenniedTableStat("members")); + + Assert.assertFalse(provider.checkValid(insert_sql)); + Assert.assertFalse(provider.checkValid(update_sql)); + Assert.assertFalse(provider.checkValid(delete_sql)); + + Assert.assertEquals(3, provider.getDenniedTableStat("members").getDenyCount()); + Assert.assertEquals(3, provider.getDenniedTableStat("Members").getDenyCount()); + } + + public void testORACLE() throws Exception { + OracleWallProvider provider = new OracleWallProvider(config); + Assert.assertTrue(provider.checkValid(sql)); + + Assert.assertNull(provider.getDenniedTableStat("members")); + + Assert.assertFalse(provider.checkValid(insert_sql)); + Assert.assertFalse(provider.checkValid(update_sql)); + Assert.assertFalse(provider.checkValid(delete_sql)); + + Assert.assertEquals(3, provider.getDenniedTableStat("members").getDenyCount()); + Assert.assertEquals(3, provider.getDenniedTableStat("Members").getDenyCount()); + } +}