【问题标题】:Java equivalent for PHP's mysql_real_escape_string()Java 等效于 PHP 的 mysql_real_escape_string()
【发布时间】:2010-11-04 08:01:37
【问题描述】:

是否有与 PHP 的 mysql_real_escape_string() 等效的 Java?

这是为了在将 SQL 注入尝试传递给 Statement.execute() 之前对其进行转义。

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

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

后记: 我继承的环境中有很多微妙之处是我在我的问题中故意避免的。

需要考虑的两点:

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

2) 大多数准备好的语句实现将语句绑定到实例化该语句的数据库连接。如果你使用数据库连接池,你需要小心
仅将准备好的语句引用与准备它的连接一起使用。一些池化机制确实透明地实现了这一点。否则,您也可以合并准备好的语句,或者(最简单但开销更大)为每个查询创建一个新的准备好的语句。

【问题讨论】:

  • 您更喜欢易于维护的代码,但您更愿意使用手动字符串转义而不是 PrearedStatement?
  • 鉴于现有代码(我没有编写)的方式,是的,它会更容易维护。
  • 较低的性能可能比安全风险更有吸引力。安全惩罚可能太高了。在应用程序初始化代码中准备好你的语句,惩罚可能不明显(当然取决于应用程序)。

标签: java sql mysql security jdbc


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      这里有一些代码可以实现您正在寻找的东西。最初在 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)+"`";
           }
      
       }
      

      【讨论】:

      • @droope 代码检查字符串中是否有任何危险字符。如果不是,则返回字符串而不进行处理。然后代码检查转义字符串是否通常会删除任何可能危险的字符。如果是这样,则以转义形式返回字符串。最后,转义的字符串被发送到 MySQL,由专门用于转义字符串的 QUOTE 函数进行转义。
      • @Ken V.H. 抱歉,该网站已经关闭了一段时间,所以我发布了原始代码。
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        据我所知,没有“标准”的方法。

        尽管您目前存在顾虑,但我强烈建议您使用准备好的语句。对性能的影响可以忽略不计 - 我们也有类似的情况,每秒有数千条语句 - 其中大多数也是一次性的。

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

        无论如何,如果您稍后真的发现遇到性能问题,请通过仔细分析确保准备好的语句确实是原因,然后寻找替代方案。在此之前,您应该省去尝试正确逃脱的麻烦。

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

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

            【解决方案7】:

            不要假设 PreparedStatements 较慢。尝试,测量,然后判断。

            PreparedStatements 应该总是优先于 Statement 使用,几乎无一例外,尤其是当您试图避免 SQL 注入攻击时。

            【讨论】:

              猜你喜欢
              • 2014-07-13
              • 2010-09-06
              • 2013-02-26
              • 1970-01-01
              • 2012-11-27
              • 2012-06-30
              • 1970-01-01
              • 2016-02-07
              • 1970-01-01
              相关资源
              最近更新 更多