Безопасно ли не параметризовать SQL-запрос, когда параметр не является строкой?
===============================================================================

В терминах [SQL-инъекции](https://en.wikipedia.org/wiki/SQL_injection) я полностью понимаю необходимость параметризации параметра `string`; что один из самых старых трюков в книге. Но когда может быть оправдано не параметризовать `SqlCommand`? Являются ли какие-либо типы данных "безопасными", чтобы не параметризовать?

Например: я не считаю себя где-то рядом с экспертом в SQL, но я не могу думать о каких-либо случаях, когда он потенциально уязвим для SQL-инъекции, чтобы принять `bool` или `int` и просто соедините его прямо в запросе.

Является ли мое предположение правильным или может потенциально оставить огромную уязвимость безопасности в моей программе?

Для уточнения этот вопрос помечен [С#](/questions/tagged/c%23 "показать теги с тегами 'С#'"), который строго типизированный язык; когда я говорю "параметр", думаю что-то вроде `public int Query(int id)`.

 

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

```
var sqlCommand = new SqlCommand("SELECT * FROM People WHERE IsAlive = " + isAlive + 
" AND FirstName = @firstName");

sqlCommand.Parameters.AddWithValue("firstName", "Rob");

```

 (function(w, d, n, s, t) {
 w[n] = w[n] || [];
 w[n].push(function() {
 Ya.Context.AdvManager.render({
 blockId: "R-A-248011-3",
 renderTo: "yandex\_rtb\_R-A-248011-3",
 async: true
 });
 });
 t = d.getElementsByTagName("script")[0];
 s = d.createElement("script");
 s.src = "//an.yandex.ru/system/context.js";
 s.async = true;
 t.parentNode.insertBefore(s, t);
 })(this, this.document, "yandexContextAsyncCallbacks");

Он также оставляет вас уязвимым в ситуации, когда тип изменяется от целого к строке (подумайте, номер сотрудника, который, несмотря на его имя, может содержать буквы).

Итак, мы изменили тип EmployeeNumber с `int` на `string`, но забыли обновить наши SQL-запросы. К сожалению.

 

При использовании строго типизированной платформы на компьютере, который вы управляете (например, веб-сервере), вы можете предотвратить ввод кода для запросов только с `bool`, `DateTime` или `int` (и другими числовыми) значениями. В чем проблема, связанные с производительностью, вызванные тем, что SQL-сервер повторно компилирует каждый запрос и не позволяет ему получать хорошую статистику о том, какие запросы запускаются с какой частотой (что вредит управлению кешем).

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

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

 (function(w, d, n, s, t) {
 w[n] = w[n] || [];
 w[n].push(function() {
 Ya.Context.AdvManager.render({
 blockId: "R-A-248011-4",
 renderTo: "yandex\_rtb\_R-A-248011-4",
 async: true
 });
 });
 t = d.getElementsByTagName("script")[0];
 s = d.createElement("script");
 s.src = "//an.yandex.ru/system/context.js";
 s.async = true;
 t.parentNode.insertBefore(s, t);
 })(this, this.document, "yandexContextAsyncCallbacks");

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

Я добавлю, что это, к сожалению, не тот аргумент, который вы можете выиграть прямо. Это звучит как ситуация, когда быть "правильным" уже недостаточно, и наступать на своих сотрудников, чтобы решить эту проблему самостоятельно, вряд ли будет способствовать хорошей командной динамике; это может в конечном итоге повредить больше, чем помогает. Лучшим подходом в этом случае может быть содействие использованию инструмента статического анализа. Это обеспечило бы легитимность и доверие к усилиям, направленным на восстановление и исправление существующего кода.

 

В некоторых случаях возможно выполнить SQL-инъекцию с непараметризованными (конкатенированными) переменными, отличными от строковых значений - см. эту статью от Jon: <http://codeblog.jonskeet.uk/2014/08/08/the-bobbytables-culture/>.

Дело в том, что когда вызывается `ToString`, некоторый пользовательский поставщик культуры может преобразовать нестроковый параметр в свое строковое представление, которое вставляет в запрос некоторый SQL.

 

Это  **не** безопасно даже для нестроковых типов.  **Всегда** использовать параметры. Период.

Рассмотрим следующий пример кода:

```
var utcNow = DateTime.UtcNow;
var sqlCommand = new SqlCommand("SELECT * FROM People WHERE created_on <= '" + utcNow + "'");

```

При первом взгляде код выглядит безопасным, но все меняется, если вы вносите некоторые изменения в региональные настройки Windows и добавляете инъекцию в короткий формат даты:

 [![Datetime Injection]()](/img/b7f6dee4d8f7f1e2f4f69a4a4d578147.jpg)

Теперь полученный текст команды выглядит следующим образом:

```
SELECT * FROM People WHERE created_on <= '26.09.2015' OR '1'<>' 21:21:43'

```

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

Можно утверждать, что вместо текущей культуры следует использовать инвариантную культуру, но я так много раз встречал конкатенации строк, и это довольно легко пропустить при конкатенации строк с помощью объектов с помощью `+`.

 

  **"SELECT * FROM Table1 WHERE Id =" + intVariable.ToString()**

---

  **Безопасность**   

Все в порядке.
Атакующие не могут вводить ничего в вашу типизированную переменную int.

  **Производительность**   

Не в порядке.

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

  **Стиль кодирования**   

Плохая практика.

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

   

  **"SELECT * FROM Product WHERE Id =" + TextBox1.Text**

---

Хотя это не ваш вопрос, но, возможно, полезный для будущих читателей:

  **Безопасность**   

Disaster!   

Даже если поле `Id` является целым числом, ваш запрос может подвергаться SQL Injection.
Предположим, у вас есть запрос в вашем приложении `"SELECT * FROM Table1 WHERE Id=" + TextBox1.Text`, злоумышленник может вставить в текстовое поле `1; DELETE Table1`, и запрос будет выглядеть следующим образом:

```
"SELECT * FROM Table1 WHERE Id=1; DELETE Table1"

```

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

```
string.Format("SELECT * FROM Table1 WHERE Id={0}", int.Parse(TextBox1.Text))

```

   

  **Ваш вопрос**

---

>  Мой вопрос возник из-за того, что коллега написал кучу запросов конкатенирование целочисленных значений, и мне было интересно, было ли это тратить свое время на то, чтобы пройти и исправить их все.
> 
> 
> 

Я думаю, что изменение этих кодов не пустая трата времени. Действительно, изменение рекомендуется!

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

 

В одном есть два вопроса. И вопрос из названия очень мало связан с озабоченностями, выраженными OP в комментариях впоследствии.

Хотя я понимаю, что для ОП это особый случай для читателей, поступающих из Google, важно ответить на более общий вопрос, который можно сформулировать как "конкатенация как безопасная, как подготовленные заявления, если Я удостоверился, что каждый литерал, который я конкатенирую, безопасен?". Поэтому я хотел бы сосредоточиться на этом последнем. И ответ

 Определенно NO.
================

Объяснение не так прямо, как хотелось бы большинству читателей, но я постараюсь изо всех сил.

Я размышлял над этим вопросом некоторое время, в результате чего [статья](http://phpdelusions.net/sql_injection) (хотя на основе среды PHP), где я пытался суммировать все в порядке. Мне пришло в голову, что вопрос защиты от SQL-инъекции часто ускользает от некоторых связанных, но более узких тем, таких как экранирование строк, литье типов и т.д. Хотя некоторые из мер можно считать безопасными, когда они принимаются сами по себе, нет системы или простого правила. Это делает его очень скользким, слишком много внимания уделяет внимания и опыту разработчиков.

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

(С этой точки зрения статья из Jon Skeet, приведенная в другом ответе, довольно плоха, чем хорошая, поскольку она снова задирает какой-то крайний случай, концентрируясь на конкретной проблеме синтаксиса и неспособная решить проблему на целом).

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

* список возможных вариантов форматирования действительно огромен. Значит, можно легко упустить некоторые из них. Или путайте их (например, используя экранирование строки для идентификатора).
* Конкатенация означает, что все меры защиты должны выполняться программистом, а не программой. Только эта проблема приводит к нескольким последствиям:
	+ такое форматирование является ручным. Ручной режим означает чрезмерную подверженность ошибкам. Можно просто забыть применить.
	+ Кроме того, существует соблазн переместить процедуры форматирования в некоторую централизованную функцию, еще более запутать вещи и испортить данные, которые не будут поступать в базу данных.
* Когда задействовано более одного разработчика, проблемы умножаются в десять раз.
* когда используется конкатенация, невозможно сразу определить потенциально опасный запрос: все они потенциально опасны!

В отличие от этого беспорядка, подготовленные заявления действительно являются Святым Граалем:

* его можно выразить в виде одного простого правила, которое легко выполнить.
* это, по сути, неопределенная мера, означает, что разработчик не может вмешиваться и, охотно или неохотно, испортил процесс.
* Защита от инъекций - это действительно только побочный эффект подготовленных операторов, целью которых является создание синтаксически правильной формулировки. И синтаксически корректная инструкция - это 100% -ное доказательство в отношении инъекций. Однако нам нужен наш синтаксис, чтобы быть правильным, несмотря на возможность инъекции.
* если он используется полностью, он защищает приложение независимо от опыта разработчика. Скажем, есть вещь, называемая [инъекция второго порядка](https://download.oracle.com/oll/tutorials/SQLInjection/html/lesson1/les01_tm_attacks2.htm). И очень сильное заблуждение, которое гласит: "Чтобы защитить, [" Escape All User Supplied Input](https://www.owasp.org/index.php/SQL_Injection_Prevention_Cheat_Sheet#Defense_Option_3:_Escaping_All_User_Supplied_Input)". В сочетании друг с другом они приводят к инъекции, если разработчик принимает решение, что нужно защитить, а что нет.

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

Интересно, что этот вопрос противоречий вызван весьма противоречивым характером. Идея сайта состоит в том, чтобы использовать  **конкретные вопросы от пользователей, которые запрашивают напрямую**для достижения цели создания базы данных  **ответов общего назначения, подходящих для пользователей, которые пришли из поиска**. Идея не плохая сама по себе, но она не срабатывает в такой ситуации: когда пользователь задает очень узкий вопрос, в частности, чтобы получить аргумент в споре с коллегой (или решить, стоит ли реорганизовать код). В то время как большинство опытных участников пытаются написать ответ, имея в виду миссию Qaru в целом, делая их ответы полезными для максимально возможного числа читателей, а не только для OP.

 

Позвольте не просто думать о соображениях безопасности или типа.

Причина использования параметризованных запросов заключается в повышении производительности на уровне базы данных. С точки зрения базы данных параметризованный запрос является одним запросом в буфере SQL (для использования терминологии Oracle, хотя я считаю, что все базы данных имеют одинаковую концепцию внутри). Таким образом, база данных может хранить определенное количество запросов в памяти, готовых и готовых к выполнению. Эти запросы не нуждаются в анализе и будут быстрее. Часто запускаемые запросы обычно находятся в буфере и не будут обрабатываться каждый раз, когда они будут использоваться.

UNLESS

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

сейчас

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

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

 

Чтобы добавить некоторую информацию в ответ Maciek:

Легко изменить информацию о культуре стороннего приложения .NET, вызвав основную функцию сборки путем отражения:

```
using System;
using System.Globalization;
using System.Reflection;
using System.Threading;

namespace ConsoleApplication2
{
  class Program
  {
    static void Main(string[] args)
    {
      Assembly asm = Assembly.LoadFile(@"C:\BobbysApp.exe");
      MethodInfo mi = asm.GetType("Test").GetMethod("Main");
      mi.Invoke(null, null);
      Console.ReadLine();
    }

    static Program()
    {
      InstallBobbyTablesCulture();
    }

    static void InstallBobbyTablesCulture()
    {
      CultureInfo bobby = (CultureInfo)CultureInfo.InvariantCulture.Clone();
      bobby.DateTimeFormat.ShortDatePattern = @"yyyy-MM-dd'' OR ' '=''";
      bobby.DateTimeFormat.LongTimePattern = "";
      bobby.NumberFormat.NegativeSign = "1 OR 1=1 OR 1=";
      Thread.CurrentThread.CurrentCulture = bobby;
    }
  }
}

```

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

 

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

 

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

 

Нет, вы можете получить атаку SQL-инъекций таким образом. Я написал старую статью на турецком языке, которая показывает, как [здесь](http://inet-tr.org.tr/inetconf10/bildiri/35.doc). Пример статьи в PHP и MySQL, но концепция работает на С# и SQL Server.

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

```
http://localhost/sqlEnjeksiyon//instructors.aspx?id=24

```

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

```
http://localhost/sqlEnjeksiyon//instructors.aspx?id=ASCII((SELECT%20DATABASE()))

```

Обратите внимание, что здесь введенное значение не является строкой. Мы меняем значение char на int, используя функцию ASCII. Вы можете выполнить то же самое в SQL Server, используя "CAST (YourVarcharCol AS INT)".

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

```
http://localhost/sqlEnjeksiyon//instructors.aspx?id=LEN((SELECT%20DATABASE()))

http://localhost/sqlEnjeksiyon//instructors.aspx?id=ASCII(SUBSTR(SELECT%20DATABASE(),1,1))

```

Затем, используя имя базы данных, вы начинаете получать имена таблиц в базе данных.

```
http://localhost/sqlEnjeksiyon//instructors.aspx?id=ASCII(SUBSTR((SELECT table_name FROM INFORMATION_SCHEMA.TABLES LIMIT 1),1,1))

```

Конечно, вам нужно автоматизировать этот процесс, так как вы получаете только один символ для каждого запроса. Но вы можете легко автоматизировать его. В моей статье приведен один пример в [watir](http://watir.com/). Использование только одной страницы, а не параметризованного значения ID. Я могу узнать каждое имя таблицы в вашей базе данных. После этого я могу искать важные таблицы. Это займет время, но это выполнимо.

 

Хорошо... одно можно сказать точно:
Безопасность - это не нормально, когда вы объединяете строку (взятую пользователем) со своей строкой командной строки SQL.
Это не имеет значения, когда предложение where ссылается на Integer или на любой тип; могут произойти инъекции.

  **Что важно в SQL Injection - это тип данных переменной, используемой для получения значения от пользователя.**

Предположим, что мы имеем целое число в условии where и

* Пользовательская переменная является строкой. Тогда хорошо, это не очень легко
(используя UNION), но очень легко обойти, используя "OR 1 = 1" -
как атаки...
* Если переменная пользователя является целым числом. Затем снова мы можем "проверить"
прочность системы путем прохождения необычного большого количества тестов для
сбоев системы или даже для скрытого переполнения буфера (в финале
string)...;)

Возможно, параметры для запросов или (даже лучше - imo) для хранимых процедур не являются 100% -ными угрозами, но они являются наименее необходимой мерой (или элементарной, если вы предпочитаете), чтобы минимизировать их.