Должен ли я защищаться от SQL-инъекций, если я использовал раскрывающийся список?

96

Я понимаю, что вам НИКОГДА не следует доверять пользовательскому вводу из формы, в основном из-за возможности SQL-инъекции.

Однако применимо ли это также к форме, в которой ввод осуществляется только из раскрывающихся списков (см. Ниже)?

Я сохраняю $_POST['size']его в сеансе, который затем используется по всему сайту для запроса различных баз данных (с помощью mysqliзапроса Select), и любая SQL-инъекция определенно повредит (возможно, отбросит) их.

Здесь нет области для ввода пользовательского ввода для запроса баз данных, только раскрывающиеся списки.

<form action="welcome.php" method="post">
<select name="size">
  <option value="All">Select Size</option> 
  <option value="Large">Large</option>
  <option value="Medium">Medium</option>
  <option value="Small">Small</option>
</select>
<input type="submit">
</form>
Тряпки
источник
112
Да. Ничто не мешает злоумышленнику <select>ввести любые значения, которые он пожелает, в ваш ввод. Действительно, даже немного технически подготовленный пользователь может добавить дополнительные параметры с помощью консоли браузера. если вы сохраните белый список доступных значений массива и сравните входные данные с ним, вы можете смягчить это (и вы должны, потому что это предотвращает нежелательные значения)
Майкл Берковски
13
Вы должны понимать основные вещи запроса / ответа и то, что не имеет значения, как интерфейс построен на запросе, то есть в данном случае раскрывающийся список
Royal Bg
13
@YourCommonSense Потому что это хороший вопрос. Не все понимают, насколько клиент манипулирует. Это вызовет очень ценные ответы на этот сайт.
Cruncher
8
@Cruncher Понятно. Для среднего stackoverflowian это ракетостроение, о котором они слышали раньше. Даже несмотря на самый популярный вопрос по тегу PHP.
Your Common Sense
9
«Я понимаю, что НИКОГДА не следует доверять пользовательскому вводу». Без исключений.
Prinzhorn

Ответы:

69

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

$possibleOptions = array('All', 'Large', 'Medium', 'Small');

if(in_array($_POST['size'], $possibleOptions)) {
    // Expected
} else {
    // Not Expected
}

Затем используйте mysqli_ *, если вы используете версию php> = 5.3.0, которой вы должны быть, чтобы сохранить результат. При правильном использовании это поможет с внедрением sql.

Оливер Байес-Шелтон
источник
это сокращенная версия «белого списка» OliverBS?
Tatters
На самом деле это не просто очень простая версия одного, вы можете добавить значения в базу данных для проверки, чтобы упростить ее и использовать повторно, или, возможно, создать класс белого списка с определенным методом для каждого белого списка для проверки. если вы не хотите использовать базу данных, свойства белого списка могут быть внутри свойства массива в вашем классе белого списка.
Оливер Байес-Шелтон,
9
Тем не менее, вы должны использовать Prepared Statements (рекомендуется) или mysqli_real_escape_string. Также правильно экранируйте значения при их выводе (например, используйте htmlspecialchars () в документе HTML).
ComFreek
4
Я хотел бы предложить , установив третий параметр in_arrayдля trueстрогого сравнения. Я не уверен, что может пойти не так, но вольное сравнение довольно странно.
Brilliand
1
Значения @OliverBS $ _POST также могут быть массивами. Числовые строки сравниваются как числа ('5' == '05'). Я не думаю, что в вашем конкретном примере есть дыра в безопасности, но правила сложны, и дыры могут открыться по причинам, которых я даже не понимаю. Строгое сравнение легче рассуждать, и поэтому его легче безопасно использовать.
Brilliand
195

Да, от этого нужно защититься.

Позвольте мне показать вам, почему, используя консоль разработчика Firefox:

Я отредактировал одно из значений в раскрывающемся списке, чтобы оно было оператором выпадающей таблицы

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

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

Если вы попытались дополнительно ограничить это использование поведения на своей странице, мои варианты включают отключение этого поведения или просто написание пользовательского HTTP-запроса на ваш сервер, который все равно имитирует отправку этой формы. Именно для этого используется инструмент под названием curl , и я думаю, что команда для отправки этой SQL-инъекции в любом случае будет выглядеть примерно так:

curl --data "size=%27%29%3B%20DROP%20TABLE%20*%3B%20--"  http://www.example.com/profile/save

(Возможно, это не совсем верная команда curl, но опять же, я надеюсь, что уловил свою точку зрения.)

Итак, повторю:

НИКОГДА не доверяйте вводу пользователя. ВСЕГДА защищайте себя.

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

doppelgreener
источник
24
Не говоря уже о создании пользовательской полезной нагрузки с помощью curl. ВСЕГДА дезинфицируйте ввод на стороне сервера !
recursion.ninja
14
Изображение говорит более тысячи слов.
davidkonrad
4
Это должен быть ответ по умолчанию. Также следует включить кое-что о curl. Люди не понимают, что вы можете отправить HTTP-запрос из любого места в любое место, используя любой формат и передавая любые значения, сервер должен убедиться, что запрос действителен перед его обработкой.
retrohacker
2
Какой милый! Это маленькие столики Бобби!
Aura
45

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

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

