diff --git a/src/main/java/com/alibaba/druid/sql/SQLUtils.java b/src/main/java/com/alibaba/druid/sql/SQLUtils.java index 5a872def1a..d82ea56868 100644 --- a/src/main/java/com/alibaba/druid/sql/SQLUtils.java +++ b/src/main/java/com/alibaba/druid/sql/SQLUtils.java @@ -15,12 +15,14 @@ */ package com.alibaba.druid.sql; +import java.util.ArrayList; import java.util.List; import com.alibaba.druid.DruidRuntimeException; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLObject; import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; import com.alibaba.druid.sql.dialect.db2.visitor.DB2OutputVisitor; import com.alibaba.druid.sql.dialect.db2.visitor.DB2SchemaStatVisitor; import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor; @@ -44,7 +46,6 @@ import com.alibaba.druid.util.JdbcConstants; import com.alibaba.druid.util.JdbcUtils; import com.alibaba.druid.util.StringUtils; - public class SQLUtils { private final static Log LOG = LogFactory.getLog(SQLUtils.class); @@ -63,7 +64,7 @@ public class SQLUtils { if (JdbcUtils.POSTGRESQL.equals(dbType)) { return toPGString(sqlObject); } - + if (JdbcUtils.DB2.equals(dbType)) { return toDB2String(sqlObject); } @@ -114,7 +115,7 @@ public class SQLUtils { String sql = out.toString(); return sql; } - + public static String toDB2String(SQLObject sqlObject) { StringBuilder out = new StringBuilder(); sqlObject.accept(new DB2OutputVisitor(out)); @@ -211,7 +212,7 @@ public class SQLUtils { if (JdbcUtils.SQL_SERVER.equals(dbType) || JdbcUtils.JTDS.equals(dbType)) { return new SQLServerOutputVisitor(out); } - + if (JdbcUtils.DB2.equals(dbType)) { return new DB2OutputVisitor(out); } @@ -257,37 +258,53 @@ public class SQLUtils { } return stmtList; } - - /** + + /** * @author owenludong.lud - * @param columnName - * @param tableAlias - * @param pattern if pattern is null,it will be set {%Y-%m-%d %H:%i:%s} as mysql default value and set {yyyy-mm-dd hh24:mi:ss} as oracle default value - * @param dbType {@link JdbcConstants} if dbType is null ,it will be set the mysql as a default value + * @param columnName + * @param tableAlias + * @param pattern if pattern is null,it will be set {%Y-%m-%d %H:%i:%s} as mysql default value and set {yyyy-mm-dd + * hh24:mi:ss} as oracle default value + * @param dbType {@link JdbcConstants} if dbType is null ,it will be set the mysql as a default value */ - public static String buildToDate(String columnName,String tableAlias,String pattern,String dbType){ - StringBuilder sql = new StringBuilder(); - if(StringUtils.isEmpty(columnName)) - return ""; - if(StringUtils.isEmpty(dbType)) dbType = JdbcConstants.MYSQL; - String formatMethod = ""; - if(JdbcConstants.MYSQL.equalsIgnoreCase(dbType)){ - formatMethod = "STR_TO_DATE"; - if(StringUtils.isEmpty(pattern)) pattern = "%Y-%m-%d %H:%i:%s"; - }else if(JdbcConstants.ORACLE.equalsIgnoreCase(dbType)){ - formatMethod = "TO_DATE"; - if(StringUtils.isEmpty(pattern)) pattern = "yyyy-mm-dd hh24:mi:ss"; - }else{ - return ""; - //expand date's handle method for other database - } - sql.append(formatMethod).append("("); - if(!StringUtils.isEmpty(tableAlias)) - sql.append(tableAlias).append("."); - sql.append(columnName).append(","); - sql.append("'"); - sql.append(pattern); - sql.append("')"); - return sql.toString(); - } + public static String buildToDate(String columnName, String tableAlias, String pattern, String dbType) { + StringBuilder sql = new StringBuilder(); + if (StringUtils.isEmpty(columnName)) return ""; + if (StringUtils.isEmpty(dbType)) dbType = JdbcConstants.MYSQL; + String formatMethod = ""; + if (JdbcConstants.MYSQL.equalsIgnoreCase(dbType)) { + formatMethod = "STR_TO_DATE"; + if (StringUtils.isEmpty(pattern)) pattern = "%Y-%m-%d %H:%i:%s"; + } else if (JdbcConstants.ORACLE.equalsIgnoreCase(dbType)) { + formatMethod = "TO_DATE"; + if (StringUtils.isEmpty(pattern)) pattern = "yyyy-mm-dd hh24:mi:ss"; + } else { + return ""; + // expand date's handle method for other database + } + sql.append(formatMethod).append("("); + if (!StringUtils.isEmpty(tableAlias)) sql.append(tableAlias).append("."); + sql.append(columnName).append(","); + sql.append("'"); + sql.append(pattern); + sql.append("')"); + return sql.toString(); + } + + public static List split(SQLBinaryOpExpr x) { + List groupList = new ArrayList(); + SQLExpr left = x.getLeft(); + for (;;) { + if (left instanceof SQLBinaryOpExpr && ((SQLBinaryOpExpr) left).getOperator() == x.getOperator()) { + SQLBinaryOpExpr binaryLeft = (SQLBinaryOpExpr) left; + groupList.add(binaryLeft.getRight()); + left = binaryLeft.getLeft(); + } else { + groupList.add(left); + break; + } + } + groupList.add(x.getRight()); + return groupList; + } } diff --git a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLMethodInvokeExpr.java b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLMethodInvokeExpr.java index e9848e2b14..1481371569 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLMethodInvokeExpr.java +++ b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLMethodInvokeExpr.java @@ -67,6 +67,13 @@ public class SQLMethodInvokeExpr extends SQLExprImpl implements Serializable { public List getParameters() { return this.parameters; } + + public void addParameter(SQLExpr param) { + if (param != null) { + param.setParent(this); + } + this.parameters.add(param); + } public void output(StringBuffer buf) { if (this.owner != null) { diff --git a/src/main/java/com/alibaba/druid/sql/dialect/mysql/parser/MySqlExprParser.java b/src/main/java/com/alibaba/druid/sql/dialect/mysql/parser/MySqlExprParser.java index 9d1854a6d3..22a6555fd9 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/mysql/parser/MySqlExprParser.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/mysql/parser/MySqlExprParser.java @@ -229,10 +229,10 @@ public class MySqlExprParser extends SQLExprParser { } } else if (expr instanceof SQLCharExpr) { SQLMethodInvokeExpr concat = new SQLMethodInvokeExpr("CONCAT"); - concat.getParameters().add(expr); + concat.addParameter(expr); do { String chars = lexer.stringVal(); - concat.getParameters().add(new SQLCharExpr(chars)); + concat.addParameter(new SQLCharExpr(chars)); lexer.nextToken(); } while (lexer.token() == Token.LITERAL_CHARS); expr = concat; @@ -325,7 +325,7 @@ public class MySqlExprParser extends SQLExprParser { SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr(ident); for (;;) { SQLExpr param = expr(); - methodInvokeExpr.getParameters().add(param); + methodInvokeExpr.addParameter(param); if (lexer.token() == Token.COMMA) { lexer.nextToken(); @@ -333,12 +333,12 @@ public class MySqlExprParser extends SQLExprParser { } else if (lexer.token() == Token.FROM) { lexer.nextToken(); SQLExpr from = expr(); - methodInvokeExpr.getParameters().add(from); + methodInvokeExpr.addParameter(from); if (lexer.token() == Token.FOR) { lexer.nextToken(); SQLExpr forExpr = expr(); - methodInvokeExpr.getParameters().add(forExpr); + methodInvokeExpr.addParameter(forExpr); } break; } else if (lexer.token() == Token.RPAREN) { @@ -371,7 +371,7 @@ public class MySqlExprParser extends SQLExprParser { } SQLExpr param = expr(); - methodInvokeExpr.getParameters().add(param); + methodInvokeExpr.addParameter(param); if (lexer.token() == Token.FROM) { lexer.nextToken(); @@ -461,8 +461,8 @@ public class MySqlExprParser extends SQLExprParser { accept(Token.RPAREN); SQLMethodInvokeExpr locate = new SQLMethodInvokeExpr("LOCATE"); - locate.getParameters().add(subStr); - locate.getParameters().add(str); + locate.addParameter(subStr); + locate.addParameter(str); expr = locate; return primaryRest(expr); @@ -502,7 +502,7 @@ public class MySqlExprParser extends SQLExprParser { SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr("INTERVAL"); if (lexer.token() != Token.RPAREN) { - exprList(methodInvokeExpr.getParameters()); + exprList(methodInvokeExpr.getParameters(), methodInvokeExpr); } accept(Token.RPAREN); diff --git a/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java b/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java index 78fb8bc140..c37ee63610 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java +++ b/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java @@ -667,7 +667,7 @@ public class SQLExprParser extends SQLParser { lexer.nextToken(); } else { if (lexer.token() == Token.PLUS) { - methodInvokeExpr.getParameters().add(new SQLIdentifierExpr("+")); + methodInvokeExpr.addParameter(new SQLIdentifierExpr("+")); lexer.nextToken(); } else { exprList(methodInvokeExpr.getParameters(), methodInvokeExpr); 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 5c2f777ea7..9b58390d80 100644 --- a/src/main/java/com/alibaba/druid/wall/spi/WallVisitorUtils.java +++ b/src/main/java/com/alibaba/druid/wall/spi/WallVisitorUtils.java @@ -22,12 +22,12 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; -import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Set; +import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLName; import com.alibaba.druid.sql.ast.SQLObject; @@ -44,6 +44,7 @@ import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; import com.alibaba.druid.sql.ast.expr.SQLInListExpr; import com.alibaba.druid.sql.ast.expr.SQLInSubQueryExpr; import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; +import com.alibaba.druid.sql.ast.expr.SQLLiteralExpr; import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; import com.alibaba.druid.sql.ast.expr.SQLNCharExpr; import com.alibaba.druid.sql.ast.expr.SQLNotExpr; @@ -121,7 +122,27 @@ public class WallVisitorUtils { } public static void check(WallVisitor visitor, SQLBinaryOpExpr x) { - + if (x.getOperator() == SQLBinaryOperator.Add || x.getOperator() == SQLBinaryOperator.Concat) { + List groupList = SQLUtils.split(x); + if (groupList.size() >= 4) { + int chrCount = 0; + for (int i = 0; i < groupList.size(); ++i) { + SQLExpr item = groupList.get(i); + if (item instanceof SQLMethodInvokeExpr) { + SQLMethodInvokeExpr methodExpr = (SQLMethodInvokeExpr) item; + String methodName = methodExpr.getMethodName().toLowerCase(); + if ("chr".equals(methodName) || "char".equals(methodName)) { + if (methodExpr.getParameters().get(0) instanceof SQLLiteralExpr) { + chrCount++; + } + } + } + } + if (chrCount >= 4) { + addViolation(visitor, ErrorCode.EVIL_CONCAT, "evil concat", x); + } + } + } } public static void check(WallVisitor visitor, SQLCreateTableStatement x) { @@ -232,7 +253,7 @@ public class WallVisitorUtils { if (queryBlockFromIsNull(visitor, x, false)) { addViolation(visitor, ErrorCode.EMPTY_QUERY_HAS_CONDITION, "empty select has condition", x); } - + boolean isSimpleConstExpr = false; SQLExpr first = getFirst(where); @@ -616,19 +637,7 @@ public class WallVisitorUtils { } if (x.getOperator() == SQLBinaryOperator.BooleanOr) { - List groupList = new ArrayList(); - SQLExpr left = x.getLeft(); - for (;;) { - if (left instanceof SQLBinaryOpExpr && ((SQLBinaryOpExpr) left).getOperator() == x.getOperator()) { - SQLBinaryOpExpr binaryLeft = (SQLBinaryOpExpr) left; - groupList.add(binaryLeft.getRight()); - left = binaryLeft.getLeft(); - } else { - groupList.add(left); - break; - } - } - groupList.add(x.getRight()); + List groupList = SQLUtils.split(x); boolean allFalse = true; for (int i = groupList.size() - 1; i >= 0; --i) { @@ -1108,7 +1117,7 @@ public class WallVisitorUtils { checkSchema(visitor, x.getOwner()); - String methodName = x.getMethodName(); + String methodName = x.getMethodName().toLowerCase(); if (!visitor.getProvider().checkDenyTable(methodName)) { if (isTopUpdateStatement(x) || isFirstSelectTableSource(x)) { if (topStatementContext != null) { @@ -1132,7 +1141,7 @@ public class WallVisitorUtils { return; } - String methodName = x.getMethodName(); + String methodName = x.getMethodName().toLowerCase(); WallContext context = WallContext.current(); if (context != null) { diff --git a/src/main/java/com/alibaba/druid/wall/violation/ErrorCode.java b/src/main/java/com/alibaba/druid/wall/violation/ErrorCode.java index 644d168fbb..0159fc69db 100644 --- a/src/main/java/com/alibaba/druid/wall/violation/ErrorCode.java +++ b/src/main/java/com/alibaba/druid/wall/violation/ErrorCode.java @@ -66,6 +66,7 @@ public interface ErrorCode { public final static int CONST_CASE_CONDITION = 2109; public final static int EVIL_HINTS = 2110; public final static int EVIL_NAME = 2111; + public final static int EVIL_CONCAT = 2112; public final static int NOT_PARAMETERIZED = 2200; public final static int MULTI_TENANT = 2201; diff --git a/src/test/java/com/alibaba/druid/bvt/filter/wall/MySqlWallTest140.java b/src/test/java/com/alibaba/druid/bvt/filter/wall/MySqlWallTest140.java new file mode 100644 index 0000000000..ebeb643366 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/filter/wall/MySqlWallTest140.java @@ -0,0 +1,35 @@ +/* + * 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.WallProvider; +import com.alibaba.druid.wall.spi.MySqlWallProvider; + +public class MySqlWallTest140 extends TestCase { + + public void test_false() throws Exception { + WallProvider provider = new MySqlWallProvider(); + + String sql = "SELECT name, '******' password, createTime from user " // + + "where name like 'admin%' "// + + " AND 8600=CONVERT(INT,(SELECT CHAR(113)+CHAR(118)+CHAR(100)+CHAR(114)+CHAR(113)+(SELECT (CASE WHEN (8600=8600) THEN CHAR(49) ELSE CHAR(48) END))+CHAR(113)+CHAR(118)+CHAR(98)+CHAR(97)+CHAR(113))) AND '%'=''"; + Assert.assertFalse(provider.checkValid(sql)); + } +} diff --git a/src/test/java/com/alibaba/druid/bvt/filter/wall/MySqlWallTest141.java b/src/test/java/com/alibaba/druid/bvt/filter/wall/MySqlWallTest141.java new file mode 100644 index 0000000000..166ec5f1ab --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/filter/wall/MySqlWallTest141.java @@ -0,0 +1,33 @@ +/* + * 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.WallProvider; +import com.alibaba.druid.wall.spi.MySqlWallProvider; + +public class MySqlWallTest141 extends TestCase { + + public void test_false() throws Exception { + WallProvider provider = new MySqlWallProvider(); + + String sql = "SELECT name, '******' password, createTime from user where length(name) = 999999.9 or ascii(substring((database()),1,1))=127"; + Assert.assertFalse(provider.checkValid(sql)); + } +}