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


146

我试图在Java中放置一些反SQL注入,并发现使用“ replaceAll”字符串函数非常困难。最后,我需要,将转换现有的功能\\\,任何"\",任何'\',任何\n\\n使得当字符串由MySQL的SQL注入评估将被阻止。

我已经整理了一些正在使用的代码,并且\\\\\\\\\\\该函数中的所有内容都使我大失所望。如果有人碰巧有这个例子,我将不胜感激。


1
好的,我得出的结论是,PreparedStatement是可行的方法,但是根据当前的目标,我需要按原计划进行,并且暂时放置一个筛选器,一旦达到当前的里程碑,我就可以返回并重构数据库以准备语句。在平均时间保持势头,是否有人有一个解决方案,以有效地逃避给出了Java MySQL的上述特点和它的正则表达式系统是一个绝对的痛苦制定需要....逃逸的数量
斯科特-邦纳

2
并非所有SQL语句都是可参数化的,例如“ SET ROLE role_name”或“ LISTEN channel_name”
Neil McGuigan

1
@NeilMcGuigan是的。CREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?由于主语句是DDL语句,因此大多数驱动程序也将拒绝参数化,即使您要参数化的部分实际上是DML
SeldomNeedy

Answers:


249

PreparedStatement是可行的方法,因为它们使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方法。 PreparedStatement文档列出了可用于设置和获取数据的所有不同方法。


1
通过这种方法,您可以将每个参数都视为字符串并且仍然安全吗?我试图找出一种方法来更新我现有的体系结构,使其安全,而不必重新构建整个数据库层……
Scott Bonner 2009年

1
所有的动态SQL都是字符串,因此这不是要问的问题。我对PrepareStatement并不熟悉,所以真正的问题是它是否会生成可以通过ExecuteUpdate执行的参数化查询。如果是,那很好。如果不是,那么这只是在隐藏问题,除了重新设计数据库层之外,您可能没有其他安全选择。从一开始就必须处理SQL注入问题。这不是您以后可以轻松添加的内容。
Cylon Cat

2
如果要插入INTEGER字段,则需要使用'setInt'。同样,其他数字数据库字段将使用其他设置器。我发布了指向PreparedStatement文档的链接,该文档列出了所有设置器类型。
Kaleb Brasee

2
是的,Cylon,PreparedStatements生成参数化查询。
Kaleb Brasee

2
@Kaleb Brasee,谢谢。很高兴知道。这些工具在每种环境中都不同,但是深入了解参数化查询是基本答案。
Cylon Cat

46

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

因此,请对所有输入,更新和where子句使用参数。动态SQL只是向黑客敞开的大门,并且在存储过程中包含了动态SQL。参数化,参数化,参数化。


11
甚至参数化的SQL也不是100%保证。但这是一个很好的开始。
duffymo

2
@duffymo,我同意没有什么是100%安全的。您是否有一个即使在参数化SQL下也可以使用的SQL注入示例?
Cylon Cat

3
@Cylon Cat:当然,当将一部分SQL(例如@WhereClause或@tableName)作为参数传递,串联到SQL中并动态执行时。当您让用户编写代码时,就会发生SQL注入。是否捕获它们的代码作为参数都没有关系。
史蒂夫·卡斯

16
顺便说一句,我不知道这是为什么不提及,但用预处理工作也容易,很多更具可读性。仅此一点就可能使它们成为所有了解它们的程序员的默认设置。
Edan Maor

3
请注意,某些数据库的PreparedStatement创建起来非常昂贵,因此,如果您需要做很多事情,请同时测量这两种类型。
托尔比约恩Ravn的安徒生

36

如果确实不能使用防御选项1:预准备语句(参数化查询)防御选项2:存储过程,则不要构建自己的工具,请使用OWASP Enterprise Security API。通过Google Code上托管的OWASP ESAPI

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

有关更多详细信息,请参阅《防止Java中的SQL注入》和《SQL Injection Prevention备忘单》

特别注意防御选项3:转义引入OWASP ESAPI项目的所有用户提供的输入)。


