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

Java - escape string to prevent SQL injection

提问人:Scott Bonner 提问时间:11/29/2009 最后编辑:Tim PietzckerScott Bonner 更新时间:10/19/2022 访问量:322697

问:

我正在尝试在 java 中放置一些反 sql 注入,但发现使用“replaceAll”字符串函数非常困难。最终,我需要一个函数,它将任何现有转换为 、 any 到 、 any 到 和 any 到,以便当 MySQL 评估字符串时,SQL 注入将被阻止。\\\"\"'\'\n\\n

我已经掌握了一些我正在使用的代码,函数中的所有代码都让我的眼睛发疯了。如果有人碰巧有这方面的例子,我将不胜感激。\\\\\\\\\\\

Java 正则表达式 转义 SQL 注入

评论

2赞 Scott Bonner 12/1/2009
好的,我得出的结论是 PreparedStatements 是要走的路,但是根据当前的目标,我需要按照最初的计划进行,并暂时放置一个过滤器,一旦达到当前的里程碑,我就可以返回并重构 preparedstatement 的数据库。同时,为了保持势头,是否有人有一种解决方案可以有效地转义MySQL的上述字符,因为Java及其正则表达式系统绝对是计算出所需的转义次数。
2赞 Neil McGuigan 1/3/2016
并非所有 SQL 语句都是可参数化的,例如“SET ROLE role_name”或“LISTEN channel_name”
1赞 Seldom 'Where's Monica' Needy 2/23/2017
@NeilMcGuigan 是的。大多数驱动程序也会拒绝参数化,因为主语句是 DDL 语句,即使您尝试参数化的部分实际上是 DMLCREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?

答:

51赞 Cylon Cat 11/29/2009 #1

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

因此,对所有 input、updates 和 where 子句使用参数。动态 SQL 只是为黑客敞开的大门,其中包括存储过程中的动态 SQL。参数化,参数化,参数化。

评论

11赞 duffymo 11/29/2009
即使是参数化的 SQL 也不能 100% 保证。但这是一个非常好的开始。
2赞 Cylon Cat 11/29/2009
@duffymo,我同意没有什么是 100% 安全的。您是否有一个即使使用参数化 SQL 也能正常工作的 SQL 注入示例?
4赞 Steve Kass 11/29/2009
@Cylon Cat:当然,当 SQL 块(如 @WhereClause 或 @tableName)作为参数传递、连接到 SQL 中并动态执行时。当您允许用户编写代码时,会发生 SQL 注入。是否将其代码捕获为参数并不重要。
16赞 Edan Maor 11/29/2009
顺便说一句,我不知道为什么没有多提这一点,但使用 PreparedStatements 也更容易且更具可读性。仅此一项就可能使它们成为每个了解它们的程序员的默认设置。
4赞 Thorbjørn Ravn Andersen 11/29/2009
请注意,某些数据库的 PreparedStatements 的创建成本非常高,因此如果您需要执行大量操作,请测量这两种类型。
9赞 coobird 11/29/2009 #2

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

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

有关更多信息,请使用 Java 教程中的预准备语句将是一个很好的起点。

270赞 Kaleb Brasee 11/29/2009 #3

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 列,则应使用方法。PreparedStatement 文档列出了可用于设置和获取数据的所有不同方法。setInt

评论

1赞 Scott Bonner 11/29/2009
通过这种方法,您可以将每个参数视为一个字符串并且仍然安全吗?我正试图找到一种方法来更新我现有的架构以确保安全,而无需重建整个数据库层......
1赞 Cylon Cat 11/29/2009
所有 dynqmic SQL 都只是字符串,所以这不是要问的问题。我不熟悉 PrepareStatement,所以真正的问题是它是否生成了一个参数化查询,然后可以使用 ExecuteUpdate 执行。如果是,那就好了。如果不是,那么它只是隐藏了问题,除了重新设计数据库层之外,您可能没有任何安全选项。处理 SQL 注入是您必须从一开始就设计的事情之一;这不是您以后可以轻松添加的内容。
2赞 Kaleb Brasee 11/29/2009
如果要插入 INTEGER 字段,则需要使用“setInt”。同样,其他数值数据库字段将使用其他 setter。我发布了一个指向 PreparedStatement 文档的链接,其中列出了所有 setter 类型。
2赞 Kaleb Brasee 11/29/2009
是的,Cylon、PreparedStatements 会生成参数化查询。
2赞 Cylon Cat 11/29/2009
@Kaleb Brasee,谢谢。很高兴知道。这些工具在每个环境中都不同,但深入到参数化查询是基本答案。
43赞 Pascal Thivent 11/29/2009 #4

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

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

有关详细信息,请参阅在 Java 中防止 SQL 注入和 SQL 注入防护备忘单

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

评论

4赞 ChrisOdney 5/31/2017
截至今天,ESAPI似乎已经不复存在。在AWS上,有WAF可以帮助对抗SQL注入,XSS等。
0赞 Javan R. 2/25/2019
@ChrisOdney 可以轻松绕过 WAF。大多数框架已经实现了自己的 SQL 注入防护,其中它们会自动转义参数。遗留项目的替代方案:owasp.org/index.php/...
6赞 Bozho 11/29/2009 #5

