Java的等价于PHP的mysql_real_escape_string()

时间:2009-06-19 14:34:56

标签: java sql mysql security jdbc

是否存在与PHP的mysql_real_escape_string()相当的Java?

这是为了在将它们传递给Statement.execute()之前逃避SQL注入尝试。

我知道我可以使用PreparedStatement,但我们假设这些是一次性语句,因此准备它们将导致lower performance。我已经将代码更改为使用PreparedStatement,但考虑到现有代码的结构,escape()函数会使代码更改更容易查看和维护;我更喜欢易于维护代码,除非有一个令人信服的理由来增加复杂性。此外,PreparedStatements由数据库以不同方式处理,因此这可能会使我们面临数据库中我们之前未遇到的错误,在发布到生产之前需要进行更多测试。

Apache StringEscapeUtils escapeSQL()只能使用单引号。

后记: 在我继承的环境中有许多细微之处,我在我的问题中故意避免。

要考虑两点:

1)准备好的声明不是灵丹妙药,也不能提供100%的SQL注入保护。某些数据库驱动程序使用不安全的字符串连接实例化参数化查询,而不是将查询预编译为二进制形式。此外,如果您的SQL依赖于存储过程,则需要确保存储过程本身不会以不安全的方式构建查询。

2)大多数预准备语句实现将语句绑定到语句被实例化的数据库连接上。如果您使用的是数据库连接池,则需要注意 使用预准备语句引用仅与其准备的连接一起使用。一些池化机制确实透明地实现了这一点。否则,您也可以汇集预准备语句或(最简单但更多开销)为每个查询创建一个新的预准备语句。

7 个答案:

答案 0 :(得分:12)

据我所知,没有“标准”方法可以做到。

尽管您目前担心,我强烈建议您使用准备好的陈述。性能影响可以忽略不计 - 我们有类似的情况,每秒有几千个语句 - 其中大多数也是一次性的。

您获得的安全性应该比您尚未看到的性能问题高得多。在我看来,这是“不要过早优化”的明确情况。

在任何情况下,如果您真的发现性能问题,请确保准备好的语句通过仔细分析然后寻找备选方案来确定原因。那么你应该省去试图逃避正确的麻烦。

这更为重要,因为我推断您正在开发某种面向公众的网站 - 内部应用程序很少获得足够的流量来关注性能。

答案 1 :(得分:6)

以下是一些可以实现您所需要的代码。最初是在Vnet Publishing wiki上。

https://web.archive.org/web/20131202082741/http://wiki.vnetpublishing.com/Java_Mysql_Real_Escape_String

/**
  * Mysql Utilities
  *        
  * @author Ralph Ritoch <rritoch@gmail.com>
  * @copyright Ralph Ritoch 2011 ALL RIGHTS RESERVED
  * @link http://www.vnetpublishing.com
  *
  */

 package vnet.java.util;

 public class MySQLUtils {

     /**
      * Escape string to protected against SQL Injection
      *
      * You must add a single quote ' around the result of this function for data,
      * or a backtick ` around table and row identifiers. 
      * If this function returns null than the result should be changed
      * to "NULL" without any quote or backtick.
      *
      * @param link
      * @param str
      * @return
      * @throws Exception 
      */

     public static String mysql_real_escape_string(java.sql.Connection link, String str) 
           throws Exception
     {
         if (str == null) {
             return null;
         }

         if (str.replaceAll("[a-zA-Z0-9_!@#$%^&*()-=+~.;:,\\Q[\\E\\Q]\\E<>{}\\/? ]","").length() < 1) {
             return str;
         }

         String clean_string = str;
         clean_string = clean_string.replaceAll("\\\\", "\\\\\\\\");
         clean_string = clean_string.replaceAll("\\n","\\\\n");
         clean_string = clean_string.replaceAll("\\r", "\\\\r");
         clean_string = clean_string.replaceAll("\\t", "\\\\t");
         clean_string = clean_string.replaceAll("\\00", "\\\\0");
         clean_string = clean_string.replaceAll("'", "\\\\'");
         clean_string = clean_string.replaceAll("\\\"", "\\\\\"");

         if (clean_string.replaceAll("[a-zA-Z0-9_!@#$%^&*()-=+~.;:,\\Q[\\E\\Q]\\E<>{}\\/?\\\\\"' ]"
           ,"").length() < 1) 
         {
             return clean_string;
         }

         java.sql.Statement stmt = link.createStatement();
         String qry = "SELECT QUOTE('"+clean_string+"')";

         stmt.executeQuery(qry);
         java.sql.ResultSet resultSet = stmt.getResultSet();
         resultSet.first();
         String r = resultSet.getString(1);
         return r.substring(1,r.length() - 1);       
     }

     /**
      * Escape data to protected against SQL Injection
      *
      * @param link
      * @param str
      * @return
      * @throws Exception 
      */

     public static String quote(java.sql.Connection link, String str)
           throws Exception
     {
         if (str == null) {
             return "NULL";
         }
         return "'"+mysql_real_escape_string(link,str)+"'";
     }

     /**
      * Escape identifier to protected against SQL Injection
      *
      * @param link
      * @param str
      * @return
      * @throws Exception 
      */

     public static String nameQuote(java.sql.Connection link, String str)
           throws Exception
     {
         if (str == null) {
             return "NULL";
         }
         return "`"+mysql_real_escape_string(link,str)+"`";
     }

 }

