Java - escape-строка для предотвращения внедрения SQL


146

Я пытаюсь внедрить некоторые анти-SQL инъекции в Java, и мне очень трудно работать со строковой функцией replaceAll. В конечном счете мне нужна функция, которая преобразует любые существующие \в \\, любые "в \", любые 'в \'и любые \nв \\nтак, чтобы при оценке строки с помощью SQL-инъекций MySQL блокировалась.

Я испортил некоторый код, с которым работал, и все \\\\\\\\\\\в функции заставляют мои глаза сходить с ума. Если у кого-то есть пример этого, я был бы очень признателен.


1
Хорошо, я пришел к выводу, что PreparedStatements - это путь, однако, исходя из текущих целей, мне нужно действовать так, как было запланировано изначально, и просто установить фильтр на некоторое время, и как только текущий этап достигнут, я могу вернуться и реорганизовать базу данных для подготовленного заявления. В то же время, чтобы поддержать импульс, есть ли у кого-нибудь решение, позволяющее эффективно экранировать вышеперечисленные символы для MySQL, учитывая Java, и его система регулярных выражений - абсолютная боль в определении необходимого количества экранирований ...
— Скотт Боннер,

2
Не все операторы SQL являются параметризуемыми, например «SET ROLE role_name» или «LISTEN channel_name»
— Нил Макгиган

1
@NeilMcGuigan Да. Большинство драйверов также отказываются от параметризации чего-то подобного, CREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?так как основной оператор - это DDL- утверждение, хотя часть, которую вы пытаетесь параметризовать, на самом деле является DML .
— SeldomNeedy

Ответы:


249

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 перечислены все различные методы, доступные для установки и получения данных.


1
С помощью этого метода вы можете рассматривать каждый параметр как строку и при этом быть в безопасности? Я пытаюсь найти способ обновить мою существующую архитектуру, чтобы она была безопасной, без необходимости перестраивать весь слой базы данных ...
— Скотт Боннер,

1
Весь dynqmic SQL - это просто строки, так что вопрос не в этом. Я не знаком с PrepareStatement, поэтому реальный вопрос в том, генерирует ли он параметризованный запрос, который затем может быть выполнен с помощью ExecuteUpdate. Если да, это хорошо. Если нет, то это просто скрывает проблему, и у вас может не быть никакой безопасной опции, кроме как переделать слой базы данных. Работа с SQL-инъекцией - это одна из тех вещей, которую вы должны разрабатывать с самого начала; это не то, что вы можете легко добавить позже.
— Кот-Сайлон

2
Если вы вставляете в поле INTEGER, вы хотите использовать 'setInt'. Аналогично, другие числовые поля базы данных будут использовать другие сеттеры. Я разместил ссылку на документы PreparedStatement, в которых перечислены все типы сеттеров.
— Калеб Браси

2
Да, Сайлон, PreparedStatements генерирует параметризованные запросы.
— Калеб Бразе

2
@ Kaleb Brasee, спасибо. Это хорошо знать. Инструменты различны в каждой среде, но основополагающий ответ - переход к параметризованным запросам.
— Кот Сайлон

46

Единственный способ предотвратить внедрение SQL - это параметризованный SQL. Просто невозможно создать фильтр, который умнее людей, которые взламывают SQL на жизнь.

Поэтому используйте параметры для всех входных данных, обновлений и предложений where. Динамический SQL является просто открытой дверью для хакеров, и это включает динамический SQL в хранимых процедурах. Параметризация, параметризация, параметризация.


11
И даже параметризованный SQL не является гарантией 100%. Но это очень хорошее начало.
— duffymo

2
@ Duffymo, я согласен, что ничто не может быть на 100% безопасным. У вас есть пример внедрения SQL, который будет работать даже с параметризованным SQL?
— Кот Сайлон

3
@Cylon Cat: Конечно, когда кусок SQL (например, @WhereClause или @tableName) передается в качестве параметра, объединяется в SQL и выполняется динамически. SQL-инъекция происходит, когда вы позволяете пользователям писать ваш код. Неважно, захватываете ли вы их код в качестве параметра или нет.
— Стив Касс

16
Кстати, я не знаю, почему это не упоминается больше, но работа с PreparedStatements также намного проще и намного более читабельна. Одно это, вероятно, делает их по умолчанию для каждого программиста, который знает о них.
— Эдан Маор

3
Обратите внимание, что PreparedStatements для некоторых баз данных ОЧЕНЬ дорого создавать, поэтому, если вам нужно их много, измерьте оба типа.
— Торбьерн Равн Андерсен

36

Если вы действительно не можете использовать опцию защиты 1: подготовленные операторы (параметризованные запросы) или вариант защиты 2: хранимые процедуры , не создавайте свой собственный инструмент, используйте OWASP Enterprise Security API . Из OWASP ESAPI, размещенного в Google Code:

Не пишите свои собственные меры безопасности! Изобретая колесо, когда речь заходит о разработке мер безопасности для каждого веб-приложения или веб-службы, это приводит к потере времени и огромным дырам в безопасности. Наборы инструментов OWASP Enterprise Security API (ESAPI) помогают разработчикам программного обеспечения защититься от недостатков проектирования и реализации, связанных с безопасностью.

Дополнительные сведения см. В разделах «Предотвращение внедрения SQL в Java» и « Шпаргалка по предотвращению внедрения SQL» .

Обратите особое внимание на вариант защиты 3: экранирование всех вводимых пользователем данных , представляющих проект OWASP ESAPI ).


