Java - 转义字符串以防止SQL注入

时间:2009-11-28 16:11:14

标签: java sql regex escaping sql-injection

我正在尝试在java中使用一些反sql注入,并且发现很难使用“replaceAll”字符串函数。最后,我需要一个功能,可以将任何现有\转换为\\,任何"转换为\",任何'转换为\',以及任何\n\\n,以便在MySQL评估字符串时SQL注入将被阻止。

我已经搞砸了一些我正在使用的代码,而且函数中的所有\\\\\\\\\\\都让我的眼睛变得疯狂。如果有人碰巧有这样的例子我会非常感激。

14 个答案:

答案 0 :(得分:238)

PreparedStatements是可行的方法,因为它们使SQL注入变得不可能。这是一个将用户输入作为参数的简单示例:

public insertUser(String name, String email) {
   Connection conn = null;
   PreparedStatement stmt = null;
   try {
      conn = setupTheDatabaseConnectionSomehow();
      stmt = conn.prepareStatement("INSERT INTO person (name, email) values (?, ?)");
      stmt.setString(1, name);
      stmt.setString(2, email);
      stmt.executeUpdate();
   }
   finally {
      try {
         if (stmt != null) { stmt.close(); }
      }
      catch (Exception e) {
         // log this error
      }
      try {
         if (conn != null) { conn.close(); }
      }
      catch (Exception e) {
         // log this error
      }
   }
}

无论姓名和电子邮件中包含哪些字符,这些字符都将直接放在数据库中。它们不会以任何方式影响INSERT语句。

对于不同的数据类型有不同的设置方法 - 您使用哪种方法取决于您的数据库字段。例如,如果数据库中有INTEGER列,则应使用setInt方法。 The PreparedStatement documentation列出了可用于设置和获取数据的所有不同方法。

答案 1 :(得分:44)

防止SQL注入的唯一方法是使用参数化SQL。只是不可能构建一个比以黑客为生的人更聪明的过滤器。

因此,请对所有输入,更新和where子句使用参数。动态SQL只是黑客的一扇门,它包括存储过程中的动态SQL。参数化,参数化,参数化。

答案 2 :(得分:34)

如果确实无法使用Defense Option 1: Prepared Statements (Parameterized Queries)Defense Option 2: Stored Procedures,请不要构建自己的工具,请使用OWASP Enterprise Security API。来自Google代码托管的OWASP ESAPI

  

不要编写自己的安全控件!在为每个Web应用程序或Web服务开发安全控件时重新发明轮子会导致浪费时间和大量安全漏洞。 OWASP企业安全API(ESAPI)工具包可帮助软件开发人员防范与安全相关的设计和实现缺陷。

有关详细信息,请参阅Preventing SQL Injection in JavaSQL Injection Prevention Cheat Sheet

特别关注介绍Defense Option 3: Escaping All User Supplied Input项目的OWASP ESAPI

答案 3 :(得分:19)

(这是对原始问题下OP的评论的回答;我完全同意PreparedStatement是这项工作的工具,而非正则表达式。)

当您说\n时,您的意思是序列\ + n还是实际的换行符?如果它是\ + n,则任务非常简单:

s = s.replaceAll("['\"\\\\]", "\\\\$0");

要匹配输入中的一个反斜杠,请将其中的四个放入正则表达式字符串中。要在输出中放入一个反斜杠,可以将其中的四个放入替换字符串中。这假设您正在以Java String文字的形式创建正则表达式和替换。如果您以任何其他方式创建它们(例如,通过从文件中读取它们),则不必执行所有双重转义。

如果您在输入中有换行符并且想要用转义序列替换它,则可以使用以下命令对输入进行第二次传递:

s = s.replaceAll("\n", "\\\\n");

或许你想要两个反斜杠(我不太清楚):

s = s.replaceAll("\n", "\\\\\\\\n");

答案 4 :(得分:13)

PreparedStatements是进入大多数情况的方式,但不是所有情况。有时您会发现自己处于需要构建查询或其中一部分的情况并将其存储为字符串以供以后使用。查看SQL Injection Prevention Cheat Sheet上的OWASP Site,了解更多详细信息和不同编程语言的API。

答案 5 :(得分:9)

