Избежать 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? Согласны ли ваши серверы (сервер приложений и сервер БД) друг с другом?
Если фактор риска не убедительно для них, как насчет производительности? СУБД может повторно использовать план запроса, если вы используете параметры, повышающие производительность. Он может не только строка.
аргумент не является выигрышным. Если вам удастся найти уязвимость, ваши сотрудники просто изменят функцию SafeDBString для ее учета, а затем попросят вас снова доказать, что это небезопасно.
учитывая, что параметризованные запросы являются бесспорной лучшей практикой программирования, бремя доказательства должно лежать на них, чтобы указать, почему они не используют метод, который является более безопасным и более эффективным.
Если проблема заключается в переписывании всего унаследованного кода, то простым компромиссом было бы использование параметризованных запросов во всех новых кодах и рефакторинг старого кода для их использования при работе над этим кодом.
Я думаю, что на самом деле проблема заключается в гордости и упрямстве, и вы не можете сделать с этим больше.
прежде всего, ваш образец для версии "Replace" неверен. Вам нужно поставить апострофы вокруг текста:
string sql = "SELECT * FROM Users WHERE Name='" + SafeDBString(name) & "'"; SqlCommand getUser = new SqlCommand(sql, connection);
Так что это еще одна вещь, которую параметры делают для вас: вам не нужно беспокоиться о том, Должно ли значение быть заключено в кавычки. Конечно, вы можете встроить это в функцию, но тогда вам нужно добавить много сложности в функцию: как узнать разницу между "NULL" как null и "NULL" как просто строку или между числом и a строка, которая просто содержит много цифр. Это просто еще один источник ошибок.
другое дело производительность: параметризованные планы запросов часто кэшируются лучше, чем объединенные планы, таким образом, возможно, сохраняя сервер шаг при выполнении запроса.
кроме того, экранирование одинарных кавычек недостаточно. многие продукты БД позволяют альтернативные методы для экранирования символов, которые злоумышленник может воспользоваться. В MySQL, например, вы также можно избежать одинарной кавычки с обратной косой чертой. И поэтому следующее значение "name" взорвало бы MySQL только с помощью
SafeDBString()
функция, потому что при удвоении одинарной кавычки первая все еще экранируется обратной косой чертой, оставляя вторую "активной":x\ ' или 1=1;--
кроме того, Юлианр поднимает хороший вопрос ниже:никогда попробуйте сделать работу безопасности самостоятельно. Это так легко получить Программирование безопасности неправильно в тонких отношениях, что появляется работать, даже с тщательным тестированием. Затем проходит время, и через год вы узнаете, что ваша система была взломана шесть месяцев назад, и вы даже не знали об этом до тех пор.
всегда полагайтесь как можно больше на библиотеки безопасности, предоставляемые для вашей платформы. Они будут написаны людьми, которые делают код безопасности для жизни, гораздо лучше протестированы, чем то, что вы можете управлять, и обслуживаются поставщиком, если уязвимость найдено.
Я говорю:
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 для выполнения вызовов БД, вы все равно можете увидеть выполняемый 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 - инъекцией, я вижу, что создание значения "safe" также означает, что вы закрываете дверь в ситуации, когда вам действительно могут понадобиться апострофы в ваших данных-как насчет чьего-то имени, например O'Reilly.
Это оставляет параметры и хранимые процедуры.
и да, вы всегда должны пытаться реализовать код наилучшим образом, который вы знаете сейчас, а не только так, как это всегда делалось.
вот несколько статей, которые вы можете найти полезными в убеждении ваших коллег.
http://www.sommarskog.se/dynamic_sql.html
http://unixwiz.net/techtips/sql-injection.html
лично я предпочитаю никогда не позволять какому-либо динамическому коду касаться моей базы данных, требуя, чтобы все контакты были через sps (а не тот, который использует динамический SQl). Это означает ничего кроме того, что я дал пользователям разрешение может быть сделано и что внутренние пользователи (за исключением очень немногих с производственным доступом для целей администрирования) не могут напрямую обращаться к моим таблицам и создавать хаос, красть данные или совершать мошенничество. Если вы запускаете финансовое приложение, это самый безопасный способ пойти.
Он может быть сломан, однако средства зависят от точных версий / патчей и т. д.
тот, который уже был поднят, является ошибкой переполнения/усечения, которая может быть использована.
еще одним будущим средством будет поиск ошибок, подобных другим базам данных - например, стек MySQL / PHP страдал проблемой экранирования, потому что некоторые последовательности UTF8 могут использоваться для управления функцией замены - функция замены будет обманута в вводим характеры впрыски.
в конце концов, механизм безопасности замены полагается на ожидается а не предназначены функциональность. Поскольку функциональность не была назначением кода, существует высокая вероятность того, что некоторые обнаруженные причуды нарушат вашу ожидаемую функциональность.
Если у вас много устаревшего кода, метод 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(); } } }
Я не видел никаких других ответов, адресованных этой стороне "почему делать это самостоятельно плохо", но рассмотрим атака усечения SQL.
есть еще
QUOTENAME
функция T-SQL, которая может быть полезна, если вы не можете убедить их использовать params. Он ловит много (все?) из уцелевших проблем qoute.
2 лет, Я рецидивировал... Любой, кто находит параметры боли, может попробовать мое расширение VS, QueryFirst. Вы редактируете свой запрос в реальном времени .файл sql (проверка, Intellisense). Чтобы добавить параметр, просто введите его непосредственно в SQL, начиная с '@'. При сохранении файла QueryFirst создаст классы-оболочки, которые позволят вам выполнить запрос и получить доступ к результатам. Он будет искать тип БД вашего параметра и сопоставлять его к типу .net, который вы найдете в качестве входных данных для созданных методов Execute (). не может быть проще. Делать это правильно радикально быстрее и проще, чем делать это любым другим способом, и создание уязвимости SQL-инъекции становится невозможным или, по крайней мере, извращенно сложным. Есть и другие преимущества убийцы, такие как возможность удалять столбцы в вашей БД и сразу же видеть ошибки компиляции в вашем приложении.
правовая оговорка : я писал QueryFirst
вот несколько причин для использования параметризованных запросов:
- безопасность-уровень доступа к базе данных знает, как удалить или экранировать элементы, которые не разрешены в данных.
- разделение проблем-мой код не несет ответственности за преобразование данных в формат, который нравится базе данных.
- нет избыточности - мне не нужно включать сборку или класс В каждый проект, который выполняет это форматирование/экранирование базы данных; он встроен в класс библиотека.
было несколько уязвимостей (я не могу вспомнить, какая это была база данных), которая связана с переполнением буфера оператора SQL.
Я хочу сказать, что SQL-инъекция-это больше, чем просто "побег из цитаты", и вы понятия не имеете, что будет дальше.
еще одним важным фактором является отслеживание сбежал и данные декодировались. Есть тонны и тонны приложений, веб-и других, которые, похоже, не отслеживают должным образом, когда данные являются необработанными-Unicode, & - encoded, formatted HTML и т. д. Очевидно, что будет трудно отслеживать, какие строки
''
–закодированы и которые не являются.Это тоже проблема, когда вы в конечном итоге изменить тип переменной - возможно, это было целое, но теперь это строка. Теперь у вас есть проблема.
всегда использовать параметризованные запросы. Иногда даже простой ввод без использования каких-либо странных символов уже может создать SQL-инъекцию, если ее не идентифицируют как вход для поля в базе данных.
Так что просто позвольте базе данных сделать свою работу по идентификации самого ввода, не говоря уже о том, что это также экономит много проблем, когда вам нужно на самом деле вставить странные символы, которые в противном случае были бы экранированы или изменены. Он даже может сохранить некоторые ценные среды выполнения в конец для того, чтобы рассчитать.