Защищают ли htmlspecialchars и mysql_real_escape_string мой PHP-код от внедрения?

116

Ранее сегодня был задан вопрос о стратегиях проверки ввода в веб-приложениях .

Главный ответ на момент написания предполагает PHPпросто использовать htmlspecialcharsи mysql_real_escape_string.

Мой вопрос: всегда ли этого достаточно? Мы должны знать больше? Где ломаются эти функции?

Cheekysoft
источник

Ответы:

241

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

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

Представьте себе следующий SQL:

$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);

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

1 OR 1=1

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

SELECT fields FROM table WHERE id= 1 OR 1=1

Это прекрасный вектор SQL-инъекций, позволяющий злоумышленнику вернуть все строки. Или

1 or is_admin=1 order by id limit 1

который производит

SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1

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

Хотя эти функции полезны, их следует использовать с осторожностью. Вам необходимо убедиться, что все веб-входы в какой-то степени проверены. В этом случае мы видим, что нас могут использовать, потому что мы не проверили, что переменная, которую мы использовали в качестве числа, на самом деле была числовой. В PHP вам следует широко использовать набор функций для проверки того, что входные данные являются целыми числами, числами с плавающей запятой, буквенно-цифровыми и т. Д. Но когда дело доходит до SQL, обратите внимание на значение подготовленного оператора. Приведенный выше код был бы безопасным, если бы это был подготовленный оператор, поскольку функции базы данных знали бы, что 1 OR 1=1это недопустимый литерал.

Что касается htmlspecialchars(). Это собственное минное поле.

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

Во-первых, если вы находитесь внутри тега HTML, у вас большие проблемы. смотреть на

echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';

Мы уже внутри HTML-тега, поэтому нам не нужны <или>, чтобы делать что-нибудь опасное. Наш вектор атаки может быть простоjavascript:alert(document.cookie)

Теперь результирующий HTML выглядит как

<img src= "javascript:alert(document.cookie)" />

Атака проходит прямо.

Становится хуже. Зачем? потому что htmlspecialchars(при таком вызове) кодирует только двойные кавычки, а не одиночные. Итак, если бы у нас было

echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";

Наш злой злоумышленник теперь может вводить совершенно новые параметры

pic.png' onclick='location.href=xxx' onmouseover='...

дает нам

<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />

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

Даже если вы используете htmlspecialchars($string)не HTML-теги, вы все равно уязвимы для векторов атаки с использованием многобайтовых кодировок.

Наиболее эффективным может быть использование комбинации mb_convert_encoding и htmlentities следующим образом.

$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');

Даже это оставляет IE6 уязвимым из-за способа обработки UTF. Однако вы можете вернуться к более ограниченной кодировке, такой как ISO-8859-1, пока использование IE6 не прекратится.

Для более глубокого изучения многобайтовых проблем см. Https://stackoverflow.com/a/12118602/1820.

Cheekysoft
источник
24
Единственное, что здесь упущено, это то, что первый пример запроса к БД ... простой intval () решит инъекцию. Всегда используйте intval () вместо mysqlescape ... (), когда требуется число, а не строка.
Роберт К.
11
и помните, что использование параметризованных запросов позволит вам всегда обрабатывать данные как данные, а не как код. Используйте библиотеку, такую ​​как PDO, и по возможности используйте параметризованные запросы.
Cheekysoft
9
Два замечания: 1. В первом примере вы будете в безопасности, если вы также заключите параметр в кавычки, например $result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";2. Во втором случае (атрибут, содержащий URL-адрес), для него вообще нет смысла htmlspecialchars; в этих случаях вы должны кодировать ввод с использованием схемы кодирования URL, например, используя rawurlencode. Таким образом, пользователь не сможет вставить javascript:et al.
Марсель Корпель,
7
«Htmlspecialchars кодирует только двойные кавычки, а не одинарные»: это неверно, это зависит от установленных флагов, см. Его параметры .
Марсель Корпель
2
Это должно быть выделено жирным шрифтом: в Take a whitelist approach and only let through the chars which are good.черном списке всегда что-то упускается. +1
Jo Smo
10

В дополнение к отличному ответу Cheekysoft:

  • Да, они будут держать вас в безопасности, но только при абсолютно правильном использовании. Используйте их неправильно, и вы по-прежнему будете уязвимы, и у вас могут быть другие проблемы (например, повреждение данных)
  • Вместо этого используйте параметризованные запросы (как указано выше). Вы можете использовать их, например, через PDO или через оболочку, такую ​​как PEAR DB.
  • Убедитесь, что magic_quotes_gpc и magic_quotes_runtime выключены постоянно и никогда не включаются случайно, даже ненадолго. Это ранняя и глубоко ошибочная попытка разработчиков PHP предотвратить проблемы с безопасностью (которые уничтожают данные).

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

В HTML вещи нужно экранировать по-разному в зависимости от контекста. Это особенно верно для строк, помещаемых в Javascript.

MarkR
источник
3

Я определенно согласен с приведенными выше сообщениями, но у меня есть одна небольшая вещь, которую я могу добавить в ответ на ответ Cheekysoft, а именно:

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

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

Представьте себе следующий SQL:

$ result = "ВЫБРАТЬ поля ИЗ таблицы WHERE id =" .mysql_real_escape_string ($ _ POST ['id']);

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

1 ИЛИ 1 = 1

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

ВЫБЕРИТЕ поля ИЗ таблицы WHERE id = 1 OR 1 = 1

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

function Numbers($input) {
  $input = preg_replace("/[^0-9]/","", $input);
  if($input == '') $input = 0;
  return $input;
}

Поэтому вместо использования