Независимо от любого материала HTML!
Важно понимать, что SQL-запросы должны быть правильно отформатированы независимо от любых внешних факторов, будь то ввод HTML или что-то еще.

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

Здесь вы можете найти подробное объяснение, почему подготовленные операторы необходимы и как их правильно использовать, а где они неприменимы, и что делать в таком случае: Автостопом по защите от SQL-инъекций

Кроме того, этот вопрос был помечен . Я полагаю, что в основном случайно, но в любом случае я должен предупредить вас, что необработанный mysqli не является адекватной заменой старых функций mysq_ * . Просто потому, что, если использовать его в старом стиле, он вообще не добавит безопасности. Хотя поддержка подготовленных операторов болезненна и проблематична, до такой степени, что средний пользователь PHP просто не может попытаться их применить. Таким образом, если ORM или какая-то библиотека абстракций не является вариантом, тогда PDO - ваш единственный выбор.

Ваш здравый смысл
источник
5
я = случайный (0, 15); // какой-то запрос с использованием i. Мне еще нужно подготовить выписку здесь?
Cruncher
2
Какая реализация random?
Slicedpan
9
@YourCommonSense узкоспециализированный? Мне кажется, что фраза «Всегда делай X, и я не стану объяснять это» - это узкий кругозор.
Cruncher
5
@Cruncher Я не могу придумать причины, чтобы не всегда использовать подготовленные операторы, каждый раз, для всего, всегда. Вы можете сказать мне один?
Wesley Murch
3
@Cruncher С этим я согласен, просто похоже, что вы играете в Devil's Advocate. Оговорка в ответе также «включает любые переменные данные» . Есть несколько случаев, например, PHP int casting, но, кажется, лучше просто использовать подготовленные операторы. Сказать (неопытным) людям, что небольшой «прирост» производительности важнее, чем безопасность, невозможно. Ответ «неполный», но он посылает убедительный сигнал, который игнорируют другие. Я оставлю свое дело.
Wesley Murch
12

Да.

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

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