4
ESAPI кажется несуществующим на сегодняшний день. На AWS есть WAF, который может помочь против внедрения SQL, XSS и т. Д. Есть ли другие альтернативы на этом этапе?
— ChrisOdney

@ChrisOdney WAF можно легко обойти. Большинство Frameworks уже реализуют свои собственные средства предотвращения SQL-инъекций, в которых они автоматически экранируют параметры. Альтернативы для устаревших проектов: owasp.org/index.php/…
— Джаван Р.

19

(Это ответ на комментарий ОП под первоначальным вопросом; я полностью согласен с тем, что PreparedStatement является инструментом для этой работы, а не регулярными выражениями.)

Когда вы говорите \n, вы имеете в виду последовательность \+ nили фактический символ перевода строки? Если это \+ n, задача довольно проста:

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

Чтобы сопоставить одну обратную косую черту во вводе, вы помещаете четыре из них в строку регулярного выражения. Чтобы поместить один обратный слеш в вывод, вы помещаете четыре из них в строку замены. Это предполагает, что вы создаете регулярные выражения и замены в форме литералов Java String. Если вы создаете их любым другим способом (например, читая их из файла), вам не нужно делать все это двойным экранированием.

Если у вас есть символ перевода строки на входе, и вы хотите заменить его на escape-последовательность, вы можете сделать второй проход по вводу с помощью этого:

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

Или, может быть, вы хотите две обратные косые черты (я не слишком ясно):

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

1
Спасибо за комментарий, мне нравится, как вы сделали все символы в одном, я собирался использовать способ с регулярным выражением замены всех для каждого ... Я не уверен, как назначить ответ на этот вопрос сейчас , В конечном счете, PreparedStatements - это ответ, но для моей текущей цели ваш ответ - это тот ответ, который мне нужен, вы были бы расстроены, если бы я дал ответ на один из ранее подготовленных ответов, или есть способ поделиться ответом между парой?
— Скотт Боннер

1
Так как это всего лишь временное препятствие, продолжайте и примите один из ответов PreparedStatement.
— Алан Мур

12

PreparedStatements - это путь в большинстве, но не во всех случаях. Иногда вы окажетесь в ситуации, когда запрос или его часть должны быть построены и сохранены в виде строки для последующего использования. Ознакомьтесь с инструкциями по предотвращению инъекций SQL на сайте OWASP для получения более подробной информации и API на разных языках программирования.


1
Шпаргалки OWASP были перенесены на GitHub.
— theferrit32

10

Подготовленные операторы являются лучшим решением, но если вам действительно нужно сделать это вручную, вы также можете использовать StringEscapeUtilsкласс из библиотеки Apache 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-инъекций.
— Пако Абато

11
Это было удалено из последних версий, потому что это было только избежать одиночных кавычек
— Пини Чейни

2
Этот ответ должен быть удален, потому что он не препятствует внедрению SQL.
— Джаван Р.

9

Использование регулярного выражения для удаления текста, который может вызвать внедрение SQL, звучит так, как будто оператор SQL отправляется в базу данных через, Statementа не через PreparedStatement.

Одним из самых простых способов предотвратить внедрение SQL в первую очередь является использование a PreparedStatement, который принимает данные для замены в оператор SQL с использованием заполнителей, который не использует конкатенации строк для создания оператора SQL для отправки в базу данных.

Для получения дополнительной информации было бы неплохо начать с использования подготовленных операторов из учебников по Java .


6

Если вы имеете дело с унаследованной системой или у вас слишком много мест, чтобы переключиться на PreparedStatements за слишком короткое время - то есть, если есть препятствие для использования передовой практики, предложенной другими ответами, вы можете попробовать 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 избегает, то знание этого действительно удобно с точки зрения безопасности. Может быть, 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
Я думаю, что этот код является декомпилированной версией исходного кода по ссылке выше. Теперь в новом mysql-connector-java-xxx, то case '\u00a5'и case '\u20a9'заявлении , кажется , вывезенный
— жа

я попробовал sqlmap с вашим кодом, и он не защитил меня от первой атаки. `Тип: булевский блайнд Название: И булевский блайнд - предложение WHERE или HAVING Полезная нагрузка: q = 1% 'И 5430 = 5430 И'% ' = '`
— Шариф

Извините, это работает, но просматривал последние сохраненные результаты сеанса .. я сохранил комментарий для будущего аналогичного ..
— shareef

Вы можете использовать org.ostermiller.utils.StringHelper.escapeSQL()или com.aoindustries.sql.SQLUtility.escapeSQL().
— Мохамед Эннахди Эль Идрисси

1
Важно отметить лицензию GPLv2 на исходный код, с которого она была скопирована для всех, кто сталкивался с этим. Я не юрист, но я настоятельно рекомендую не использовать этот ответ в своем проекте, если вы не полностью осведомлены о последствиях включения этого лицензионного кода.
— Ник Спейсек

0

После поиска тестируемого решения для предотвращения sqlmap от внедрения sql, в случае устаревшей системы, которая не может применять подготовленные отчеты везде.

Java-безопасность межсайтовых-сценарии-XSS-и-SQL-инъекция тема была решением

Я попробовал решение @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;
    }
}

Это хорошо java-security-cross-site-scripting-xss-and-sql-injection topic? Я пытаюсь найти решение для устаревшего приложения.
— около

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;

}


0

Если вы используете PL / SQL, вы также можете использовать DBMS_ASSERT его для очистки вашего ввода, чтобы вы могли использовать его, не беспокоясь о SQL-инъекциях.

см. этот ответ, например: https://stackoverflow.com/a/21406499/1726419

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.