$ result = "ВЫБРАТЬ поля ИЗ таблицы WHERE id =" .mysqlrealescapestring ("1 OR 1 = 1");

я хотел бы использовать

$ result = "ВЫБРАТЬ поля ИЗ таблицы WHERE id =" .Numbers ("1 OR 1 = 1");

и он бы безопасно запустил запрос

ВЫБЕРИТЕ поля ИЗ таблицы WHERE id = 111

Конечно, это просто остановило отображение правильной строки, но я не думаю, что это большая проблема для тех, кто пытается внедрить sql на ваш сайт;)

BrilliantWinter
источник
1
Отлично! Это как раз то, что вам нужно. Первоначальный код не удался, потому что он не подтвердил, что число было числовым. Ваш код делает это. вы должны вызывать Numbers () для всех целочисленных переменных, значения которых происходят из-за пределов кодовой базы.
Cheekysoft,
1
Стоит упомянуть, что intval () отлично подойдет для этого, поскольку PHP автоматически преобразует целые числа в строки за вас.
Адам Эрнст,
11
Я предпочитаю intval. 1abc2 превращается в 1, а не в 12.
jmucchiello 05
1
intval лучше, особенно по ID. В большинстве случаев, если он был поврежден, то, как указано выше, 1 или 1 = 1. Вы действительно не должны сливать чужие ID. Таким образом, intval вернет правильный идентификатор. После этого вы должны проверить, совпадают ли исходные и очищенные значения. Это отличный способ не только остановить атаки, но и найти нападающих.
triunenature
2
Неправильная строка будет катастрофой, если вы показываете личные данные, вы увидите информацию другого пользователя! вместо этого было бы лучше проверитьreturn preg_match('/^[0-9]+$/',$input) ? $input : 0;
Фрэнк Форте
2

Важная часть этой головоломки - это контексты. Кто-то отправляет «1 OR 1 = 1» в качестве идентификатора, это не проблема, если вы процитируете каждый аргумент в своем запросе:

SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"

Что приводит к:

SELECT fields FROM table WHERE id='1 OR 1=1'

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

Лукас Оман
источник
15
а затем я начну свой вектор атаки с многобайтового символа 0xbf27, который в вашей базе данных latin1 будет преобразован функцией фильтра как 0xbf5c27 - это один многобайтовый символ, за которым следует одна кавычка.
Cheekysoft,
8
Старайтесь не защищаться от одного известного вектора атаки. В конечном итоге вы будете преследовать свой хвост до конца времени, применяя патч за патчем к вашему коду. Если остановиться и посмотреть на общие случаи, это приведет к более безопасному коду и лучшему мышлению, ориентированному на безопасность.
Cheekysoft,
Я согласен; в идеале OP будет использовать подготовленные операторы.
Лукас Оман
1
Хотя цитирование аргументов, предложенных в этом сообщении, не является надежным, оно смягчит многие из распространенных атак типа 1 ИЛИ 1 = 1, поэтому его стоит упомянуть.
Night Owl
2
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];

Хорошо работает, даже лучше на 64-битных системах. Остерегайтесь ограничений вашей системы на адресацию больших чисел, но для идентификаторов баз данных это отлично работает в 99% случаев.

Вы также должны использовать одну функцию / метод для очистки ваших значений. Даже если эта функция всего лишь оболочка для mysql_real_escape_string (). Зачем? Потому что однажды, когда будет обнаружен эксплойт к предпочитаемому вами методу очистки данных, вам нужно будет обновить его только в одном месте, а не проводить поиск и замену в масштабе всей системы.

cnizzardini
источник
-3

почему, о ПОЧЕМУ, ты бы не включили кавычки вокруг пользовательского ввода в свой оператор sql? кажется довольно глупым не делать этого! включение кавычек в ваш sql-оператор сделало бы "1 или 1 = 1" бесплодной попыткой, не так ли?

Итак, теперь вы скажете: «Что, если пользователь включит кавычки (или двойные кавычки) во входные данные?»

Что ж, это легко исправить: просто удалите кавычки, введенные пользователем. например: input =~ s/'//g;. теперь, как мне кажется, ввод пользователя будет защищен ...

Джаретт Л
источник
«почему, о, ПОЧЕМУ, вы бы не включили кавычки вокруг пользовательского ввода в ваш оператор sql?» - Вопрос ничего не говорит о том, что пользователь не цитирует вводимые данные.
Quentin
1
"ну, это легко исправить" - ужасное исправление для этого. Это отбрасывает данные. Решение, упомянутое в самом вопросе, - лучший подход.
Quentin
Хотя я согласен, что вопрос не касается цитирования ввода пользователя, все же кажется, что не цитировать ввод. и я бы предпочел выбросить данные, чем вводить неверные данные. как правило, при инъекционной атаке вам все равно НЕ нужны эти данные .... верно?
Jarett L
«хотя я согласен, что вопрос не касается цитирования ввода пользователя, все же кажется глупым не цитировать ввод». - Нет, это не так. Вопрос не демонстрирует это так или иначе.
Квентин
1
@JarettL Либо привыкните использовать заранее подготовленные операторы, либо привыкните к тому, что Bobby Tables разрушает ваши данные каждый вторник . Параметризованный SQL - это единственный лучший способ защитить себя от SQL-инъекций. Вам не нужно делать «проверки SQL-инъекций», если вы используете подготовленный оператор. Их чрезвычайно легко реализовать (и, на мой взгляд, они делают код НАМНОГО проще для чтения), они защищают от различных особенностей конкатенации строк и внедрения sql, и, что самое главное, вам не нужно изобретать велосипед, чтобы реализовать его. ,
Siyual