if(in_array($_POST['ddMenu'], $dropDownValues){

    $valueYouUseLaterInPDO = $dropDownValues[array_search("two", $arr)];

} else {

    die("effin h4x0rs! Keep off my LAMP!!"); 

}
rm-vanda
источник
5
Этот ответ является неполным, кроме того, вы должны использовать подготовленные операторы, чтобы инъекция была невозможна, независимо от того, какое значение установлено
Slicedpan
7
@Slicedpan Правда? Звучит так, будто вы подпрыгиваете, не зная почему ... Если у меня есть несколько возможных входных данных в запрос, так что я могу проверить, все ли они в порядке (что я знаю, потому что я их сделал), тогда вы не получите никаких дополнительных преимуществ безопасности от использования подготовленного заявления
Cruncher
3
За исключением того, что принудительное использование подготовленных операторов в качестве соглашения защищает вас от внедрения уязвимостей SQL-инъекций в будущем.
Slicedpan
1
@Cruncher Давать обещание «все значения в раскрывающемся списке всегда будут безопасными» - очень небезопасное обещание. Значения изменены, код не обязательно обновляется. Тот, кто обновляет значения, может даже не знать, что такое небезопасное значение! Особенно в области веб-программирования, которая изобилует всевозможными проблемами безопасности, экономить на чем-то вроде этого просто безответственно (и другими менее приятными словами).
hyde
1
@Cruncher Если вы правильно обрабатываете свои SQL-данные, вы можете принимать любой ввод, не мешая правильной работе SQL. Часть SQL должна быть подготовлена ​​и готова принять что угодно, откуда бы оно ни исходило. Все остальное подвержено ошибкам.
glglgl
8

Один из способов защиты от пользователей, изменяющих раскрывающиеся списки с помощью консоли, - использовать в них только целые значения. Затем вы можете проверить, что значение POST содержит целое число, и использовать массив для преобразования его в текст при необходимости. Например:

<?php
// No, you don't need to specify the numbers in the array but as we're using them I always find having them visually there helpful.
$sizes = array(0 => 'All', 1 => 'Large', 2 => 'Medium', 3 => 'Small');
$size = filter_input(INPUT_POST, "size", FILTER_VALIDATE_INT);

echo '<select name="size">';
foreach($sizes as $i => $s) {
    echo '<option value="' . $i . '"' . ($i == $size ? ' selected' : '') . '>' . $s . '</option>';
}
echo '</select>';

Затем вы можете использовать $sizeв своем запросе, зная, что он всегда будет содержать FALSEтолько целое число.

Стифон
источник
2
@OliverBS Что, filter_inputесли не проверка на стороне сервера? Никто не может публиковать ничего, кроме целого числа.
Styphon
Спасибо, Стайфон, так это заменяет форму в OP? И применимо ли это к более крупным формам с несколькими раскрывающимися списками? Если бы я добавил к нему еще одно раскрывающееся меню, скажем «цвет»?
Tatters
@SamuelTattersfield Да и да. Вы можете использовать это для любого количества выпадающих списков с любым количеством опций. Вы просто создаете новый массив для каждого раскрывающегося списка и вводите все параметры для раскрывающегося списка в массиве.
Styphon
Ницца! Мне это нравится. Я попробую и посмотрю, что я получу при тестировании.
Tatters
@SamuelTattersfield Отлично, просто убедитесь, что вы используете эту filter_inputчасть для проверки, в противном случае она так же полезна, как шоколадный чайник для безопасности.
Styphon
7

Другие ответы уже охватывают то, что вам нужно знать. Но, возможно, это поможет прояснить еще кое-что:

Есть две вещи , которые вы должны сделать:

1. Подтвердите данные формы.

Как ясно показывает ответ Джонатана Хоббса , выбор элемента html для ввода формы не обеспечивает надежной фильтрации.

Проверка обычно выполняется таким образом, чтобы данные не изменялись, но снова отображалась форма с полями, помеченными как «Исправьте это».

Большинство фреймворков и CMS имеют конструкторы форм, которые помогут вам с этой задачей. И не только это, они также помогают против CSRF (или «XSRF»), который является еще одной формой атаки.

2. Очистить / избежать переменных в операторах SQL.

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

Если вы создаете инструкцию (My) SQL с любыми переменными, предоставленными пользователем или нет, вам нужно экранировать и заключить эти переменные в кавычки.

Как правило, любая такая переменная, которую вы вставляете в инструкцию MySQL, должна быть либо строкой, либо чем-то, что PHP может быть надежно преобразовано в строку, которую MySQL может переварить. Например, числа.

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

  • В старой школе MySQL + PHP mysql_real_escape_string () выполняет эту работу. Проблема в том, что об этом слишком легко забыть, поэтому вы обязательно должны использовать подготовленные операторы или построители запросов.
  • В MySQLi вы можете использовать подготовленные операторы.
  • Большинство фреймворков и CMS предоставляют построители запросов, которые помогут вам с этой задачей.

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

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

Что произойдет, если вы пропустите одно из них?

Если вы не используете проверку формы , но дезинфицируете свой ввод SQL, вы можете увидеть все виды плохих вещей, но вы не увидите SQL-инъекцию! (*)

Во-первых, это может привести ваше приложение в состояние, которое вы не планировали. Например, если вы хотите рассчитать средний возраст всех пользователей, но один пользователь указал возраст «aljkdfaqer», ваш расчет не удастся.

Во-вторых, могут быть всевозможные другие виды атак с использованием инъекций, которые вам необходимо учитывать: например, пользовательский ввод может содержать javascript или другой материал.

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

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

(*) или это было бы что-то действительно экзотическое.

Если вы не экранируете переменные для операторов SQL , но проверили ввод формы, вы все равно можете видеть, что происходит плохое.

Во-первых, вы рискуете, что при сохранении данных в базе данных и их повторной загрузке это уже не будут те же данные, «потерянные при переводе».

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

В-третьих, это все еще может вызывать SQL-инъекцию.

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

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

Дон Кихот
источник
Ни слишком длинные данные, ни строка в числовом поле ничего не сломают
Your Common Sense
хм, просто попробовал это сейчас, и действительно показывает предупреждение вместо ошибки. Я думаю, что именно PDO превращает это в ошибку. Я уверен, что обсуждал с десяток вопросов на drupal.org, где некоторые строки слишком длинные для varchar.
donquixote
6

Ваш веб-браузер не «знает», что он получает страницу с php, он видит только html. А слой http знает еще меньше. Вам нужно уметь обрабатывать практически любой тип ввода, который может пересекать слой http (к счастью, для большинства вводимых php уже выдает ошибку). Если вы пытаетесь не допустить, чтобы вредоносные запросы испортили вашу базу данных, вам нужно предположить, что парень на другом конце знает, что он делает, и что он не ограничен тем, что вы можете видеть в своем браузере при нормальных обстоятельствах ( не говоря уже о том, что вы можете возиться с инструментами разработчика браузера). Итак, да, вам нужно учитывать любой ввод из раскрывающегося списка, но для большинства вводимых данных вы можете выдать ошибку.

Безымянный
источник
6

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

Джейсон Хиггинс
источник
5

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

SELECT * FROM table WHERE size = ?

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

SELECT * FROM table WHERE size = 'DROP table;'

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

Дэн Смит
источник
4

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

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

GenericJam
источник
4

Хакер может полностью обойти браузер, включая проверку формы Javascript, отправив запрос через Telnet. Конечно, он будет смотреть на код вашей html-страницы, чтобы получить имена полей, которые он должен использовать, но с тех пор для него «все идет». Таким образом, вы должны проверить все значения, отправленные на сервер, как если бы они не были получены с вашей html-страницы.

пользователь1452686
источник