使用正则表达式删除可能导致SQL注入的文本听起来像SQL语句通过Statement而不是PreparedStatement发送到数据库。

首先防止SQL注入的最简单方法之一是使用PreparedStatement,它使用占位符接受数据替换为SQL语句,占位符不依赖于字符串连接来创建SQL要发送到数据库的语句。

有关详细信息,Using Prepared Statements中的The Java Tutorials将是一个很好的起点。

答案 6 :(得分:8)

准备好的语句是最好的解决方案,但如果你真的需要手动完成,你也可以使用Apache Commons-Lang库中的StringEscapeUtils类。它有一个escapeSql(String)方法,您可以使用它:

import org.apache.commons.lang.StringEscapeUtils; … String escapedSQL = StringEscapeUtils.escapeSql(unescapedSQL);

答案 7 :(得分:6)

如果您正在处理遗留系统,或者您有太多地方在很短的时间内切换到PreparedStatement - 即如果使用其他答案建议的最佳做法存在障碍,则可以尝试AntiSQLFilter

答案 8 :(得分:6)

您需要以下代码。乍一看,这可能看起来像我编写的任何旧代码。但是,我所做的是查看http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.31/com/mysql/jdbc/PreparedStatement.java的源代码。然后,我仔细查看了setString(int parameterIndex,String x)的代码,找到它转义的字符,并将其自定义为我自己的类,以便它可以用于您需要的目的。毕竟,如果这是Oracle逃脱的字符列表,那么知道这一点在安全方面确实令人感到安慰。也许Oracle需要轻推一下,为下一个主要的Java版本添加一个与此类似的方法。

public class SQLInjectionEscaper {

    public static String escapeString(String x, boolean escapeDoubleQuotes) {
        StringBuilder sBuilder = new StringBuilder(x.length() * 11/10);

        int stringLength = x.length();

        for (int i = 0; i < stringLength; ++i) {
            char c = x.charAt(i);

            switch (c) {
            case 0: /* Must be escaped for 'mysql' */
                sBuilder.append('\\');
                sBuilder.append('0');

                break;

            case '\n': /* Must be escaped for logs */
                sBuilder.append('\\');
                sBuilder.append('n');

                break;

            case '\r':
                sBuilder.append('\\');
                sBuilder.append('r');

                break;

            case '\\':
                sBuilder.append('\\');
                sBuilder.append('\\');

                break;

            case '\'':
                sBuilder.append('\\');
                sBuilder.append('\'');

                break;

            case '"': /* Better safe than sorry */
                if (escapeDoubleQuotes) {
                    sBuilder.append('\\');
                }

                sBuilder.append('"');

                break;

            case '\032': /* This gives problems on Win32 */
                sBuilder.append('\\');
                sBuilder.append('Z');

                break;

            case '\u00a5':
            case '\u20a9':
                // escape characters interpreted as backslash by mysql
                // fall through

            default:
                sBuilder.append(c);
            }
        }

        return sBuilder.toString();
    }
}

答案 9 :(得分:0)

在搜索大量的解决方案以防止sqlmap从sql注入之后,如果遗留系统无法在每个地方应用准备好的语句。

java-security-cross-site-scripting-xss-and-sql-injection topic 是解决方案

我试过@Richard的解决方案,但在我的情况下不起作用。 我用过滤器

  

此过滤器的目标是将请求包装为自己编码的   包装器MyHttpRequestWrapper转换:

     

将带有特殊字符(&lt;,&gt;,',...)的HTTP参数转换为HTML   代码通过org.springframework.web.util.HtmlUtils.htmlEscape(...)   方法。注意:Apache Commons中有类似的classe:   org.apache.commons.lang.StringEscapeUtils.escapeHtml(...)SQL   注入字符(',“,...)通过Apache Commons classe   org.apache.commons.lang.StringEscapeUtils.escapeSql(...)

<filter>
<filter-name>RequestWrappingFilter</filter-name>
<filter-class>com.huo.filter.RequestWrappingFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>RequestWrappingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>




package com.huo.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletReponse;
import javax.servlet.http.HttpServletRequest;

public class RequestWrappingFilter implements Filter{

    public void doFilter(ServletRequest req, ServletReponse res, FilterChain chain) throws IOException, ServletException{
        chain.doFilter(new MyHttpRequestWrapper(req), res);
    }

