Вопрос:

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

php mysql sql security sql-injection

1478166 просмотра

28 ответа

13345 Репутация автора

Если пользовательский ввод вставлен без изменений в 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;--')

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

Автор: Andrew G. Johnson Источник Размещён: 12.09.2008 11:55

Ответы (28)


786 плюса

51159 Репутация автора

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

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

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

Автор: Kibbee Размещён: 13.09.2008 12:02

424 плюса

5193 Репутация автора

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

Вы могли бы сделать что-то основное:

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

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

Автор: Tanerax Размещён: 13.09.2008 12:15

1548 плюса

66424 Репутация автора

Предупреждение: примерный код ответа (например, примерный код вопроса) использует mysqlрасширение PHP , которое устарело в PHP 5.5.0 и полностью удалено в PHP 7.0.0.

Если вы используете последнюю версию PHP, 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подход, вы столкнетесь с проблемой, описанной Polynomial в комментариях ниже. Этот случай более сложный, поскольку целые числа не будут окружены кавычками, поэтому вы можете справиться, подтвердив, что пользовательский ввод содержит только цифры.
  • Вероятно, есть другие случаи, о которых я не знаю. Вы можете обнаружить, что это полезный ресурс по некоторым из более тонких проблем, с которыми вы можете столкнуться.
Автор: Matt Sheppard Размещён: 13.09.2008 09:48

8160 плюса

113191 Репутация автора

Решение

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

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

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

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // do something with $row
    }
    
  2. Использование 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);

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

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

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

объяснение

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

Важно то, что значения параметров объединены с скомпилированным оператором, а не с строкой SQL. SQL-инъекция работает, обманывая сценарий, включая вредоносные строки, когда он создает 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';
}
Автор: Theo Размещён: 13.09.2008 12:30

582 плюса

42853 Репутация автора

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

( $connявляется PDOобъектом)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();
Автор: Imran Размещён: 13.09.2008 01:20

357 плюса

43378 Репутация автора

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

Автор: Rob Размещён: 08.12.2008 05:26

275 плюса

3362 Репутация автора

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

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

Недостатки -

  1. Они (хранимые процедуры) трудно поддерживать и имеют тенденцию к быстрому размножению. Это делает их проблемой.
  2. Они не очень подходят для динамических запросов - если они созданы для принятия динамического кода в качестве параметров, тогда многие преимущества сбрасываются.
Автор: Nikhil Размещён: 03.11.2009 06:05

468 плюса

8524 Репутация автора

ВАЖНЫЙ

Лучший способ предотвратить 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 .

Автор: rahularyansharma Размещён: 17.06.2011 04:00

341 плюса

2925 Репутация автора

Параметризованный запрос и проверка ввода - это путь. Существует множество сценариев, в которых может произойти 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 недостаточно)

Автор: Cedric Размещён: 03.07.2011 09:50

973 плюса

1 Репутация автора

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

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

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

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

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

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

$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) , в том числе второго по upvoted ответа предлагая вам вручную строку вытекающей - устаревший подход , который , как доказано быть небезопасными.

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

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

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

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

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

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

Автор: Your Common Sense Размещён: 24.11.2011 09:50

237 плюса

2894 Репутация автора

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

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
Автор: devOp Размещён: 12.06.2012 08:03

291 плюса

30665 Репутация автора

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

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

Автор: Johannes Fahrenkrug Размещён: 03.07.2012 10:14

271 плюса

17688 Репутация автора

Существует множество способов предотвращения 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']= admin@emali.com' OR '1=1

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

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

$query="select * from users where email='admin@emali.com' OR '1=1';

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

Автор: Manish Shrivastava Размещён: 23.07.2012 10:19

211 плюса

1926 Репутация автора

Используя эту функцию 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
Автор: Nicolas Finelli Размещён: 03.08.2012 07:59

167 плюса

1848 Репутация автора

I use three different ways to prevent my web application from being vulnerable to SQL injection.

  1. Use of mysql_real_escape_string(), which is a pre-defined function in PHP, and this code add backslashes to the following characters: \x00, \n, \r, \, ', " and \x1a. Pass the input values as parameters to minimize the chance of SQL injection.
  2. The most advanced way is to use PDOs.

I hope this will help you.

Consider the following query:

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

mysql_real_escape_string() will not protect here. If you use single quotes (' ') around your variables inside your query is what protects you against this. Here is an solution below for this:

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

