Избежать SQL-инъекции без параметров

у нас есть еще одно обсуждение здесь на работе об использовании параметризованных SQL-запросов в нашем коде. У нас есть две стороны в обсуждении: я и некоторые другие, которые говорят, что мы всегда должны использовать параметры для защиты от SQL-инъекций, и другие ребята, которые не считают это необходимым. Вместо этого они хотят заменить апострофы две запятые во всех строках, чтобы избежать SQL-инъекции. Все наши базы данных работают под управлением Sql Server 2005 или 2008, а наша база кода работает на .NET Framework версии 2.0.

позвольте мне привести вам простой пример в C#:

Я хочу, чтобы мы использовали это:

string sql = "SELECT * FROM Users WHERE Name=@name";
SqlCommand getUser = new SqlCommand(sql, connection);
getUser.Parameters.AddWithValue("@name", userName);
//... blabla - do something here, this is safe

в то время как другие ребята хотят сделать это:

string sql = "SELECT * FROM Users WHERE Name=" + SafeDBString(name);
SqlCommand getUser = new SqlCommand(sql, connection);
//... blabla - are we safe now?

где функция SafeDBString определяется следующим образом:

string SafeDBString(string inputValue) 
{
    return "'" + inputValue.Replace("'", "''") + "'";
}

Теперь, пока мы используем SafeDBString для всех строковых значений в наших запросах, мы должны быть в безопасности. Правильно?

есть две причины использовать функцию SafeDBString. Во-первых, это то, как это было сделано начиная с каменного века, а во-вторых, легче отлаживать операторы sql, так как вы видите запрос excact, который запускается в базе данных.

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

есть ли там кто-нибудь, кто может сломать это? Как бы вы это сделали?

EDIT: Подводя итог ответам:

  • никто еще не нашел способ обойти SafeDBString на Sql Server 2005 или 2008. Это хорошо, я думаю?
  • несколько ответов указали, что вы получаете прирост производительности при использовании параметризованных запросов. Причина в том, что планы запросов можно использовать повторно.
  • мы также согласны с тем, что использование параметризованных запросов дает более читаемый код, который легче поддерживать
  • кроме того, проще всегда использовать параметры, чем использовать различные версии SafeDBString, преобразования строки в число и преобразования строки в дату.
  • используя параметры, вы получаете автоматическое преобразование типов, что особенно полезно, когда мы работаем с датами или десятичными числами.
  • и наконец: не пытайтесь сделать безопасность самостоятельно как писал Юлианр. Поставщики баз данных тратят много времени и денег на безопасность. Нет способа сделать лучше, и нет причин пытаться делать их работу.

поэтому, пока никто не смог сломать простую безопасность функции SafeDBString, я получил много других хороших аргументов. Спасибо!

21 ответов


Я думаю, правильный ответ:

не пытайтесь сделать безопасность самостоятельно. Используйте любую надежную библиотеку отраслевого стандарта, доступную для того, что вы пытаетесь сделать, а не попытка сделать это самостоятельно. Какие бы предположения вы ни делали о безопасности, они могут быть неверными. Как безопасный, как ваш собственный подход может выглядеть (и он выглядит шатким в лучшем случае), есть риск, что вы что-то упускаете, и вы действительно хотите воспользоваться этим шансом, когда он приходит безопасности?

параметры использования.


а потом кто-то и использует " вместо '. Параметры, ИМО, единственный безопасный способ пойти.

Это также позволяет избежать многих проблем i18n с датами / номерами; какая дата 01/02/03? Сколько 123,456? Согласны ли ваши серверы (app-server и db-server) друг с другом?

Если фактор риска не убедительно для них, как насчет производительности? РСУБД могут повторно использовать план запроса, если используются параметры, способствующие производительности. Он может не только строка.


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

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

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

Я предполагаю, что фактическая проблема-гордость и упрямство, и вы больше ничего не можете с этим поделать.


прежде всего, ваш образец для версии "Replace" неверен. Вам нужно поместить апострофы вокруг текста:

string sql = "SELECT * FROM Users WHERE Name='" + SafeDBString(name) & "'";
SqlCommand getUser = new SqlCommand(sql, connection);

Итак, это еще одна вещь, которую параметры делают для вас: вам не нужно беспокоиться о том, нужно ли заключать значение в кавычки. Конечно, вы можете встроить это в функцию, но тогда вам нужно добавить много сложности в функцию: как узнать разницу между "NULL" как null и "NULL" как просто строка или между числом и строка, которая просто содержит много цифр. Это просто еще один источник ошибок.

другое дело производительность: параметризованные планы запросов часто кэшируются лучше, чем конкатенированные планы, таким образом, возможно, сохраняя сервер шаг при выполнении запроса.

кроме того, экранирование одинарных кавычек недостаточно. многие продукты БД допускают альтернативные методы экранирования символов, которыми может воспользоваться злоумышленник. В MySQL, например, вы можно также избежать одной цитаты с обратной косой чертой. И поэтому следующее значение "name" взорвало бы MySQL только с SafeDBString() функция, потому что при удвоении одинарной кавычки первая все еще экранируется обратной косой чертой, оставляя вторую "активной":

