提问人:Scott Bonner 提问时间:11/29/2009 最后编辑:Tim PietzckerScott Bonner 更新时间:10/19/2022 访问量:322697
Java - 转义字符串以防止 SQL 注入
Java - escape string to prevent SQL injection
问:
我正在尝试在 java 中放置一些反 sql 注入,但发现使用“replaceAll”字符串函数非常困难。最终,我需要一个函数,它将任何现有转换为 、 any 到 、 any 到 和 any 到,以便当 MySQL 评估字符串时,SQL 注入将被阻止。\
\\
"
\"
'
\'
\n
\\n
我已经掌握了一些我正在使用的代码,函数中的所有代码都让我的眼睛发疯了。如果有人碰巧有这方面的例子,我将不胜感激。\\\\\\\\\\\
答:
防止 SQL 注入的唯一方法是使用参数化 SQL。根本不可能构建一个比那些以 SQL 为生的人更聪明的过滤器。
因此,对所有 input、updates 和 where 子句使用参数。动态 SQL 只是为黑客敞开的大门,其中包括存储过程中的动态 SQL。参数化,参数化,参数化。
评论
使用正则表达式删除可能导致 SQL 注入的文本听起来像是 SQL 语句通过 Statement
而不是 PreparedStatement
发送到数据库。
首先,防止 SQL 注入的最简单方法之一是使用 ,它接受数据以使用占位符替换到 SQL 语句中,它不依赖于字符串连接来创建要发送到数据库的 SQL 语句。PreparedStatement
有关更多信息,请使用 Java 教程中的预准备语句将是一个很好的起点。
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:预准备语句(参数化查询)或防御选项 2:存储过程,请不要构建自己的工具,请使用 OWASP Enterprise Security API。从 Google Code 上托管的 OWASP ESAPI:
不要编写自己的安全控件!在为每个 Web 应用程序或 Web 服务开发安全控制时,重新发明轮子会导致浪费时间和巨大的安全漏洞。OWASP 企业安全 API (ESAPI) 工具包可帮助软件开发人员防范与安全相关的设计和实现缺陷。
有关详细信息,请参阅在 Java 中防止 SQL 注入和 SQL 注入防护备忘单。
请特别注意介绍OWASP ESAPI项目的防御选项3:转义所有用户提供的输入。
评论
如果你正在处理一个遗留系统,或者你有太多的地方需要在太短的时间内切换到 s - 也就是说,如果使用其他答案建议的最佳实践有障碍,你可以尝试 AntiSQLFilterPreparedStatement
(这是对OP在原始问题下的评论的回答;我完全同意 PreparedStatement 是这项工作的工具,而不是正则表达式。
当你说 时,你指的是序列 + 还是实际的换行符?如果是 +,则任务非常简单:\n
\
n
\
n
s = s.replaceAll("['\"\\\\]", "\\\\$0");
若要匹配输入中的一个反斜杠,请将其中四个反斜杠放在正则表达式字符串中。若要在输出中放置一个反斜杠,请将其中四个放在替换字符串中。这是假设您正在以 Java String 字面量的形式创建正则表达式和替换。如果您以任何其他方式创建它们(例如,通过从文件中读取它们),则不必执行所有这些双重转义。
如果输入中有一个换行符,并且想要将其替换为转义序列,则可以使用以下命令对输入进行第二次传递:
s = s.replaceAll("\n", "\\\\n");
或者,也许你想要两个反斜杠(我不太清楚):
s = s.replaceAll("\n", "\\\\\\\\n");
评论
在大多数情况下,PreparedStatements 是要走的路,但不是所有情况。有时,您会发现自己处于必须构建查询或查询的一部分并将其存储为字符串以供以后使用的情况。查看 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 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();
}
}
评论
mysql-connector-java-xxx
case '\u00a5'
case '\u20a9'
org.ostermiller.utils.StringHelper.escapeSQL()
com.aoindustries.sql.SQLUtility.escapeSQL()
预准备语句是最好的解决方案,但如果你真的需要手动完成,你也可以使用 Apache Commons-Lang 库中的 StringEscapeUtils
类。它有一个 escapeSql(String)
方法,您可以使用它:
import org.apache.commons.lang.StringEscapeUtils;
…
String escapedSQL = StringEscapeUtils.escapeSql(unescapedSQL);
评论
在搜索了大量测试解决方案以防止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;
}
}
评论
java-security-cross-site-scripting-xss-and-sql-injection topic
来源:来源
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;
}
评论
null
,因此您可能会得到意外的结果:将显示项目 where 而不是 !"SELECT ITEM.id FROM ITEM WHERE ITEM.value = '" + MysqlRealScapeString(text) + "'"
ITEM.value = 'null'
ITEM.value = ''
如果您使用的是 PL/SQL,您也可以使用它来清理您的输入,这样您就可以使用它而不必担心 SQL 注入。DBMS_ASSERT
例如,请参阅此答案: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;
}
评论
大多数人都建议,但是这需要您使用Java应用程序与数据库建立直接连接。但是,其他人都会说,由于安全问题,您不应该直接连接到数据库,而是使用 Restful API 来处理查询。PreparedStatements
在我看来,只要你知道你必须小心你逃跑的东西,并故意去做,应该没有问题。
我的解决方案是用于检查SQL关键字,例如或其他危险字符,例如通过要求用户在输入中插入其他字符来完全取消SQL注入。contains()
UPDATE
=
编辑:您可以使用 W3Schools 中有关 Java 正则表达式的源材料对字符串进行此验证。
您可以尝试清理参数(不是第一个选项)
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) + "' ...;
评论
CREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?