improve wall filter

This commit is contained in:
wenshao
2013-04-11 15:38:13 +08:00
parent 7d80ffd7a7
commit eddb42ae53
28 changed files with 946 additions and 69 deletions
@@ -890,6 +890,10 @@ public class DruidDataSource extends DruidAbstractDataSource implements DruidDat
return;
}
if (pooledConnection.getOwnerThread() != Thread.currentThread()) {
throw new SQLException("closed error, get/close not same thread");
}
final Connection physicalConnection = holder.getConnection();
if (pooledConnection.isTraceEnable()) {
@@ -60,6 +60,9 @@ public class SQLInSubQueryExpr extends SQLExprImpl implements Serializable {
}
public void setSubQuery(SQLSelect subQuery) {
if (subQuery != null) {
subQuery.setParent(this);
}
this.subQuery = subQuery;
}
@@ -28,10 +28,7 @@ public class SQLPropertyExpr extends SQLExprImpl implements SQLName {
private String name;
public SQLPropertyExpr(SQLExpr owner, String name){
if (owner != null) {
owner.setParent(this);
}
this.owner = owner;
setOwner(owner);
this.name = name;
}
@@ -53,7 +50,7 @@ public class SQLPropertyExpr extends SQLExprImpl implements SQLName {
}
this.owner = owner;
}
public String getName() {
return this.name;
}
@@ -363,7 +363,7 @@ public class MySqlLexer extends Lexer {
return;
}
if (ch == '*' && charAt(pos + 1) == '/') {
bufPos += 2;
bufPos += 3;
scanChar();
scanChar();
break;
@@ -393,11 +393,6 @@ public class MySqlLexer extends Lexer {
return;
}
if (!isAllowComment()) {
throw new NotAllowCommentException();
}
if (ch == '/' || ch == '-') {
scanChar();
bufPos++;
@@ -425,8 +420,17 @@ public class MySqlLexer extends Lexer {
bufPos++;
}
stringVal = subString(mark + 1, bufPos);
stringVal = subString(mark, bufPos + 1);
token = Token.LINE_COMMENT;
if (commentHandler != null && commentHandler.handle(lastToken, stringVal)) {
return;
}
if (!isAllowComment()) {
throw new NotAllowCommentException();
}
return;
}
}
@@ -32,6 +32,8 @@ public class WallCheckResult {
private final boolean syntaxError;
private final WallSqlStat sqlStat;
public WallCheckResult(){
this(null);
}
@@ -50,15 +52,17 @@ public class WallCheckResult {
statementList = stmtList;
syntaxError = false;
}
this.sqlStat = sqlStat;
}
public WallCheckResult(WallSqlStat sqlStat){
this(sqlStat, Collections.<SQLStatement> emptyList());
}
public WallCheckResult(List<Violation> violations, Map<String, WallSqlTableStat> tableStats,
public WallCheckResult(WallSqlStat sqlStat, List<Violation> violations, Map<String, WallSqlTableStat> tableStats,
Map<String, WallSqlFunctionStat> functionStats, List<SQLStatement> statementList,
boolean syntaxError){
this.sqlStat = sqlStat;
this.tableStats = tableStats;
this.violations = violations;
this.functionStats = functionStats;
@@ -85,4 +89,9 @@ public class WallCheckResult {
public boolean isSyntaxError() {
return syntaxError;
}
public WallSqlStat getSqlStat() {
return sqlStat;
}
}
@@ -118,8 +118,6 @@ public class WallConfig implements WallConfigMBean {
this.limitZeroAllow = limitZero;
}
public boolean isConditionAndAlwayTrueAllow() {
return conditionAndAlwayTrueAllow;
}
@@ -20,12 +20,17 @@ import java.util.Map;
public class WallContext {
private final static ThreadLocal<WallContext> contextLocal = new ThreadLocal<WallContext>();
private final static ThreadLocal<WallContext> contextLocal = new ThreadLocal<WallContext>();
private WallSqlStat sqlStat;
private Map<String, WallSqlTableStat> tableStats;
private Map<String, WallSqlFunctionStat> functionStats;
private final String dbType;
private int commentCount;
private int warnnings = 0;
private int unionWarnnings = 0;
private int updateNoneConditionWarnnings = 0;
private int deleteNoneConditionWarnnings = 0;
public WallContext(String dbType){
this.dbType = dbType;
@@ -92,7 +97,7 @@ public class WallContext {
public static void clearContext() {
contextLocal.remove();
}
public static void setContext(WallContext context) {
contextLocal.set(context);
}
@@ -117,4 +122,56 @@ public class WallContext {
return dbType;
}
public int getCommentCount() {
return commentCount;
}
public void incrementCommentCount() {
if (this.commentCount == 0) {
this.warnnings++;
}
this.commentCount++;
}
public int getWarnnings() {
return warnnings;
}
public void incrementWarnnings() {
this.warnnings++;
}
public int getUnionWarnnings() {
return unionWarnnings;
}
public void incrementUnionWarnnings() {
if (this.unionWarnnings == 0) {
this.incrementWarnnings();
}
this.unionWarnnings++;
}
public int getUpdateNoneConditionWarnnings() {
return updateNoneConditionWarnnings;
}
public void incrementUpdateNoneConditionWarnnings() {
if (this.updateNoneConditionWarnnings == 0) {
this.incrementWarnnings();
}
this.updateNoneConditionWarnnings++;
}
public int getDeleteNoneConditionWarnnings() {
return deleteNoneConditionWarnnings;
}
public void incrementDeleteNoneConditionWarnnings() {
if (this.deleteNoneConditionWarnnings == 0) {
this.incrementWarnnings();
}
this.deleteNoneConditionWarnnings++;
}
}
@@ -45,32 +45,33 @@ public abstract class WallProvider {
private LRUCache<String, WallSqlStat> whiteList;
private int MAX_SQL_LENGTH = 2048; // 1k
private int MAX_SQL_LENGTH = 2048; // 1k
private int whiteSqlMaxSize = 500; // 1k
private int whiteSqlMaxSize = 500; // 1k
private LRUCache<String, WallSqlStat> blackList;
private int blackSqlMaxSize = 100; // 1k
private int blackSqlMaxSize = 100; // 1k
protected final WallConfig config;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private static final ThreadLocal<Boolean> privileged = new ThreadLocal<Boolean>();
private static final ThreadLocal<Boolean> privileged = new ThreadLocal<Boolean>();
private final ConcurrentMap<String, WallFunctionStat> functionStats = new ConcurrentHashMap<String, WallFunctionStat>();
private final ConcurrentMap<String, WallTableStat> tableStats = new ConcurrentHashMap<String, WallTableStat>();
private final ConcurrentMap<String, WallFunctionStat> functionStats = new ConcurrentHashMap<String, WallFunctionStat>();
private final ConcurrentMap<String, WallTableStat> tableStats = new ConcurrentHashMap<String, WallTableStat>();
public final WallDenyStat commentDeniedStat = new WallDenyStat();
public final WallDenyStat commentDeniedStat = new WallDenyStat();
protected String dbType = null;
protected final AtomicLong checkCount = new AtomicLong();
protected final AtomicLong hardCheckCount = new AtomicLong();
protected final AtomicLong whiteListHitCount = new AtomicLong();
protected final AtomicLong blackListHitCount = new AtomicLong();
protected final AtomicLong syntaxErrrorCount = new AtomicLong();
protected final AtomicLong violationCount = new AtomicLong();
protected String dbType = null;
protected final AtomicLong checkCount = new AtomicLong();
protected final AtomicLong hardCheckCount = new AtomicLong();
protected final AtomicLong whiteListHitCount = new AtomicLong();
protected final AtomicLong blackListHitCount = new AtomicLong();
protected final AtomicLong syntaxErrrorCount = new AtomicLong();
protected final AtomicLong violationCount = new AtomicLong();
protected final AtomicLong violationEffectRowCount = new AtomicLong();
public WallProvider(WallConfig config){
this.config = config;
@@ -215,7 +216,7 @@ public abstract class WallProvider {
return Collections.<String> unmodifiableSet(hashSet);
}
public List<Map<String, Object>> getBlackListStat() {
List<Map<String, Object>> map = new ArrayList<Map<String, Object>>();
lock.readLock().lock();
@@ -223,23 +224,24 @@ public abstract class WallProvider {
if (blackList != null) {
for (Map.Entry<String, WallSqlStat> entry : this.blackList.entrySet()) {
WallSqlStat sqlStat = entry.getValue();
Map<String, Object> sqlStatMap = new LinkedHashMap<String, Object>();
sqlStatMap.put("sql", entry.getKey());
sqlStatMap.put("executeCount", sqlStat.getExecuteCount());
sqlStatMap.put("effectRowCount", sqlStat.getEffectRowCount());
List<Violation> violations = sqlStat.getViolations();
if (violations.size() > 0) {
sqlStatMap.put("violationMessage", violations.get(0).getMessage());
}
map.add(sqlStatMap);
}
}
} finally {
lock.readLock().unlock();
}
return map;
}
@@ -342,7 +344,7 @@ public abstract class WallProvider {
if (schemaName == null) {
return true;
}
if (!this.config.isSchemaCheck()) {
return true;
}
@@ -432,7 +434,8 @@ public abstract class WallProvider {
final Token lastToken = parser.getLexer().token();
if (lastToken != Token.EOF) {
violations.add(new IllegalSQLObjectViolation(ErrorCode.SYNTAX_ERROR, "not terminal sql, token " + lastToken, sql));
violations.add(new IllegalSQLObjectViolation(ErrorCode.SYNTAX_ERROR, "not terminal sql, token "
+ lastToken, sql));
}
} catch (NotAllowCommentException e) {
violations.add(new SyntaxErrorViolation(e, sql));
@@ -466,6 +469,18 @@ public abstract class WallProvider {
violations.addAll(visitor.getViolations());
}
if (context != null && context.getWarnnings() >= 2) {
if (context.getDeleteNoneConditionWarnnings() > 0) {
violations.add(new IllegalSQLObjectViolation(ErrorCode.NONE_CONDITION, "delete none condition", sql));
} else if (context.getUpdateNoneConditionWarnnings() > 0) {
violations.add(new IllegalSQLObjectViolation(ErrorCode.NONE_CONDITION, "update none condition", sql));
} else if (context.getCommentCount() > 0) {
violations.add(new IllegalSQLObjectViolation(ErrorCode.COMMIT_NOT_ALLOW, "comment not allow", sql));
} else {
violations.add(new IllegalSQLObjectViolation(ErrorCode.COMPOUND, "multi-warnnings", sql));
}
}
WallSqlStat sqlStat = null;
if (violations.size() > 0) {
violationCount.incrementAndGet();
@@ -492,7 +507,7 @@ public abstract class WallProvider {
return new WallCheckResult(sqlStat, statementList);
}
return new WallCheckResult(violations, tableStats, functionStats, statementList, syntaxError);
return new WallCheckResult(sqlStat, violations, tableStats, functionStats, statementList, syntaxError);
}
private WallCheckResult checkWhiteAndBlackList(String sql) {
@@ -609,6 +624,14 @@ public abstract class WallProvider {
public long getHardCheckCount() {
return hardCheckCount.get();
}
public long getViolationEffectRowCount() {
return violationEffectRowCount.get();
}
public void addViolationEffectRowCount(long rowCount) {
violationEffectRowCount.addAndGet(rowCount);
}
public static class WallCommentHandler implements Lexer.CommentHandler {
@@ -636,7 +659,12 @@ public abstract class WallProvider {
default:
break;
}
WallContext context = WallContext.current();
if (context != null) {
context.incrementCommentCount();
}
return false;
}
}
@@ -647,6 +675,7 @@ public abstract class WallProvider {
info.put("checkCount", this.getCheckCount());
info.put("hardCheckCount", this.getHardCheckCount());
info.put("violationCount", this.getViolationCount());
info.put("violationEffectRowCount", this.getViolationEffectRowCount());
info.put("blackListHitCount", this.getBlackListHitCount());
info.put("blackListSize", this.getBlackList().size());
info.put("whiteListHitCount", this.getWhiteListHitCount());
@@ -23,9 +23,13 @@ import java.util.concurrent.atomic.AtomicLongFieldUpdater;
public class WallSqlStat {
private volatile long executeCount;
private volatile long effectRowCount;
final static AtomicLongFieldUpdater<WallSqlStat> executeCountUpdater = AtomicLongFieldUpdater.newUpdater(WallSqlStat.class,
"executeCount");
final static AtomicLongFieldUpdater<WallSqlStat> executeCountUpdater = AtomicLongFieldUpdater.newUpdater(WallSqlStat.class,
"executeCount");
final static AtomicLongFieldUpdater<WallSqlStat> effectRowCountUpdater = AtomicLongFieldUpdater.newUpdater(WallSqlStat.class,
"effectRowCount");
private final Map<String, WallSqlTableStat> tableStats;
private final List<Violation> violations;
@@ -54,6 +58,18 @@ public class WallSqlStat {
public long getExecuteCount() {
return executeCount;
}
public long incrementAndGetEffectRowCount() {
return effectRowCountUpdater.incrementAndGet(this);
}
public long addAndGetEffectRowCount(long delta) {
return effectRowCountUpdater.addAndGet(this, delta);
}
public long getEffectRowCount() {
return effectRowCount;
}
public Map<String, WallSqlTableStat> getTableStats() {
return tableStats;
@@ -165,7 +165,8 @@ public class MySqlWallVisitor extends MySqlASTVisitorAdapter implements WallVisi
@Override
public boolean visit(SQLSelectStatement x) {
if (!config.isSelelctAllow()) {
this.getViolations().add(new IllegalSQLObjectViolation(ErrorCode.SELECT_NOT_ALLOW, "select not allow", this.toSQL(x)));
this.getViolations().add(new IllegalSQLObjectViolation(ErrorCode.SELECT_NOT_ALLOW, "select not allow",
this.toSQL(x)));
return false;
}
@@ -175,9 +176,18 @@ public class MySqlWallVisitor extends MySqlASTVisitorAdapter implements WallVisi
@Override
public boolean visit(Limit x) {
if (x.getRowCount() instanceof SQLNumericLiteralExpr) {
WallContext context = WallContext.current();
int rowCount = ((SQLNumericLiteralExpr) x.getRowCount()).getNumber().intValue();
if (rowCount == 0 && !provider.getConfig().isLimitZeroAllow()) {
this.getViolations().add(new IllegalSQLObjectViolation(ErrorCode.LIMIT_ZERO, "limit row 0", this.toSQL(x)));
if (rowCount == 0) {
if (context != null) {
context.incrementWarnnings();
}
if (!provider.getConfig().isLimitZeroAllow()) {
this.getViolations().add(new IllegalSQLObjectViolation(ErrorCode.LIMIT_ZERO, "limit row 0",
this.toSQL(x)));
}
}
}
return true;
@@ -190,14 +200,16 @@ public class MySqlWallVisitor extends MySqlASTVisitorAdapter implements WallVisi
String varName = varExpr.getName();
if (varName.equalsIgnoreCase("@@session") || varName.equalsIgnoreCase("@@global")) {
if (!(parent instanceof SQLSelectItem) && !(parent instanceof SQLAssignItem)) {
violations.add(new IllegalSQLObjectViolation(ErrorCode.VARIANT_DENY, "variable in condition not allow", toSQL(x)));
violations.add(new IllegalSQLObjectViolation(ErrorCode.VARIANT_DENY,
"variable in condition not allow", toSQL(x)));
return false;
}
if (!checkVar(x.getParent(), x.getName())) {
boolean isTop = WallVisitorUtils.isTopNoneFromSelect(x);
if (!isTop) {
violations.add(new IllegalSQLObjectViolation(ErrorCode.VARIANT_DENY, "variable not allow : " + x.getName(), toSQL(x)));
violations.add(new IllegalSQLObjectViolation(ErrorCode.VARIANT_DENY, "variable not allow : "
+ x.getName(), toSQL(x)));
}
}
return false;
@@ -245,7 +257,8 @@ public class MySqlWallVisitor extends MySqlASTVisitorAdapter implements WallVisi
if (varName.startsWith("@@") && !checkVar(x.getParent(), x.getName())) {
boolean isTop = WallVisitorUtils.isTopNoneFromSelect(x);
if (!isTop) {
violations.add(new IllegalSQLObjectViolation(ErrorCode.VARIANT_DENY, "variable not allow : " + x.getName(), toSQL(x)));
violations.add(new IllegalSQLObjectViolation(ErrorCode.VARIANT_DENY, "variable not allow : "
+ x.getName(), toSQL(x)));
}
}
@@ -364,7 +377,7 @@ public class MySqlWallVisitor extends MySqlASTVisitorAdapter implements WallVisi
public boolean visit(SQLCallStatement x) {
return false;
}
@Override
public boolean visit(MySqlShowCreateTableStatement x) {
String tableName = ((SQLName) x.getName()).getSimleName();
@@ -70,6 +70,7 @@ import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement;
import com.alibaba.druid.sql.ast.statement.SQLUseStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlBooleanExpr;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCommitStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDescribeStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlReplaceStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSetCharSetStatement;
@@ -235,10 +236,24 @@ public class WallVisitorUtils {
addViolation(visitor, ErrorCode.INSERT_NOT_ALLOW, "delete not allow", x);
return;
}
boolean hasUsing = false;
if (x instanceof MySqlDeleteStatement) {
hasUsing = ((MySqlDeleteStatement) x).getUsing() != null;
}
if (x.getWhere() == null && config.isDeleteWhereNoneCheck()) {
addViolation(visitor, ErrorCode.NONE_CONDITION, "delete none condition not allow", x);
return;
boolean isJoinTableSource = x.getTableSource() instanceof SQLJoinTableSource;
if (x.getWhere() == null && (!hasUsing) && !isJoinTableSource) {
WallContext context = WallContext.current();
if (context != null) {
context.incrementDeleteNoneConditionWarnnings();
}
if (config.isDeleteWhereNoneCheck()) {
addViolation(visitor, ErrorCode.NONE_CONDITION, "delete none condition not allow", x);
return;
}
}
if (Boolean.TRUE == getConditionValue(visitor, x.getWhere(), config.isDeleteWhereAlwayTrueCheck())) {
@@ -430,16 +445,23 @@ public class WallVisitorUtils {
return;
}
if (x.getWhere() == null && config.isUpdateWhereNoneCheck()) {
if (x instanceof MySqlUpdateStatement) {
MySqlUpdateStatement mysqlUpdate = (MySqlUpdateStatement) x;
if (mysqlUpdate.getLimit() == null) {
if (x.getWhere() == null) {
WallContext context = WallContext.current();
if (context != null) {
context.incrementUpdateNoneConditionWarnnings();
}
if (config.isUpdateWhereNoneCheck()) {
if (x instanceof MySqlUpdateStatement) {
MySqlUpdateStatement mysqlUpdate = (MySqlUpdateStatement) x;
if (mysqlUpdate.getLimit() == null) {
addViolation(visitor, ErrorCode.NONE_CONDITION, "update none condition not allow", x);
return;
}
} else {
addViolation(visitor, ErrorCode.NONE_CONDITION, "update none condition not allow", x);
return;
}
} else {
addViolation(visitor, ErrorCode.NONE_CONDITION, "update none condition not allow", x);
return;
}
}
@@ -643,6 +665,13 @@ public class WallVisitorUtils {
final Object value = getValue(x);
final WallConditionContext current = wallConditionContextLocal.get();
WallContext context = WallContext.current();
if (context != null) {
if (current.hasPartAlwayTrue() || Boolean.TRUE == value) {
context.incrementWarnnings();
}
}
if (current.hasPartAlwayTrue() && alwayTrueCheck && !visitor.getConfig().isConditionAndAlwayTrueAllow()) {
addViolation(visitor, ErrorCode.ALWAY_TRUE, "part alway true condition not allow", x);
}
@@ -910,7 +939,41 @@ public class WallVisitorUtils {
boolean topSelectStatement = isTopSelectStatement(x);
if (!topSelectStatement) {
addViolation(visitor, ErrorCode.SCHEMA_DENY, "deny schema : " + owner, x);
SQLObject parent = x.getParent();
while (parent != null && !(parent instanceof SQLStatement)) {
parent = parent.getParent();
}
boolean sameToTopSelectSchema = false;
if (parent instanceof SQLSelectStatement) {
SQLSelectStatement selectStmt = (SQLSelectStatement) parent;
SQLSelectQuery query = selectStmt.getSelect().getQuery();
if (query instanceof SQLSelectQueryBlock) {
SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) query;
SQLTableSource from = queryBlock.getFrom();
while (from instanceof SQLJoinTableSource) {
from = ((SQLJoinTableSource) from).getLeft();
}
if (from instanceof SQLExprTableSource) {
SQLExpr expr = ((SQLExprTableSource) from).getExpr();
if (expr instanceof SQLPropertyExpr) {
SQLExpr schemaExpr = ((SQLPropertyExpr) expr).getOwner();
if (schemaExpr instanceof SQLIdentifierExpr) {
String schema = ((SQLIdentifierExpr) schemaExpr).getName();
schema = form(schema);
if (schema.equalsIgnoreCase(owner)) {
sameToTopSelectSchema = true;
}
}
}
}
}
}
if (!sameToTopSelectSchema) {
addViolation(visitor, ErrorCode.SCHEMA_DENY, "deny schema : " + owner, x);
}
}
return true;
}
@@ -1026,10 +1089,6 @@ public class WallVisitorUtils {
return;
}
if (!visitor.getConfig().isSelectUnionCheck()) {
return;
}
if (WallVisitorUtils.queryBlockFromIsNull(x.getLeft()) || WallVisitorUtils.queryBlockFromIsNull(x.getRight())) {
boolean isTopUpdateStatement = false;
SQLObject selectParent = x.getParent();
@@ -1047,8 +1106,15 @@ public class WallVisitorUtils {
if (isTopUpdateStatement) {
return;
}
WallContext context = WallContext.current();
if (context != null) {
context.incrementUnionWarnnings();
}
addViolation(visitor, ErrorCode.UNION, "union query not contains 'from clause'", x);
if (visitor.getConfig().isSelectUnionCheck()) {
addViolation(visitor, ErrorCode.UNION, "union query not contains 'from clause'", x);
}
}
}
@@ -68,5 +68,7 @@ public interface ErrorCode {
public final static int READ_ONLY = 4000;
public final static int UNION = 5000;
public final static int COMPOUND = 8000;
public final static int OTHER = 9999;
}