阻止 SQL 注入的 Java 转义字符串

我试图把一些反 sql 注入到 java 中,我发现使用“ replaceAll”字符串函数非常困难。最终,我需要一个函数将任何现有的 \转换为 \\,任何 "转换为 \",任何 '转换为 \',任何 \n转换为 \\n,这样当字符串被 MySQL 注入时将被阻塞。

我已经提升了一些代码,我正在工作和所有的 \\\\\\\\\\\在函数中让我的眼睛疯了。如果有人碰巧有这方面的例子,我将非常感激。

305235 次浏览

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

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

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

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

要了解更多信息,可以从 Java 教程使用已准备语句开始。

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 语句。

对于不同的数据类型,有不同的 set 方法——您使用哪种方法取决于您的数据库字段是什么。例如,如果数据库中有一个 INTEGER 列,则应使用 setInt方法。PreparedStatement 文档列出了所有可用于设置和获取数据的不同方法。

如果你真的不能使用 防御选项1: 准备好的语句(参数化查询)答辩备选案文2: 存储程序,不要建立自己的工具,使用 企业安全 API。来自谷歌代码托管的 OWASP ESAPI:

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

有关详细信息,请参阅 在 Java 中防止 SQL 注入防止 SQL 注入作弊表

特别注意介绍 OWASP ESAPI项目的 防御方案3: 逃避所有用户提供的输入)。

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

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

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

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

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

如果你在输入中有一个 linefeed 字符,并且你想用一个转义序列来替换它,你可以使用以下方法在输入中进行第二次传递:

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

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

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

在大多数情况下,但不是所有情况下,准备陈述都是可行的方法。有时候,您会发现自己处于这样一种情况,即必须将查询或查询的一部分构建并存储为字符串以供以后使用。查看 OWASP 网站上的 防止 SQL 注入作弊表以了解不同编程语言的更多细节和 API。

您需要以下代码。乍一看,这可能看起来像我编写的任何旧代码。然而,我所做的是查看 http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.31/com/mysql/jdbc/PreparedStatement.java的源代码。然后,我仔细地查看 setString (int Parameter terIndex,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();
}
}

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

Import org.apache.commons.lang. StringEscapeUtils; ... 字符串 escape edSQL = StringEscapeUtils.escape eSql (unescape edSQL) ;

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

Java-security-cross-site-scripting-xss-and-sql- 注入主题 就是解决办法

我尝试了理查德的解决方案,但在我的情况下不起作用。 我用了过滤器

这个过滤器的目标是将请求包装到一个自己编码的 包装器 MyHttpRequestWrapper 转换:

将带有特殊字符(< 、 > 、‘、 ...)的 HTTP 参数转换为 HTML 通过 org.springframework.web.util. HtmlUtils.htmlEscape (...)编写代码 注意: Apache Commons 中也有类似的类: Lang. StringEscapeUtils.escape eHtml (...)的 SQL 通过 Apache Commons 类注入字符(‘ ,“ ,...) Lang. StringEscapeUtils.escape eSql (...)

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

发信人: 来源

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

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

看看这个答案: Https://stackoverflow.com/a/21406499/1726419

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

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

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

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

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

在我看来,只要你意识到你必须小心你逃避和故意这样做,应该没有问题。

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

编辑: 您可以使用来自 W3Schools 的关于 Java 正则表达式的源材料对 String 进行验证。

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

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) + "' ...;