Java中与PHP的mysql_real_escape_string()等效的函数是什么?

15

是否有Java等效于PHP的mysql_real_escape_string()函数?

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

我知道我可以使用PreparedStatement,但假设这些是一次性语句,因此准备它们将导致较低的性能。我已经更改了代码以使用PreparedStatement,但考虑到现有代码的结构,一个escape()函数将使代码更简单易于审查和维护;除非有强制性的理由需要额外的复杂性,否则我更喜欢易于维护的代码。此外,PreparedStatements被数据库以不同的方式处理,因此这可能会暴露我们以前没有遇到过的数据库错误,在发布到生产之前需要进行更多的测试。

Apache StringEscapeUtils escapeSQL()仅转义单引号。

附言: 我故意避开了我继承的环境中的许多微妙之处。

需要考虑两个要点:

1)准备好的语句并不能完全防止SQL注入。一些数据库驱动程序使用不安全的字符串连接实例化参数化查询,而不是将查询预编译为二进制形式。此外,如果您的SQL依赖存储过程,则需要确保存储过程本身不以不安全的方式构建查询。

2) 大多数预处理语句实现将语句绑定到创建它的数据库连接上。如果您正在使用数据库连接池,则需要小心地确保仅在它被准备的连接上使用预处理语句引用。某些连接池机制可以自动透明地实现这一点。否则,您可以将预处理语句作为池化对象,或者(最简单但开销更大)为每个查询创建一个新的预处理语句。


你更喜欢易于维护的代码,但你宁愿使用手动字符串转义而不是PrearedStatement吗? - skaffman
1
考虑到现有代码(我没有编写),是的,这样做会更容易维护。 - Kieran Tully
1
较低的性能可能比安全风险更具吸引力。安全惩罚可能会太高。在应用程序初始化代码中准备您的语句,这样惩罚可能就不会被注意到(当然取决于应用程序)。 - Cheekysoft
7个回答

14
据我所知,没有“标准”的方法来实现这一点。 我强烈建议使用预处理语句,尽管您目前有些担忧。性能影响将是可以忽略不计的 - 我们有一个类似的情况,每秒几千个语句 - 其中大部分也只是单次操作。你获得的安全性应该比你还没有看到的性能问题高得多。在我看来,这是一个明显的“不要过早优化”的情况。无论如何,如果您真的发现以后遇到性能问题,请仔细分析并查找其他替代方案,确保预处理语句确实是问题的原因。在那之前,您应该避免尝试正确地进行转义,特别是如果您正在开发某种公共面向站点的网站 - 内部应用程序很少会涉及到性能问题。

6
这里有一些代码可以实现你所需要的功能。这些代码最初出自于Vnet Publishing wiki。 点击此处可以查看详细内容。
/**
  * 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
@droope 代码检查字符串中是否有任何危险字符。如果没有,则返回未经处理的字符串。然后,代码检查是否通过正常转义可以删除任何可能危险的字符。如果是,则以转义形式返回字符串。最后,将转义后的字符串发送到MySQL,由QUOTE函数进行转义,该函数专门用于转义字符串。 - Ralph Ritoch
1
@Ken V.H. 对不起,网站已经宕机了一段时间,所以我发布了原始代码。 - Ralph Ritoch

5
不要假设PreparedStatements更慢。尝试一下,测量一下,然后再评判。
在避免SQL注入攻击时,几乎没有例外,应始终优先使用PreparedStatements,而不是Statement。

3

避免SQL注入攻击的唯一明智方法是使用预处理/参数化语句。

例如,您正在尝试避免的 PreparedStatement。如果您只执行一次语句,则准备时间应该可以忽略不计(“一次性”和“性能关键”是矛盾的,在我看来)。如果您在循环中执行操作,则准备好的语句甚至会导致性能提高。


2

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


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();

    }


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();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接