Как я могу предотвратить внедрение SQL в PHP?

2641

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

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

Это потому, что пользователь может ввести что-то вроде value'); DROP TABLE table;--, и запрос будет выглядеть следующим образом:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

Что можно сделать, чтобы это не произошло?

Теги:
security
sql-injection

28 ответов

8678
Лучший ответ

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

В основном у вас есть два варианта:

  • Используя PDO (для любого поддерживаемого драйвера базы данных):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // do something with $row
    }
    
  • Использование MySQLi (для MySQL):

    $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
    $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
    $stmt->execute();
    
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // do something with $row
    }
    

Если вы подключаетесь к базе данных, отличной от MySQL, есть вторая опция, зависящая от драйвера, к которой вы можете обратиться (например, pg_prepare() и pg_execute() для PostgreSQL). PDO является универсальным вариантом.

Правильная настройка соединения

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

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

В приведенном выше примере режим ошибки не является строго необходимым, , но рекомендуется добавить его. Таким образом, script не остановится с Fatal Error, когда что-то пойдет не так. И это дает разработчику шанс catch любой ошибки (я), которые throw n как PDOException s.

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

Хотя вы можете установить charset в параметрах конструктора, важно отметить, что более старые версии PHP (< 5.3.6) молча игнорирует параметр charset в DSN.

Описание

Что происходит, так это то, что оператор SQL, который вы передаете в prepare, анализируется и компилируется сервером базы данных. Задав параметры (либо ?, либо именованный параметр, например :name в приведенном выше примере), вы указываете механизм базы данных, в который вы хотите включить фильтр. Затем, когда вы вызываете execute, подготовленный оператор объединяется со значениями параметров, которые вы указываете.

Важно то, что значения параметров объединены с скомпилированным оператором, а не с строкой SQL. SQL-инъекция работает путем обмана script, включая вредоносные строки, когда он создает SQL для отправки в базу данных. Поэтому, отправляя фактический SQL отдельно от параметров, вы ограничиваете риск того, что закончите то, чего не намеревались. Любые параметры, которые вы отправляете при использовании подготовленного оператора, будут обрабатываться только как строки (хотя механизм базы данных может сделать некоторую оптимизацию, поэтому, конечно, параметры могут также оказаться как числа). В приведенном выше примере, если переменная $name содержит 'Sarah'; DELETE FROM employees, результат будет просто поиском строки "'Sarah'; DELETE FROM employees", и вы не получите пустой стол.

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

О, и поскольку вы спросили о том, как это сделать для вставки, вот пример (с использованием PDO):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute(array('column' => $unsafeValue));

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

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

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

// Value whitelist
// $dir can only be 'DESC' otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}
  • 64
    Просто добавьте, потому что я не видел этого нигде здесь, другая линия защиты - это брандмауэр веб-приложений (WAF), в котором могут быть установлены правила для поиска SQL-атак:
  • 32
    Кроме того, официальная документация mysql_query позволяет выполнять только один запрос, так что любой другой запрос, кроме; игнорируется Даже если это уже устарело, в PHP 5.5.0 есть много систем, которые могут использовать эту функцию. php.net/manual/en/function.mysql-query.php
Показать ещё 7 комментариев
1568

Предупреждение. В этом примере кода ответа (например, в примере кода вопроса) используется расширение PHP MySQL, которое устарело в PHP 5.5.0 и полностью удалено в PHP 7.0.0.

Если вы используете последнюю версию PHP, опция mysql_real_escape_string, mysql_real_escape_string ниже, больше не будет доступна (хотя mysqli::escape_string является современным эквивалентом). В наши дни опция mysql_real_escape_string имеет смысл только для устаревшего кода на старой версии PHP.


У вас есть два варианта - экранирование специальных символов в unsafe_variable или использование параметризованного запроса. Оба защитят вас от внедрения SQL. Параметризованный запрос считается лучшей практикой, но перед его использованием потребуется перейти на более новое расширение MySQL в PHP.

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

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

Смотрите также подробности функции mysql_real_escape_string.

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

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

Ключевой функцией, о которой вы захотите прочитать, будет mysqli::prepare.

