Java - escape-строка для предотвращения внедрения SQL
Я пытаюсь внедрить некоторые анти-SQL инъекции в Java, и мне очень трудно работать со строковой функцией replaceAll. В конечном итоге мне нужна функция, которая преобразует любые существующие \
в \\
, любой "
в \"
, любой '
в \'
и любой \n
в \\n
так что когда строка оценивается MySQL, SQL-инъекции будут заблокированы.
Я поднял некоторый код, с которым я работал, и все \\\\\\\\\\\
в функции заставляют мои глаза сходить с ума. Если у кого-то есть пример этого, я был бы очень признателен.
16 ответов
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
метод. В документации PreparedStatement перечислены все различные методы, доступные для установки и получения данных.
Единственный способ предотвратить внедрение SQL - это параметризованный SQL. Просто невозможно создать более умный фильтр, чем люди, которые взламывают SQL для жизни.
Поэтому используйте параметры для всех входных данных, обновлений и предложений where. Динамический SQL является просто открытой дверью для хакеров, и это включает динамический SQL в хранимых процедурах. Параметризация, параметризация, параметризация.
Если вы действительно не можете использовать вариант защиты 1: подготовленные операторы (параметризованные запросы) или вариант защиты 2: хранимые процедуры, не создавайте свой собственный инструмент, используйте OWASP Enterprise Security API. Из OWASP ESAPI, размещенного в Google Code:
Не пишите свои собственные меры безопасности! Изобретая колесо, когда речь заходит о разработке мер безопасности для каждого веб-приложения или веб-службы, это приводит к потере времени и огромным дырам в безопасности. Наборы инструментов OWASP Enterprise Security API (ESAPI) помогают разработчикам программного обеспечения защититься от недостатков проектирования и реализации, связанных с безопасностью.
Дополнительные сведения см. В разделах "Предотвращение внедрения SQL в Java" и " Шпаргалке по предотвращению внедрения SQL".
Обратите особое внимание на вариант защиты 3: экранирование всех вводимых пользователем данных, представляющих проект OWASP ESAPI).
(Это ответ на комментарий ОП под первоначальным вопросом; я полностью согласен с тем, что PreparedStatement является инструментом для этой работы, а не регулярными выражениями.)
Когда ты сказал \n
Вы имеете в виду последовательность \
+n
или фактический символ перевода строки? Если это \
+n
Задача довольно проста:
s = s.replaceAll("['\"\\\\]", "\\\\$0");
Чтобы сопоставить одну обратную косую черту во входных данных, вы помещаете четыре из них в строку регулярного выражения. Чтобы поместить один обратный слеш в вывод, вы помещаете четыре из них в строку замены. Предполагается, что вы создаете регулярные выражения и замены в форме литералов Java String. Если вы создаете их любым другим способом (например, читая их из файла), вам не нужно делать все это двойным экранированием.
Если у вас есть символ перевода строки на входе, и вы хотите заменить его на escape-последовательность, вы можете сделать второй проход по вводу с помощью этого:
s = s.replaceAll("\n", "\\\\n");
Или, может быть, вы хотите две обратные косые черты (я не слишком ясно):
s = s.replaceAll("\n", "\\\\\\\\n");
PreparedStatements - это подход в большинстве, но не во всех случаях. Иногда вы попадаете в ситуацию, когда запрос или его часть необходимо построить и сохранить в виде строки для последующего использования. Ознакомьтесь с инструкциями по предотвращению инъекций SQL на сайте OWASP для получения более подробной информации и API на разных языках программирования.
Подготовленные заявления являются лучшим решением, но если вам действительно нужно сделать это вручную, вы также можете использовать StringEscapeUtils
класс из библиотеки Apache Commons-Lang. Имеет escapeSql(String)
Метод, который вы можете использовать:
import org.apache.commons.lang.StringEscapeUtils;
…
String escapedSQL = StringEscapeUtils.escapeSql(unescapedSQL);
Использование регулярного выражения для удаления текста, который может вызвать внедрение SQL, звучит так, как будто оператор SQL отправляется в базу данных через Statement
а не PreparedStatement
,
Один из самых простых способов предотвратить инъекцию SQL - это использовать PreparedStatement
, который принимает данные для замены в оператор SQL с использованием заполнителей, который не использует конкатенации строк для создания оператора SQL для отправки в базу данных.
Для получения дополнительной информации, использование Подготовленных Утверждений из Учебников Java было бы хорошим началом.
Вам нужен следующий код ниже. На первый взгляд, это может выглядеть как любой старый код, который я составил. Однако я посмотрел на исходный код 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();
}
}
Если вы имеете дело с устаревшей системой или у вас слишком много мест, чтобы переключиться на PreparedStatement
в слишком короткое время - то есть, если есть препятствия для использования лучших практик, предложенных другими ответами, вы можете попробовать AntiSQLFilter
Большинство людей рекомендуют
PreparedStatements
, однако для этого требуется, чтобы у вас было прямое соединение с вашей базой данных с помощью приложения Java. Но тогда все остальные скажут, что у вас не должно быть прямого подключения к своей базе данных из-за проблем с безопасностью, а использовать Restful API для обработки запросов.
На мой взгляд, пока вы понимаете, что должны быть осторожны с тем, от чего избегаете, и делаете это намеренно, проблем быть не должно.
Мое решение использует
contains()
чтобы проверить ключевые слова SQL, такие как
UPDATE
или другие опасные персонажи, такие как
=
чтобы полностью обнулить SQL-инъекцию, попросив пользователя вставить во ввод другие символы.
Изменить: вы можете использовать этот исходный материал из W3Schools о регулярных выражениях Java, чтобы выполнить эту проверку на строках.
From: [Source]
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;
}
После поиска тестирования много решений для предотвращения sqlmap от внедрения sql, в случае устаревшей системы, которая не может применять подготовленные отчеты везде.
http://www.javablog.fr/java-security-cross-site-scripting-xss-and-sql-injection.html решением
Я попробовал решение @Richard s, но в моем случае это не сработало. я использовал фильтр
Цель этого фильтра - обернуть запрос в закодированную оболочку 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;
}
}
Думаю escapeSql
из StringEscapeUtils может сделать эту работу. Тем не менее, чтобы попробовать это, но выглядит как заслуживающий доверия вариант.
Если вы используете PL/SQL, вы также можете использовать DBMS_ASSERT
он может дезинфицировать ваш ввод, чтобы вы могли использовать его, не беспокоясь о SQL-инъекциях.
см. этот ответ, например: /questions/32364493/blok-sql-inektsii-oracle-s-dbmsassert/32364506#32364506
Вы можете попробовать очистить параметры (не 1-й вариант)
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) + "' ...;
Сначала задайте вопрос - нужны ли двойные или одинарные кавычки или обратная косая черта в полях ввода пользователя?
Обратные косые черты - нет. Двойные и одинарные кавычки редко используются в английском языке, а в Великобритании они используются иначе, чем в США.
Я говорю: удалите или замените их, и вы упростите.
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;
}