    public void init(FilterConfig config) throws ServletException{
    }

    public void destroy() throws ServletException{
    }
}




package com.huo.filter;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.commons.lang.StringEscapeUtils;

public class MyHttpRequestWrapper extends HttpServletRequestWrapper{
    private Map<String, String[]> escapedParametersValuesMap = new HashMap<String, String[]>();

    public MyHttpRequestWrapper(HttpServletRequest req){
        super(req);
    }

    @Override
    public String getParameter(String name){
        String[] escapedParameterValues = escapedParametersValuesMap.get(name);
        String escapedParameterValue = null; 
        if(escapedParameterValues!=null){
            escapedParameterValue = escapedParameterValues[0];
        }else{
            String parameterValue = super.getParameter(name);

            // HTML transformation characters
            escapedParameterValue = org.springframework.web.util.HtmlUtils.htmlEscape(parameterValue);

            // SQL injection characters
            escapedParameterValue = StringEscapeUtils.escapeSql(escapedParameterValue);

            escapedParametersValuesMap.put(name, new String[]{escapedParameterValue});
        }//end-else

        return escapedParameterValue;
    }

    @Override
    public String[] getParameterValues(String name){
        String[] escapedParameterValues = escapedParametersValuesMap.get(name);
        if(escapedParameterValues==null){
            String[] parametersValues = super.getParameterValues(name);
            escapedParameterValue = new String[parametersValues.length];

            // 
            for(int i=0; i<parametersValues.length; i++){
                String parameterValue = parametersValues[i];
                String escapedParameterValue = parameterValue;

                // HTML transformation characters
                escapedParameterValue = org.springframework.web.util.HtmlUtils.htmlEscape(parameterValue);

                // SQL injection characters
                escapedParameterValue = StringEscapeUtils.escapeSql(escapedParameterValue);

                escapedParameterValues[i] = escapedParameterValue;
            }//end-for

            escapedParametersValuesMap.put(name, escapedParameterValues);
        }//end-else

        return escapedParameterValues;
    }
}

答案 10 :(得分:0)

自:[来源]

public String MysqlRealScapeString(String str){
  String data = null;
  if (str != null && str.length() > 0) {
    str = str.replace("\\", "\\\\");
    str = str.replace("'", "\\'");
    str = str.replace("\0", "\\0");
    str = str.replace("\n", "\\n");
    str = str.replace("\r", "\\r");
    str = str.replace("\"", "\\\"");
    str = str.replace("\\x1a", "\\Z");
    data = str;
  }
return data;

}

答案 11 :(得分:0)

如果您使用的是PL / SQL,也可以使用DBMS_ASSERT 它可以清理您的输入,因此您可以使用它而不必担心SQL注入。

例如参见以下答案: https://stackoverflow.com/a/21406499/1726419

答案 12 :(得分:0)

大多数人都推荐 PreparedStatements,但是这要求您使用 Java 应用程序直接连接到您的数据库。但是随后您就会让其他人说,由于安全问题,您不应直接连接到数据库,而应使用 Restful API 来处理查询。

在我看来,只要你意识到你必须小心你逃避的事情并刻意去做,就不会有问题。

我的解决方案是使用 contains() 检查 SQL 关键字(例如 UPDATE)或其他危险字符(例如 =),通过要求用户在输入中插入其他字符来完全消除 SQL 注入.

编辑: 您可以使用来自 W3Schools 的有关 Java Regular Expressions 的源材料对字符串进行此验证。

答案 13 :(得分:-2)

首先,问一个问题 - 用户输入字段中需要双引号还是单引号或反斜杠?

反斜杠 - 不。双引号和单引号在英语中很少使用,在英国和美国的用法不同

我说删除或替换它们,你就简化了。

private String scrub(
    String parameter,
    int    length
    )
{
    String parm = null;

    if ( parameter != null && parameter.length() > 0 && parameter.length() < length )
    {
        parm = parameter
            .replace( "\\", " " )
            .replace( "\"", " " )
            .replace( "\'", " " )
            .replace( "\t", " " )
            .replace( "\r", " " )
            .replace( "\n", " " )
            .trim();
    }

    return parm;
}