Java 等效于 PHP 的 mysql_real_escape_string()

Posted

技术标签:

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

是否有与 phpmysql_real_escape_string() 等效的 Java?

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

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

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

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

需要考虑的两点:

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

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

【问题讨论】:

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

【讨论】:

以上是关于Java 等效于 PHP 的 mysql_real_escape_string()的主要内容,如果未能解决你的问题,请参考以下文章

PHP等效于JavaScript getTime() [重复]

Javascript 等效于 PHP 的 list()

PHP 5.2 等效于后期静态绑定(新静态)?

strcmp 等效于 PHP 中的整数 (strcmp)

使 JavaScript 正则表达式等效于 PHP 正则表达式

PHP 等效于 Perl 的“使用严格”(要求在使用前初始化变量)