如果你正在处理一个遗留系统,或者你有太多的地方需要在太短的时间内切换到 s - 也就是说,如果使用其他答案建议的最佳实践有障碍,你可以尝试 AntiSQLFilterPreparedStatement

24赞 Alan Moore 12/1/2009 #6

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

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

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

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

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

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

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

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

评论

1赞 Scott Bonner 12/2/2009
感谢您的评论,我喜欢您将所有字符合二为一的方式,我正在以不太正则的表达方式替换每个字符......我现在不确定如何回答这个问题。归根结底,PreparedStatements是答案,但对于我目前的目标,你的答案是我需要的答案,如果我给出了之前准备好的陈述答案之一的答案,你会感到不安吗,或者有没有办法在一对夫妇之间分享答案?
1赞 Alan Moore 12/3/2009
由于这只是一个暂时的混乱,请继续接受 PreparedStatement 答案之一。
14赞 user566057 1/11/2011 #7

在大多数情况下,PreparedStatements 是要走的路,但不是所有情况。有时,您会发现自己处于必须构建查询或查询的一部分并将其存储为字符串以供以后使用的情况。查看 OWASP 网站上SQL 注入预防备忘单,了解更多详细信息和不同编程语言的 API。

评论

3赞 theferrit32 7/16/2019
OWASP 备忘单已移至 GitHub。SQL注入备忘单现在在这里:github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/...
7赞 Richard 1/7/2016 #8

您需要以下代码。乍一看,这可能看起来像我编造的任何旧代码。但是,我所做的是查看 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();
    }
}

评论

2赞 zhy 4/18/2016
我认为这段代码是上面链接中源代码的反编译版本。现在在较新的 中,和语句似乎已被删除mysql-connector-java-xxxcase '\u00a5'case '\u20a9'
0赞 shareef 9/9/2016
我用你的代码尝试了sqlmap,但它并没有保护我免受第一次攻击' 类型:基于布尔的盲人 标题:AND 基于布尔的盲人 - WHERE 或 HAVING 子句 有效载荷:q=1%' 和 5430=5430 和 '%'=''
0赞 shareef 9/9/2016
对不起,它的工作,但正在查看上次存储的会话结果。我保留了评论以备将来类似.
0赞 Mohamed Ennahdi El Idrissi 10/5/2016
您可以使用 或 .org.ostermiller.utils.StringHelper.escapeSQL()com.aoindustries.sql.SQLUtility.escapeSQL()
1赞 Nick Spacek 4/18/2017
需要注意的是原始代码上的 GPLv2 许可证,这是为遇到此问题的任何人复制的。我不是律师,但我强烈建议不要在您的项目中使用此答案,除非您完全了解包含此许可代码的含义。
11赞 ToBe_HH 7/14/2016 #9

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

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

评论

4赞 Paco Abato 5/3/2017
供参考:commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/...无论如何,这种方法只能转义引号,似乎并不能防止SQL注入攻击。
16赞 Pini Cheyni 5/20/2017
这已从最新版本中删除,因为它仅转义单引号
12赞 Javan R. 2/25/2019
应该删除此答案,因为它不会阻止 sql 注入。
0赞 shareef 9/12/2016 #10

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

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

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

此筛选器的目标是将请求包装到自己编码的 包装器 MyHttpRequestWrapper,它转换:

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

评论

0赞 caot 6/12/2020
好吗?我正在尝试为遗留应用程序找到解决方案。java-security-cross-site-scripting-xss-and-sql-injection topic
1赞 Billcountry 5/17/2017 #11

来源:来源

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

评论

1赞 izogfif 12/11/2020
警告:它为空字符串返回 null,因此您可能会得到意外的结果:将显示项目 where 而不是 !"SELECT ITEM.id FROM ITEM WHERE ITEM.value = '" + MysqlRealScapeString(text) + "'"ITEM.value = 'null'ITEM.value = ''
0赞 yossico 6/2/2020 #12

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

例如,请参阅此答案:https://stackoverflow.com/a/21406499/1726419

-2赞 DavidCoder 3/3/2021 #13

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

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

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

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

评论

2赞 tgdavies 3/3/2021
建议不保留输入的转换是一个坏主意。
2赞 Your Common Sense 3/3/2021
试想一下,如果在 Stack Overflow 上使用这种方法,您的答案会是什么样子。
1赞 claudiodfc 5/28/2021 #14

大多数人都建议,但是这需要您使用Java应用程序与数据库建立直接连接。但是,其他人都会说,由于安全问题,您不应该直接连接到数据库,而是使用 Restful API 来处理查询。PreparedStatements

在我看来,只要你知道你必须小心你逃跑的东西,并故意去做,应该没有问题。

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

编辑:您可以使用 W3Schools 中有关 Java 正则表达式的源材料对字符串进行此验证。

0赞 Condemateguadua 10/19/2022 #15

您可以尝试清理参数(不是第一个选项)

Codec ORACLE_CODEC = new OracleCodec();
String user = req.getParameter("user");
String query = "SELECT user_id FROM user_data WHERE user_name = '" + 
     ESAPI.encoder().encodeForSQL( ORACLE_CODEC, user) + "' ...;