Кроме того, как и предлагали другие, вам может быть полезно/проще увеличить уровень абстракции с помощью чего-то вроде PDO.

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

  • Если вы хотите изменить структуру SQL на основе пользовательского ввода, параметризованные запросы не помогут, и требуемый mysql_real_escape_string не покрывается mysql_real_escape_string. В этом случае вам лучше передать пользовательский ввод через белый список, чтобы обеспечить пропуск только "безопасных" значений.
  • Если вы используете целые числа из пользовательского ввода в условии и используете подход mysql_real_escape_string, вы будете страдать от проблемы, описанной Polynomial в комментариях ниже. Этот случай сложнее, потому что целые числа не будут заключены в кавычки, так что вы могли бы справиться, проверив, что пользовательский ввод содержит только цифры.
  • Есть, вероятно, другие случаи, о которых я не знаю. Вы можете обнаружить, что это полезный ресурс по некоторым из более тонких проблем, с которыми вы можете столкнуться.
  • 1
    использование mysql_real_escape_string достаточно или я должен использовать параметризованный тоже?
  • 5
    @peimanF. придерживайтесь хорошей практики использования параметризованных запросов даже в локальном проекте. При параметризованных запросах вы гарантированно не будете внедрять SQL. Но имейте в виду, что вы должны санировать данные, чтобы избежать ложного поиска (например, XSS-инъекций, таких как размещение HTML-кода в тексте), например, htmlentities
Показать ещё 2 комментария
943

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

  • строка
  • число
  • идентификатор
  • синтаксическое ключевое слово.

И подготовленные заявления охватывают только два из них.

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

В общем, такой подход к защите основан на белых списках.

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

$orders  = array("name", "price", "qty"); // Field names
$key     = array_search($_GET['sort'], $orders)); // See if we have such a name
$orderby = $orders[$key]; // If not, first one will be set automatically. smart enuf :)
$query   = "SELECT * FROM 'table' ORDER BY $orderby"; // Value is safe

Однако есть еще один способ защитить идентификаторы - экранирование. Пока у вас есть идентификатор в кавычках, вы можете избежать обратных кавычек внутри, удвоив их.

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

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

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

Тем не менее, существует проблема с ключевыми словами синтаксиса SQL (такими как AND, DESC и т.д.), Но в этом случае единственным подходом является белый список.

Обновить

Несмотря на то, что существует общее согласие с лучшими практиками, касающимися защиты от SQL-инъекций, все еще существует много плохих практик. И некоторые из них слишком глубоко укоренились в сознании пользователей PHP. Например, на этой самой странице есть (хотя и невидимые для большинства посетителей) более 80 удаленных ответов - все они удалены сообществом из-за плохого качества или из-за пропаганды плохой и устаревшей практики. Хуже того, некоторые плохие ответы не удаляются, а процветают.

Например, (1) есть (2) еще (3) много (4) ответов (5), включая второй ответ с наибольшим количеством голосов, предлагающий вам ручное экранирование строк - устаревший подход, который, как оказалось, небезопасен.

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

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

Независимо от того, что руководство PHP говорило целую вечность, *_escape_string ни в коем случае не делает данные безопасными и никогда не предназначалось для них. Помимо бесполезности для любой части SQL, кроме строки, ручное экранирование является неправильным, поскольку оно является ручным, а не автоматическим.

И OWASP делает это еще хуже, подчеркивая необходимость избегать пользовательского ввода, что является полной ерундой: в контексте защиты от инъекций не должно быть таких слов. Каждая переменная потенциально опасна - независимо от источника! Или, другими словами - каждая переменная должна быть правильно отформатирована, чтобы ее можно было вставить в запрос - независимо от источника снова. Это пункт назначения, который имеет значение. В тот момент, когда разработчик начинает отделять овец от коз (думая, является ли какая-то конкретная переменная "безопасной" или нет), он/она делает свой первый шаг к катастрофе. Не говоря уже о том, что даже формулировка предполагает массовый выход в точке входа, напоминающий очень магическую функцию кавычек - уже презирали, осуждали и удаляли.

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

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

  • 10
    Отличная, хорошо продуманная статья. Я мог бы добавить, что использование фильтров Sanitize в PHP является своего рода (но не совсем) белым списком. Например, FILTER_SANITIZE_NUMBER_INT допускает только FILTER_SANITIZE_NUMBER_INT символы, тем самым символы белого списка, а не целые строки. В сочетании с подготовленными заявлениями, это делает хороший подход "пояс и подтяжки".
  • 21
    @Sablefoste вам не нужно белый список здесь. Любая санитарная обработка будет излишней. Чем меньше правил, тем меньше ошибок ты сделаешь. Хотя вы можете делать любые проверки, делайте это ради логики вашего приложения, но не для базы данных.