x\ ' или 1=1;--


кроме того, JulianR поднимает хороший момент ниже:никогда попробуйте сделать работу безопасности самостоятельно. Это так легко получить Security programming ошибаться в тонкостях появляется работать, даже с тщательным испытанием. Затем проходит время, и через год вы узнаете, что ваша система была взломана шесть месяцев назад, и вы даже не знали об этом до тех пор.

всегда полагаться на библиотеки безопасности, предоставленные для вашей платформы. Они будут написаны людьми, которые делают код безопасности для жизни, гораздо лучше протестированы, чем то, что вы можете управлять, и обслуживаются поставщиком, если уязвимость найдено.


Я говорю:

1) Почему вы пытаетесь повторно реализовать то, что встроенный? он есть, легко доступен, прост в использовании и уже отлажен в глобальном масштабе. Если будущие ошибки будут найдены в нем, они будут исправлены и доступны всем очень быстро, без необходимости что-либо делать.

2) Какие процессы существуют для гарантия что ты никогда пропустить вызов SafeDBString? Отсутствует он только в 1 месте откроется масса вопросов. Сколько вы собираетесь смотреть на эти вещи, и подумайте, сколько потрачено впустую это усилие, когда принятый правильный ответ так легко достичь.

3) насколько вы уверены, что вы покрыли каждый вектор атаки, о котором Microsoft(автор БД и библиотеки access) знает в вашей реализации SafeDBString ...

4) насколько легко читать структуру sql? В примере используется + конкатенация, параметры очень похожи строка.Формат, который более удобочитаем.

кроме того, есть 2 способа разработки того, что на самом деле было запущено - сверните свою собственную функцию LogCommand, простую функцию с нет проблем безопасности, или даже посмотреть на трассировку sql, чтобы выяснить, что думает база данных на самом деле происходит.

наша функция LogCommand просто:

    string LogCommand(SqlCommand cmd)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine(cmd.CommandText);
        foreach (SqlParameter param in cmd.Parameters)
        {
            sb.Append(param.ToString());
            sb.Append(" = \"");
            sb.Append(param.Value.ToString());
            sb.AppendLine("\"");
        }
        return sb.ToString();
    }

правильно или неправильно, это дает нам необходимую информацию без проблем безопасности.


с параметризованными запросами вы получаете больше, чем защиту от SQL-инъекции. Вы также получаете лучший потенциал кэширования плана выполнения. Если вы используете профилировщик запросов sql server, вы все равно можете увидеть "точный sql, который запускается в базе данных", поэтому вы ничего не теряете с точки зрения отладки ваших операторов sql.


Я использовал оба подхода, чтобы избежать атак SQL-инъекций и определенно предпочитаю параметризованные запросы. Когда я использовал конкатенированные запросы, я использовал библиотечную функцию, чтобы избежать переменных (например, mysql_real_escape_string), и не был бы уверен, что я покрыл все в собственной реализации (как кажется, вы тоже).


вы не можете легко выполнить проверку любого типа пользовательского ввода без использования параметров.

Если вы используете классы SQLCommand и SQLParameter для выполнения вызовов DB, вы все равно можете увидеть выполняемый SQL-запрос. Посмотрите на свойство CommandText SQLCommand.

Я всегда немного подозреваю в собственном подходе к предотвращению SQL-инъекции, когда параметризованные запросы настолько просты в использовании. Во-вторых, просто потому, что " это всегда было сделано путь" не означает, что это правильный способ сделать это.


это безопасно, только если вам гарантировано, что вы собираетесь передать строку.

Что делать, если вы не передаете строку в какой-то момент? Что, если вы передадите только номер?

http://www.mywebsite.com/profile/?id=7;DROP DATABASE DB

в конечном итоге стать:

SELECT * FROM DB WHERE Id = 7;DROP DATABASE DB

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

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


согласен по вопросам безопасности.
Еще одна причина использовать параметры-это эффективность.

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

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


за очень короткое время, которое мне пришлось исследовать проблемы SQL - инъекций, я вижу, что создание значения "безопасно" также означает, что вы закрываете дверь в ситуации, когда вам действительно могут понадобиться апострофы в ваших данных-как насчет чьего-то имени, например O'Reilly.

Это оставляет параметры и хранимые процедуры.

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


вот несколько статей, которые вы могли бы найти полезными в убеждении ваших коллег.

http://www.sommarskog.se/dynamic_sql.html

http://unixwiz.net/techtips/sql-injection.html

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


его можно сломать, однако средства зависят от точных версий / патчей и т. д.

один, который уже был поднят, - это ошибка переполнения/усечения, которую можно использовать.

другим будущим средством будет поиск ошибок, подобных другим базам данных - например, стек MySQL/PHP столкнулся с проблемой экранирования, потому что некоторые последовательности UTF8 могут использоваться для управления функцией replace - функция replace будет обманута в вводим персонажи инъекций.

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

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


