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

ToBe_HH
источник
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 .

coobird
источник
6

Если вы имеете дело с унаследованной системой или у вас слишком много мест, чтобы переключиться на PreparedStatements за слишком короткое время - то есть, если есть препятствие для использования передовой практики, предложенной другими ответами, вы можете попробовать AntiSQLFilter

Bozho
источник
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;
    }
}
Shareef
источник
Это хорошо 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;

}

Billcountry
источник
0

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

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

yossico
источник