752

Я бы рекомендовал использовать PDO (объекты данных PHP) для запуска параметризованных SQL-запросов.

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

И используя функции PDO, а не mysql_, mysqli_ и pgsql_, вы делаете свое приложение немного более абстрактным из базы данных, в редких случаях, когда вам приходится переключать поставщиков баз данных.

  • 2
    PDO не переносит mysqli для баз данных MySQL? В этом случае, конечно, это не может быть быстрее, чем MySQL. Я все еще рекомендую это все же. Это гораздо лучший интерфейс, чем MySQL API.
  • 4
    Использование параметризованных запросов - вот что ускоряет запросы. Технически mysqli может быть еще быстрее с очень небольшим отрывом. Фактическое количество времени, которое требуется серверу для ответа на запрос, затмевает любую разницу во времени, которая может возникнуть из-за использования оболочки. Но mysqli привязан к базе данных. Если вы хотите использовать другой движок базы данных, вы должны изменить все вызовы, которые используют mysqli. Не так для PDO.
Показать ещё 1 комментарий
615

Используйте PDO и подготовленные запросы.

($conn - объект PDO)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();
  • 8
    Из википедии : Подготовленные операторы устойчивы к внедрению SQL, потому что значения параметров, которые передаются позже с использованием другого протокола, не должны быть правильно экранированы. Если исходный шаблон оператора не является производным от внешнего ввода, внедрение SQL невозможно.
495

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

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

Мой подход:

  • Если вы ожидаете, что ввод будет целым, убедитесь, что оно действительно целое. В языке с переменным типом, таком как PHP, это очень важно. Вы можете использовать, например, это очень простое, но мощное решение: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • Если вы ожидаете чего-либо еще от целого числа hex it. Если вы отбросите его, вы полностью избежите ввода. В C/С++ существует функция, называемая mysql_hex_string(), в PHP вы можете использовать bin2hex().

    Не беспокойтесь о том, что строка с экранированной строкой будет иметь размер в 2 раза ее первоначальной длины, потому что, даже если вы используете mysql_real_escape_string, PHP должен выделять ту же емкость ((2*input_length)+1), что то же самое.

  • Этот шестнадцатеричный метод часто используется при передаче двоичных данных, но я не вижу причин, почему бы не использовать его во всех данных для предотвращения атак SQL-инъекций. Обратите внимание, что вам необходимо добавить данные с помощью 0x или использовать функцию MySQL UNHEX.

Итак, например, запрос:

SELECT password FROM users WHERE name = 'root'

Станет:

SELECT password FROM users WHERE name = 0x726f6f74

или

SELECT password FROM users WHERE name = UNHEX('726f6f74')

Hex - идеальный побег. Никоим образом не вводить.

Разница между функцией UNHEX и префиксом 0x

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

Префикс ** 0x ** может использоваться только для столбцов данных, таких как char, varchar, text, block, binary и т.д..
Кроме того, его использование немного сложно, если вы собираетесь вставить пустую строку. Вам придется полностью заменить его на '', или вы получите сообщение об ошибке.

UNHEX() работает в столбце any; вам не нужно беспокоиться о пустой строке.


Hex-методы часто используются в качестве атак

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

Например, если вы просто делаете что-то вроде этого:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

атака может ввести вас очень легко. Рассмотрим следующий введенный код, возвращенный с вашего script:

SELECT... WHERE id = -1 union all select table_name from information_schema.tables

а теперь просто извлеките структуру таблицы:

SELECT... WHERE id = -1 union all select column_name from information_schema.column, где table_name = 0x61727469636c65

И затем просто выберите нужные данные. Разве это не круто?

Но если кодер инъекционного сайта будет его шестнадцатеричным, инъекция не будет возможна, потому что запрос будет выглядеть следующим образом: SELECT ... WHERE id = UNHEX('2d312075...3635')

  • 0
    @Zaffy, мне нравится идея, но как насчет производительности, я имею в виду, если у вас есть 1 миллион записей и 1000 пользователей ищут, замедляется ли это по сравнению с подготовкой решения?
  • 0
    Я просто проверяю SELECT * FROM tblproducts ГДЕ product_code LIKE ('% 42%') находит запись, а SELECT * FROM tblproducts product_code LIKE ('%' + 0x3432 + '%') не делает, поэтому просто не работает или я что то не так сделал?