по уже приведенным причинам параметры-очень хорошая идея. Но мы ненавидим их использовать, потому что создание param и присвоение его имени переменной для последующего использования в запросе-это тройная косвенная катастрофа.

следующий класс обертывает stringbuilder, который обычно используется для построения SQL-запросов. Это позволяет вам запись параматериализованных запросов без необходимости создания параметра, так что вы можете сосредоточиться на SQL. Ваш код будет выглядеть так этот...

var bldr = new SqlBuilder( myCommand );
bldr.Append("SELECT * FROM CUSTOMERS WHERE ID = ").Value(myId, SqlDbType.Int);
//or
bldr.Append("SELECT * FROM CUSTOMERS WHERE NAME LIKE ").FuzzyValue(myName, SqlDbType.NVarChar);
myCommand.CommandText = bldr.ToString();

читаемость кода, я надеюсь, вы согласитесь, значительно улучшена, и вывод является правильным параметризованным запросом.

класс выглядит так...

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace myNamespace
{
    /// <summary>
    /// Pour le confort et le bonheur, cette classe remplace StringBuilder pour la construction
    /// des requêtes SQL, avec l'avantage qu'elle gère la création des paramètres via la méthode
    /// Value().
    /// </summary>
    public class SqlBuilder
    {
        private StringBuilder _rq;
        private SqlCommand _cmd;
        private int _seq;
        public SqlBuilder(SqlCommand cmd)
        {
            _rq = new StringBuilder();
            _cmd = cmd;
            _seq = 0;
        }
        //Les autres surcharges de StringBuilder peuvent être implémenté ici de la même façon, au besoin.
        public SqlBuilder Append(String str)
        {
            _rq.Append(str);
            return this;
        }
        /// <summary>
        /// Ajoute une valeur runtime à la requête, via un paramètre.
        /// </summary>
        /// <param name="value">La valeur à renseigner dans la requête</param>
        /// <param name="type">Le DBType à utiliser pour la création du paramètre. Se référer au type de la colonne cible.</param>
        public SqlBuilder Value(Object value, SqlDbType type)
        {
            //get param name
            string paramName = "@SqlBuilderParam" + _seq++;
            //append condition to query
            _rq.Append(paramName);
            _cmd.Parameters.Add(paramName, type).Value = value;
            return this;
        }
        public SqlBuilder FuzzyValue(Object value, SqlDbType type)
        {
            //get param name
            string paramName = "@SqlBuilderParam" + _seq++;
            //append condition to query
            _rq.Append("'%' + " + paramName + " + '%'");
            _cmd.Parameters.Add(paramName, type).Value = value;
            return this; 
        }

        public override string ToString()
        {
            return _rq.ToString();
        }
    }
}

Я не вижу другого answsers адрес этой стороны-зачем делать это самому-это плохо, но считать атака усечения SQL.

есть еще QUOTENAME функция T-SQL, которая может быть полезна, если вы не можете убедить их использовать params. Он ловит много (все?) бежал касается qoute.


2 лет, Я recidivated... Любой, кто находит параметры боли, может попробовать мое расширение VS,QueryFirst. Вы редактируете свой запрос в реальном .файл sql (проверка, Intellisense). Чтобы добавить параметр, просто введите его непосредственно в SQL, начиная с"@". При сохранении файла QueryFirst будет генерировать классы-оболочки, позволяющие запускать запрос и получать доступ к результатам. Он будет искать тип DB вашего параметра и сопоставлять его к типу .net, который вы найдете в качестве входных данных для сгенерированных методов Execute (). не может быть проще. Делать это правильно радикально быстрее и проще, чем делать это любым другим способом, и создание уязвимости SQL-инъекции становится невозможным или, по крайней мере, извращенно сложным. Есть и другие преимущества убийцы, такие как возможность удалять столбцы в вашей БД и сразу видеть ошибки компиляции в вашем приложении.

правовая оговорка : я писал QueryFirst


вот несколько причин для использования параметризованных запросов:

  1. безопасность-уровень доступа к базе данных знает, как удалить или избежать элементов, которые не разрешены в данных.
  2. разделение проблем-мой код не несет ответственности за преобразование данных в формат, который нравится базе данных.
  3. нет избыточности - мне не нужно включать сборку или класс В каждый проект, который делает это форматирование/экранирование базы данных; он встроен в класс библиотека.

было несколько уязвимостей (я не помню, какая это была база данных), связанных с переполнением буфера инструкции SQL.

Что я хочу сказать, SQL-инъекция больше, чем просто "избежать цитаты", и вы понятия не имеете, что будет дальше.


еще одним важным соображением является отслеживание экранированных и неоткрытых данных. Есть тонны и тонны приложений, веб-и других, которые, похоже, неправильно отслеживают, когда данные являются необработанными-Unicode, & - encoded, formatted HTML и т. д. Очевидно, что будет трудно отслеживать, какие строки являются ''кодировке, а какие нет.

Это тоже проблема, когда вы в конечном итоге изменить тип переменной - возможно, это было целое, но теперь это струна. Теперь у тебя проблема.


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

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