This question has some good answers about this.

I suggest, using PDO is the best option.

Edit:

mysql_real_escape_string() is deprecated as of PHP 5.5.0. Use either mysqli or PDO.

An alternative to mysql_real_escape_string() is

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Example:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");
Автор: Soumalya Banerjee Размещён: 14.09.2012 02:37

211 плюса

26809 Репутация автора

Для тех, кто не знает, как использовать 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));
Автор: Xeoncross Размещён: 19.09.2012 06:10

516 плюса

11768 Репутация автора

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

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

Мой подход:

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

  • Если вы ожидаете чего-либо еще от целого шестнадцатеричного значения . Если вы отбросите его, вы полностью избежите ввода. В C / 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 () работает в любой колонке; вам не нужно беспокоиться о пустой строке.


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

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

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

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

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

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')

Автор: Zaffy Размещён: 03.10.2012 02:07

249 плюса

4196 Репутация автора

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

  1. Подумайте об обучении PDO (объекты данных PHP) - это уровень доступа к базе данных, обеспечивающий единый метод доступа к нескольким базам данных.
  2. Подумайте об обучении MySQLi
  3. Используйте собственные функции 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();

PS :

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

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

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

Автор: RDK Размещён: 10.10.2012 02:53

219 плюса

11044 Репутация автора

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

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

Автор: nbari Размещён: 15.10.2012 01:52

171 плюса

2112 Репутация автора

The simple alternative to this problem could be solved by granting appropriate permissions in the database itself. For example: if you are using a mysql database then enter into the database through terminal or the UI provided and just follow this command:

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

This will restrict the user to only get confined with the specified query's only. Remove the delete permission and so the data would never get deleted from the query fired from the php page. The second thing to do is to flush the privileges so that the mysql refreshes the permissions and updates.

FLUSH PRIVILEGES; 

more information about flush.

To see the current privileges for the user fire the following query.

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

Learn more about GRANT.

Автор: Apurv Nerlekar Размещён: 25.10.2012 08:07

159 плюса

0 Репутация автора

Regarding many useful answers, I hope to add some values to this thread. SQL injection is an attack that can be done through user inputs (Inputs that filled by user and then used inside queries), The SQL injection patterns are correct query syntax while we can call it: bad queries for bad reasons, we assume that there might be a bad person that try to get secret information (bypassing access control) that affect the three principles of security (Confidentiality, Integrity, Availability).

Now, our point is to prevent security threats such as SQL injection attacks, the question asking (How to prevent SQL injection attack using PHP), be more realistic, data filtering or clearing input data is the case when using user-input data inside such query, using PHP or any other programming language is not the case, or as recommended by more people to use modern technology such as prepared statement or any other tools that currently supporting SQL injection prevention, consider that these tools not available anymore? How you secure your application?

My approach against SQL injection is: clearing user-input data before sending it to the database (before using it inside any query).

Data filtering for (Converting unsafe data to safe data) Consider that PDO and MySQLi not available, how can you secure your application? Do you force me to use them? What about other languages other than PHP? I prefer to provide general ideas as it can be used for wider border not just for specific language.

  1. SQL user (limiting user privilege): most common SQL operations are (SELECT, UPDATE, INSERT), then, why giving UPDATE privilege to a user that not require it? For example login, and search pages are only using SELECT, then, why using DB users in these pages with high privileges? RULE: do not create one database user for all privileges, for all SQL operations, you can create your scheme like (deluser, selectuser, updateuser) as usernames for easy usage.

see Principle of least privilege

  1. Data filtering: before building any query user input should be validated and filtered, for programmers, it's important to define some properties for each user-input variables: data type, data pattern, and data length. a field that is a number between (x and y) must be exactly validated using exact rule, for a field that is a string (text): pattern is the case, for example, username must contain only some characters lets say [a-zA-Z0-9_-.] the length varies between (x and n) where x and n (integers, x <=n ). Rule: creating exact filters and validation rules are best practice for me.

  2. Use other tools: Here, I will also agree with you that prepared statement (parametrized query) and Stored procedures, the disadvantages here is these ways requires advanced skills which do not exist for most users, the basic idea here is to distinguish between the SQL query and the data that is used inside, both approaches can be used even with unsafe data, because the user-input data here not add anything to the original query such as (any or x=x). For more information, please read OWASP SQL Injection Prevention Cheat Sheet.

Now, if you are an advanced user, start using this defense as you like, but, for beginners, if they can't quickly implement stored procedure and prepared the statement, it's better to filter input data as much they can.

Finally, let's consider that user sends this text below instead of entering his username:

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

This input can be checked early without any prepared statement and stored procedures, but to be on the safe side, using them starts after user-data filtering and validation.

The last point is detecting unexpected behavior which requires more effort and complexity; it's not recommended for normal web applications. Unexpected behavior in above user input is SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA, root once these words detected, you can avoid the input.

UPDATE1:

A user commented that this post is useless, OK! Here is what OWASP.ORG provided:

Primary defenses:

Option #1: Use of Prepared Statements (Parameterized Queries)
Option #2: Use of Stored Procedures
Option #3: Escaping all User Supplied Input

Additional defenses:

Also Enforce: Least Privilege
Also Perform: White List Input Validation

As you may know, claiming an article should be supported by valid argument, at least one reference! Otherwise, it's considered as an attack and bad claim!

Update2:

From the PHP manual, PHP: Prepared Statements - Manual:

Escaping and SQL injection

Связанные переменные автоматически экранируются сервером. Сервер вставляет свои экранированные значения в соответствующие места в шаблон оператора перед выполнением. Чтобы создать соответствующее преобразование, сервер должен указать подсказку для типа связанной переменной. Дополнительную информацию см. В функции 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, это доказывает, что проверка данных, такая как intval()хорошая идея для целочисленных значений перед отправкой любого запроса, кроме того, предотвращение вредоносных пользовательских данных перед отправкой запроса является правильным и допустимым подходом .

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

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

  1. Шифрование SQL Injection Cheat Sheet
  2. SQL-инъекция
  3. Информационной безопасности
  4. Принципы безопасности
  5. Проверка данных
Автор: user1646111 Размещён: 28.01.2013 07:37

134 плюса

2307 Репутация автора

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

Если злоумышленники пытаются взломать форму через $_GETпеременную PHP или с помощью строки запроса 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, историю, браузер или любые другие чувствительные информации, поэтому вы можете иметь дело с ними позже, запретив их учетную запись или контактные органы.

Автор: 5ervant Размещён: 04.04.2013 08:15

192 плюса

9653 Репутация автора

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

Don't use MySQL, this extension is deprecated, use MySQLi or PDO.

MySQLi

For manually escaping special characters in a string you can use the mysqli_real_escape_string function. The function will not work properly unless the correct character set is set with mysqli_set_charset.

Example:

$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')" );

For automatic escaping of values with prepared statements, use mysqli_prepare, and mysqli_stmt_bind_param where types for the corresponding bind variables must be provided for an appropriate conversion:

Example:

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

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

$stmt->execute();

No matter if you use prepared statements or mysqli_real_escape_string, you always have to know the type of input data you're working with.

So if you use a prepared statement, you must specify the types of the variables for mysqli_stmt_bind_param function.

And the use of mysqli_real_escape_string is for, as the name says, escaping special characters in a string, so it will not make integers safe. The purpose of this function is to prevent breaking the strings in SQL statements, and the damage to the database that it could cause. mysqli_real_escape_string is a useful function when used properly, especially when combined with sprintf.

Example:

$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
Автор: Danijel Размещён: 09.05.2013 04:36

161 плюса

1669 Репутация автора

A simple way would be to use a PHP framework like CodeIgniter or Laravel which have inbuilt features like filtering and active-record so that you don't have to worry about these nuances.

Автор: Deepak Thomas Размещён: 25.06.2013 07:00

116 плюса

12243 Репутация автора

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

mysql_real_escape_string

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

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

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.

Автор: Rakesh Sharma Размещён: 17.01.2014 06:25

125 плюса

769 Репутация автора

Есть так много ответов для 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);
Автор: Chintan Gor Размещён: 30.01.2014 07:00

82 плюса

8368 Репутация автора

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

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\'s 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;
}

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

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

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

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

Автор: Calmarius Размещён: 18.02.2014 08:38

119 плюса

21130 Репутация автора

Хорошей идеей является использование «объектно-реляционного картографа», такого как 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-инъекций, но и от синтаксических ошибок! Также поддерживает коллекции моделей с цепочкой методов для фильтрации или применения действий к нескольким результатам сразу и нескольких подключений.

Автор: Thomas Ahle Размещён: 20.03.2014 12:17
Вопросы из категории :
32x32