Показать ещё 21 комментарий
450

Предупреждение безопасности: этот ответ не соответствует рекомендациям по безопасности. Экранирование не подходит для предотвращения внедрения SQL, вместо этого используйте подготовленные операторы. Используйте стратегию, изложенную ниже, на свой страх и риск. (Кроме того, mysql_real_escape_string() была удалена в PHP 7.)

Устаревшее предупреждение: в настоящее время расширение mysql устарело. мы рекомендуем использовать расширение mysqli

Вы можете сделать что-то простое, как это:

$safe_variable = mysqli_real_escape_string($_POST["user-input"]);
mysqli_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

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

  • 27
    Я попробовал ваш пример, и он отлично работает для меня. Не могли бы вы пояснить, «это не решит все проблемы»
  • 13
    Если вы не цитируете строку, она все еще может быть введена. Возьмите $q = "SELECT col FROM tbl WHERE x = $safe_var"; например. Установка $safe_var в 1 UNION SELECT password FROM users работает в этом случае из-за отсутствия кавычек. Также возможно ввести строки в запрос, используя CONCAT и CHR .
Показать ещё 7 комментариев
447

ВАЖНО

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

Существуют библиотеки, такие как Aura.Sql и EasyDB, которые позволяют разработчикам легче использовать подготовленные заявления. Чтобы узнать больше о том, почему подготовленные утверждения лучше на остановить SQL-инъекцию, обратитесь к этому mysql_real_escape_string() обходному и недавно зафиксированные уязвимости Unicode SQL Injection в WordPress.

Предотвращение инъекций - mysql_real_escape_string()

У PHP есть специально созданная функция для предотвращения этих атак. Все, что вам нужно сделать, это использовать функцию функции, mysql_real_escape_string.