答案 2 :(得分:5)

不要认为PreparedStatements较慢。试一试,测量它,然后判断。

PreparedStatements 始终总是优先于Statement使用,几乎毫无例外,尤其当SQL注入攻击是你想要避免的时候。

答案 3 :(得分:3)

避免SQL注入的唯一合理方法是使用预准备/参数化语句。

例如,您出于某种原因试图避免的PreparedStatement。如果你做一次性陈述,准备它们的时间应该可以忽略不计(“一次性”和“性能关键”是一个矛盾,恕我直言)。如果你在循环中做事,准备好的语句甚至会导致性能提高。

答案 4 :(得分:2)

commons-lang.jar中的

org.apache.commons.lang.StringEscapeUtils.class可以解决您的问题!

答案 5 :(得分:0)

根据Daniel Schneller的说法,Java中没有标准的方法来处理PHP的mysql_real_escape_string() 我所做的是链接replaceAll方法来处理可能需要避免任何异常的每个方面。这是我的示例代码:

public void saveExtractedText(String group,String content) { try { content = content.replaceAll("\\", "\\\\") .replaceAll("\n","\\n") .replaceAll("\r", "\\r") .replaceAll("\t", "\\t") .replaceAll("\00", "\\0") .replaceAll("'", "\\'") .replaceAll("\\"", "\\\"");

        state.execute("insert into extractiontext(extractedtext,extractedgroup) values('"+content+"','"+group+"')");
    } catch (Exception e) {
        e.printStackTrace();

    }

state.execute("insert into extractiontext(extractedtext,extractedgroup) values('"+content+"','"+group+"')"); } catch (Exception e) { e.printStackTrace(); }

答案 6 :(得分:0)

除了PreparedStatement之外,我不会信任任何其他东西来确保安全。但是,如果在构建查询时需要类似的工作流程,则可以使用以下代码。它在下面使用了PreparedStatement,像StringBuilder一样工作,添加了转义函数并为您跟踪参数索引。可以这样使用:

SQLBuilder sqlBuilder = new SQLBuilder("update ").append(dbName).append(".COFFEES ");
sqlBuilder.append("set SALES = ").escapeString(sales);
sqlBuilder.append(", TOTAL = ").escapeInt(total);
sqlBuilder.append("where COF_NAME = ").escapeString(coffeeName);
sqlBuilder.prepareStatement(connection).executeUpdate();

代码如下:

class SQLBuilder implements Appendable {
    private StringBuilder sqlBuilder;
    private List<Object> values = new ArrayList<>();

    public SQLBuilder() {
        sqlBuilder = new StringBuilder();
    }

    public SQLBuilder(String str)
    {
        sqlBuilder = new StringBuilder(str);
    }

    @Override
    public SQLBuilder append(CharSequence csq)
    {
        sqlBuilder.append(csq);
        return this;
    }

    @Override
    public SQLBuilder append(CharSequence csq, int start, int end)
    {
        sqlBuilder.append(csq, start, end);
        return this;
    }

    @Override
    public SQLBuilder append(char c)
    {
        sqlBuilder.append(c);
        return this;
    }

    // you can add other supported parameter types here...
    public SQLBuilder escapeString(String x)
    {
        protect(x);
        return this;
    }

    public SQLBuilder escapeInt(int x)
    {
        protect(x);
        return this;
    }

    private void escape(Object o)
    {
        sqlBuilder.append('?');
        values.add(o);
    }

    public PreparedStatement prepareStatement(Connection connection)
        throws SQLException
    {
        PreparedStatement preparedStatement =
            connection.prepareStatement(sqlBuilder.toString());
        for (int i = 0; i < values.size(); i++)
        {
            Object value = values.get(i);
            // you can add other supported parameter types here...
            if (value instanceof String)
                preparedStatement.setString(i + 1, (String) value);
            else if (value instanceof Integer)
                preparedStatement.setInt(i + 1, (Integer) value);
        }
        return preparedStatement;
    }

    @Override
    public String toString()
    {
        return "SQLBuilder: " + sqlBuilder.toString();
    }
}