4
截至今天,ESAPI似乎已失效。在AWS上有WAF,它可以帮助防止SQL注入,XSS等。此时是否还有其他替代方法?
克里斯·奥德尼(ChrisOdney)'17

@ChrisOdney可以轻松绕过WAF。大多数框架已经实现了自己的SQL-Injection预防措施,在这些预防措施中,它们自己自动进行了参数转义。替代遗留项目:owasp.org/index.php/...
爪哇R.

19

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

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

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

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

如果输入中有换行符,并希望将其替换为转义符,则可以使用以下命令对输入进行第二遍:

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

或者,也许您想要两个反斜杠(对此我不太清楚):

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

1
感谢您的评论,我喜欢您将所有字符组合在一起的方式,我打算用一种不太正规的表达方式来替换所有字符...我现在不确定如何分配此问题的答案。最终,PreparedStatement是答案,但就我当前的目标而言,您的答案就是我需要的答案,如果我将答案提供给较早的准备语句的答案之一,您是否会感到沮丧,还是有办法在一对夫妇之间共享答案?
Scott Bonner

1
由于这只是暂时的麻烦,因此请继续接受PreparedStatement答案之一。
艾伦·摩尔


10

准备语句是最好的解决方案,但是如果您确实需要手动执行它,则也可以使用StringEscapeUtilsApache Commons-Lang库中的类。它具有一种escapeSql(String)可以使用的方法:

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


2
供参考:commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/…无论如何,此方法仅转义引号,并且似乎无法防止SQL Injection攻击。
Paco Abato

11
这是从最新版本删除,因为它只是逃避单引号
皮尼Cheyni

2
应删除此答案,因为它不会阻止sql注入。
爪哇河

9

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

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

欲了解更多信息,使用预处理语句Java教程将是一个良好的开端。


6

如果您正在使用旧系统,或者有太多地方可以PreparedStatement在很短的时间内切换到s-例如,如果在使用其他答案建议的最佳实践方面遇到障碍,则可以尝试AntiSQLFilter


5

您需要以下代码。乍一看,这看起来像我编写的任何旧代码。但是,我所做的只是查看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转义的字符列表,那么知道这确实在安全方面令人放心。在下一个主要的Java版本中,Oracle可能需要轻推才能添加与该方法类似的方法。

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

2
我认为这段代码是上面链接中源代码的反编译版本。现在,在较新的版本中mysql-connector-java-xxxcase '\u00a5'and case '\u20a9'语句似乎已被删除
zhy 2016年

我用您的代码尝试了sqlmap,但它并没有保护我免受首次攻击`类型:基于布尔的盲注标题:和基于布尔的盲注-WHERE或HAVING子句有效负载:q = 1%'AND 5430 = 5430 AND'%' ='`
shareef

对不起它的工作,但被观看最后存储的会话结果..我一直在为今后类似的..评论
谢里夫

您可以使用org.ostermiller.utils.StringHelper.escapeSQL()com.aoindustries.sql.SQLUtility.escapeSQL()
Mohamed Ennahdi El Idrissi

1
重要的是,请注意复制原始代码的GPLv2许可证,以供遇到此问题的任何人使用。我不是律师,但我强烈建议您不要在项目中使用此答案,除非您完全意识到包括此许可代码的含义。
尼克·史派克

0

在搜索了很多解决方案以防止sqlmap从sql注入后,如果遗留系统无法在任何地方应用准备好的语句。

Java的安全跨站点脚本-XSS-和SQL注入的主题 是解决办法

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

该过滤器的目标是将请求包装到自己编码的包装器MyHttpRequestWrapper中,该包装器将转换:

通过org.springframework.web.util.HtmlUtils.htmlEscape(…)方法将带有特殊字符(<,>,',...)的HTTP参数转换为HTML代码。注意:Apache Commons中有类似的类:org.apache.commons.lang.StringEscapeUtils.escapeHtml(…)通过Apache Commons类org.apache.commons.lang.StringEscapeUtils的SQL注入字符(“,”,…)。 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;
    }
}

是不是好java-security-cross-site-scripting-xss-and-sql-injection topic?我正在尝试为旧版应用程序找到解决方案。
CAOT

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;

}


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.