mysql_real_escape_string берет строку, которая будет использоваться в запросе MySQL и возвращает ту же строку, при которой все попытки SQL-инъекций безопасно экранированы. В принципе, он заменит эти неприятные кавычки ('), которые пользователь может ввести с помощью заменителя в MySQL, сэкономленной цитатой.

ПРИМЕЧАНИЕ. вы должны подключиться к базе данных, чтобы использовать эту функцию!

//Подключение к MySQL

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

Подробнее вы можете найти в MySQL - SQL Injection Prevention.

  • 29
    Это лучшее, что вы можете сделать с устаревшим расширением mysql. Для получения нового кода рекомендуется переключиться на mysqli или PDO.
  • 7
    Я не согласен с этой «специально сделанной функцией для предотвращения этих атак». Я думаю, что цель mysql_real_escape_string в том, чтобы позволить построить правильный SQL-запрос для каждой входной строки данных. Профилактика sql-инъекций является побочным эффектом этой функции.
Показать ещё 6 комментариев
345

Параметрированный запрос И проверка ввода - это путь. Существует множество сценариев, в которых может произойти SQL-инъекция, даже если используется mysql_real_escape_string().

Эти примеры уязвимы для SQL-инъекций:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

или

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

В обоих случаях вы не можете использовать ' для защиты инкапсуляции.

Источник: Неожиданная инъекция SQL (когда Escaping недостаточно)

  • 1
    Вы можете предотвратить внедрение SQL, если примените метод проверки входных данных, в котором пользовательский ввод аутентифицируется в соответствии с набором определенных правил для длины, типа и синтаксиса, а также в соответствии с бизнес-правилами.
344

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

  • 9
    В самом деле; работа с включенными magic_quotes только поощряет плохую практику. Однако иногда вы не всегда можете контролировать среду до этого уровня - либо у вас нет доступа для управления сервером, либо ваше приложение должно сосуществовать с приложениями, которые (дрожат) зависят от такой конфигурации. По этим причинам хорошо писать переносимые приложения - хотя очевидно, что усилия будут потрачены впустую, если вы действительно управляете средой развертывания, например, потому что это собственное приложение или будет использоваться только в вашей конкретной среде.
  • 21
    Начиная с PHP 5.4, мерзость, известная как «магические кавычки», была убита мертвым . И хорошее избавление от плохого мусора.
288

Существует множество способов предотвращения SQL-инъекций и других SQL-хаков. Вы можете легко найти его в Интернете (Google Search). Конечно, PDO - одно из хороших решений. Но я хотел бы предложить вам некоторые хорошие ссылки для предотвращения SQL Injection.

Что такое SQL-инъекция и как предотвратить

Руководство по PHP для SQL-инъекции

Microsoft объясняет SQL-инъекцию и предотвращение в PHP

и некоторые другие, такие как Предотвращение SQL-инъекций с MySQL и PHP

Теперь, , почему вы делаете, вам нужно предотвратить запрос из SQL-инъекции?

Я хотел бы сообщить вам: почему мы пытаемся предотвратить SQL-инъекцию коротким примером ниже:

Запрос для аутентификации входа:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

Теперь, если кто-то (хакер) ставит

$_POST['email']= [email protected]' OR '1=1

и пароль ничего....

Запрос будет анализироваться в системе только до:

$query="select * from users where email='[email protected]' OR '1=1';

Другая часть будет отброшена. Итак, что будет? Неавторизованный пользователь (хакер) сможет войти в систему как администратор без своего пароля. Теперь он может делать все, что может сделать администратор/адрес электронной почты. Смотрите, это очень опасно, если SQL-инъекция не предотвращается.

280

На мой взгляд, лучший способ вообще запретить SQL-инъекцию в вашем приложении PHP (или любом веб-приложении, если на то пошло) - это думать о вашей архитектуре приложения. Если единственный способ защитить от SQL-инъекции - не забудьте использовать специальный метод или функцию, которая делает The Right Thing каждый раз, когда вы разговариваете с базой данных, вы делаете это неправильно. Таким образом, это всего лишь вопрос времени, пока вы не забудете правильно отформатировать свой запрос в какой-то момент вашего кода.

Принятие шаблона MVC и структуры, например CakePHP или CodeIgniter, вероятно, правильный путь: общие задачи, такие как создание безопасных запросов к базе данных, были решены и централизованно реализованы в таких рамках. Они помогают организовать ваше веб-приложение разумным образом и заставляют вас больше думать о загрузке и сохранении объектов, а не о безопасном построении отдельных SQL-запросов.

  • 4
    Я думаю, что ваш первый абзац важен. Понимание является ключевым. Кроме того, не все работают на компанию. Для большого количества людей рамки фактически идут вразрез с идеей понимания . Быть близким к основам, возможно, не ценится во время работы в сжатые сроки, но любители делать это любят пачкать руки. Разработчики фреймворка не настолько привилегированы, что все остальные должны поклониться и предположить, что они никогда не делают ошибок. Способность принимать решения по-прежнему важна. Кто скажет, что моя структура не заменит какую-то другую схему в будущем?
  • 0
    @AnthonyRutledge Вы абсолютно правы. Очень важно понимать, что происходит и почему. Однако вероятность того, что действительно проверенная и активно используемая и разработанная инфраструктура натолкнулась на множество проблем и исправила множество дыр в безопасности, уже достаточно высока. Это хорошая идея, чтобы взглянуть на источник, чтобы почувствовать качество кода. Если это непроверенный беспорядок, это, вероятно, не безопасно.
Показать ещё 3 комментария
279

Я пользуюсь хранимыми процедурами (У MySQL была поддержка хранимых процедур с 5.0) с точки зрения безопасности - преимущества -

  • Большинство баз данных (включая MySQL) позволяют ограничить доступ пользователей к выполнению хранимых процедур. Четкое управление доступом к безопасности полезно для предотвращения эскалации атак привилегий. Это предотвращает возможность взлома приложений, которые могут быть запущены непосредственно с базой данных.
  • Они абстрагируют необработанный SQL-запрос из приложения, поэтому для приложения доступно меньше информации о структуре базы данных. Это затрудняет понимание людьми базовой структуры базы данных и разработку подходящих атак.
  • Они принимают только параметры, поэтому существуют преимущества параметризованных запросов. Конечно, IMO вам все равно нужно дезинфицировать ваш вход, особенно если вы используете динамический SQL внутри хранимой процедуры.

Недостатки -

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

Если возможно, введите типы параметров. Но он работает только на простых типах, таких как int, bool и float.

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
  • 2
    Это один из немногих случаев, когда я использовал бы «экранированное значение» вместо подготовленного выражения. И целочисленное преобразование типов чрезвычайно эффективно.
240

Я думаю, что если кто-то захочет использовать PHP и MySQL или какой-нибудь другой сервер базы данных:

  • Подумайте об обучении PDO (объекты данных PHP) - это уровень доступа к базе данных, обеспечивающий единый метод доступа к нескольким базам данных.
  • Подумайте об обучении MySQLi
  • Используйте собственные PHP-функции, такие как strip_tags, mysql_real_escape_string или если переменная числовая, просто (int)$foo. Подробнее о типах переменных в PHP читайте здесь. Если вы используете библиотеки, такие как PDO или MySQLi, всегда используйте PDO:: quote() и mysqli_real_escape_string().

Примеры библиотек:

---- PDO

----- Без заполнителей - спелые для SQL-инъекций! Плохо

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- Без названия заполнители

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- Именованные заполнители

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

P.S

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

Но хотя PDO и MySQLi довольно быстры, MySQLi выполняет незначительно быстрее в контрольных показателях - ~ 2,5% для незаготовленных и ~ 6.5% для подготовленных.

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

223

Для тех, кто не знает, как использовать PDO (исходя из функций mysql_), я сделал очень, очень простую PDO-оболочку, которая это один файл. Он существует, чтобы показать, насколько легко выполнять все обычные приложения, которые необходимо выполнить. Работает с PostgreSQL, MySQL и SQLite.

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

Я хочу один столбец

$count = DB::column('SELECT COUNT(*) FROM `user`);

Мне нужны результаты массива (key = > value) (т.е. для создания selectbox)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

Я хочу получить результат одной строки

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

Я хочу получить массив результатов

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));
222

Если вы хотите использовать механизмы кэширования, например Redis или Memcached, возможно, DALMP может быть выбором. Он использует чистый MySQLi. Проверьте это: Уровень абстракции базы данных DALMP для MySQL с использованием PHP.

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

202

Несколько рекомендаций по экранированию специальных символов в операторах SQL.

Не используйте MySQL, это расширение устарело, используйте MySQLi или PDO.

MySQLi

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

Пример:

$mysqli = new mysqli( 'host', 'user', 'password', 'database' );
$mysqli->set_charset( 'charset');

$string = $mysqli->real_escape_string( $string );
$mysqli->query( "INSERT INTO table (column) VALUES ('$string')" );

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

Пример:

$stmt = $mysqli->prepare( "INSERT INTO table ( column1, column2 ) VALUES (?,?)" );

$stmt->bind_param( "is", $integer, $string );

$stmt->execute();

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

Итак, если вы используете подготовленный оператор, вы должны указать типы переменных для функции mysqli_stmt_bind_param.

И использование mysqli_real_escape_string для, как сказано в названии, означает экранирование специальных символов в строке, поэтому оно не сделает целые числа безопасными. Цель этой функции - предотвратить разрыв строк в операторах SQL и повреждение базы данных, которое она может вызвать. mysqli_real_escape_string - полезная функция при правильном использовании, особенно в сочетании с sprintf.

Пример:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer );

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer );

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647
  • 3
    Вопрос очень общий. Некоторые отличные ответы выше, но большинство предлагают готовые заявления. MySQLi async не поддерживает подготовленные операторы, поэтому sprintf выглядит как отличный вариант для этой ситуации.
200

Используя эту функцию PHP mysql_escape_string(), вы можете быстро получить хорошую профилактику.

Например:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string - Сбрасывает строку для использования в mysql_query

Для большей профилактики вы можете добавить в конце...

wHERE 1=1   or  LIMIT 1

Наконец, вы получите:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1
184

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

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

Это ограничит пользователя только ограниченным указанным запросом. Удалите разрешение на удаление, чтобы данные никогда не удалялись из запроса, запущенного со страницы PHP. Второе, что нужно сделать, это сбросить привилегии, чтобы MySQL обновил разрешения и обновления.

FLUSH PRIVILEGES; 

больше информации о флеше.

Чтобы увидеть текущие привилегии для пользователя, запустите следующий запрос.

select * from mysql.user where User='username';

Узнайте больше о GRANT.

  • 23
    Этот ответ по сути неправильный , так как он не помогает предотвратить профилактику инъекций, а просто пытается смягчить последствия. Напрасно.
  • 0
    Да, это не решение, но то, что вы можете сделать заранее, чтобы избежать проблем.
Показать ещё 6 комментариев
171

Простым способом было бы использовать фреймворк PHP, например CodeIgniter или Laravel, которые имеют встроенные функции, такие как фильтрация и активная запись, поэтому вам не нужно беспокоиться об этих нюансах.

  • 7
    Я думаю, что весь вопрос заключается в том, чтобы сделать это без использования такой основы.
158

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

  • Использование mysql_real_escape_string(), которое является предопределенной функцией в PHP, и этот код добавляет обратную косую черту к следующим символам: \x00, \n, \r, \, ', " и \x1a. Передайте входные значения в качестве параметров, чтобы свести к минимуму вероятность внедрения SQL.
  • Самый продвинутый способ - использование PDO.

Надеюсь, это поможет вам.

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

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string() не будет защищать здесь. Если вы используете одиночные кавычки ('') вокруг ваших переменных внутри вашего запроса, это то, что защищает вас от этого. Ниже приведено ниже решение:

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

В этом question есть хорошие ответы.

Я предлагаю, что использование PDO - лучший вариант.

Edit:

mysql_real_escape_string() устарел с PHP 5.5.0. Используйте либо mysqli, либо PDO.

Альтернативой mysql_real_escape_string() является

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Пример:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");
156

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

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

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

Фильтрация данных для (Преобразование небезопасных данных в безопасные данные). Считайте, что PDO и MySQLi недоступны, как вы можете защитить свое приложение? Вы заставляете меня использовать их? А как насчет других языков, кроме PHP? Я предпочитаю давать общие идеи, так как они могут быть использованы для более широкой границы, а не только для конкретного языка.

  1. Пользователь SQL (ограничение привилегий пользователя): наиболее распространенными операциями SQL являются (SELECT, UPDATE, INSERT), тогда зачем давать привилегию UPDATE пользователю, который в этом не нуждается? Например , страницы входа и поиска используют только SELECT, тогда зачем использовать пользователей БД на этих страницах с высокими привилегиями? ПРАВИЛО: не создавайте одного пользователя базы данных для всех привилегий, для всех операций SQL вы можете создать свою схему, такую как (deluser, selectuser, updateuser), в качестве имен пользователей для простоты использования.

см. принцип наименьших привилегий

  1. Фильтрация данных: перед созданием любого запроса пользовательский ввод должен быть проверен и отфильтрован, для программистов важно определить некоторые свойства для каждой пользовательской переменной ввода: тип данных, шаблон данных и длина данных. поле, которое является числом между (x и y), должно быть точно проверено с использованием точного правила, для поля, которое является строкой (текст): шаблон имеет место, например, имя пользователя должно содержать только несколько символов, скажем, [a- zA-Z0-9_-.] длина варьируется между (x и n), где x и n (целые числа, x <= n). Правило: создание точных фильтров и правил проверки - лучшая практика для меня.

  2. Используйте другие инструменты: здесь я также согласен с вами в том, что подготовленные операторы (параметризованный запрос) и хранимые процедуры, недостатками которых являются эти способы, требуют продвинутых навыков, которых нет у большинства пользователей, основная идея здесь заключается в том, чтобы различать SQL запрос и данные, которые используются внутри, оба подхода могут использоваться даже с небезопасными данными, потому что введенные пользователем данные здесь не добавляют ничего к исходному запросу, например (any или x = x). Для получения дополнительной информации, пожалуйста, прочтите OWASP Шпаргалку по предотвращению инъекций.

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

Наконец, давайте рассмотрим, что пользователь отправляет этот текст ниже вместо ввода своего имени пользователя:

[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

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

Последний пункт - обнаружение неожиданного поведения, которое требует больших усилий и сложности; это не рекомендуется для обычных веб-приложений. Неожиданное поведение при вводе пользователем выше: SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA, root, после того как эти слова обнаружены, вы можете избежать ввода.

Update1:

Пользователь прокомментировал, что этот пост бесполезен, хорошо! Вот что предоставил OWASP.ORG:

Основные защиты:

Вариант № 1: Использование подготовленных операторов (параметризованные запросы)
Вариант № 2: Использование хранимых процедур
Вариант № 3: экранирование всех вводимых пользователем данных

Дополнительные защиты:

Также Enforce: Наименьшая привилегия
Также выполнить: проверка ввода белого списка

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

Update2:

Из руководства по PHP, PHP: Подготовленные заявления - Руководство:

Экранирование и внедрение SQL

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

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

Update3:

Я создал контрольные примеры, чтобы узнать, как PDO и MySQLi отправляют запрос на сервер MySQL при использовании подготовленного оператора:

PDO:

$user = "''1''"; //Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));

Журнал запросов:

    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit

MySQLi:

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();

Журнал запросов:

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit

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

Как также упоминалось в приведенном выше утверждении, The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements, if input values are escaped correctly The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements, if input values are escaped correctly intval() The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements, if input values are escaped correctly, следовательно, это доказывает, что проверка данных, такая как intval() является хорошей идеей для целочисленных значений перед отправкой любого запроса, кроме того, предотвращая злонамеренные действия. Данные пользователя перед отправкой запроса верны и верны.

Пожалуйста, смотрите этот вопрос для более подробной информации: PDO отправляет необработанный запрос в MySQL, в то время как Mysqli отправляет подготовленный запрос, оба дают одинаковый результат.

Рекомендации:

  1. Шпаргалка для SQL-инъекций
  2. SQL-инъекция
  3. Информационной безопасности
  4. Принципы безопасности
  5. Проверка данных
143

** Предупреждение: подход, описанный в этом ответе, применим только к очень конкретным сценариям и не является безопасным, поскольку атаки SQL-инъекций не только полагаются на возможность ввода X=Y. **

Если злоумышленники пытаются взломать форму с помощью переменной PHP $_GET или с помощью строки запроса URL, вы сможете поймать их, если они не защищены.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

Потому что 1=1, 2=2, 1=2, 2=1, 1+1=2 и т.д.... являются распространенными вопросами к базе данных SQL злоумышленника. Возможно, он также используется многими хакерскими приложениями.

Но вы должны быть осторожны, чтобы не переписывать безопасный запрос с вашего сайта. В приведенном выше коде вам дается подсказка, чтобы переписать или перенаправить (это зависит от вас) эту динамическую строку запроса, зависящую от хакерства, на страницу, в которой будет храниться злоумышленник IP-адрес или EVEN THEIR COOKIES, историю, браузер или любую другую конфиденциальную информацию, чтобы вы могли с ними справиться позже, запретив их учетную запись или контактные органы.

131

Есть так много ответов для PHP и MySQL, но вот код для PHP и Oracle для предотвращения SQL-инъекции, а также для регулярного использования драйверов oci8:

$conn = oci_connect($username, $password, $connection_string);
$stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
oci_bind_by_name($stmt, ':xx', $fieldval);
oci_execute($stmt);
  • 0
    Пожалуйста, объясните параметры oci_bind_by_name.
123

Использование PDO и MYSQLi хорошая практика для предотвращения инъекций SQL, но если вы действительно хотите работать с функциями и запросами MySQL, было бы лучше использовать

mysql_real_escape_string

$unsafe_variable = mysql_real_escape_string($_POST['user_input']);

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

is_string

$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');

is_numeric

$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');

И гораздо лучше использовать эти функции для проверки входных данных с помощью mysql_real_escape_string.

  • 9
    Также нет абсолютно никакого смысла проверять члены массива $ _POST с помощью is_string ()
  • 20
    ПРЕДУПРЕЖДЕНИЕ! mysql_real_escape_string() не является непогрешимым .
Показать ещё 2 комментария
116

Хорошей идеей является использование 'объектно-реляционного mapper', например Idiorm:

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

Это не только избавляет вас от SQL-инъекций, но и от синтаксических ошибок! Также поддерживает коллекции моделей с цепочкой методов для фильтрации или применения действий к нескольким результатам одновременно и нескольких подключений.

89

Я написал эту небольшую функцию несколько лет назад:

function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\ type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

Это позволяет запускать операторы в однострочном С# -и String.Format, например:

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

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

ОБНОВЛЕНИЕ БЕЗОПАСНОСТИ: предыдущая версия str_replace допускала инъекции, добавляя токены {#} в пользовательские данные. Эта версия preg_replace_callback не вызывает проблем, если замена содержит эти токены.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню