Перейти к содержимому

Как защититься от sql инъекций php

  • автор:

Как защититься от sql инъекций php

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

Пример #1 Постраничный вывод результата и… создание суперпользователя в СУБД PostgreSQL

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

$offset = $_GET [ ‘offset’ ]; // Осторожно, нет валидации ввода!
$query = «SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset ;» ;
$result = pg_query ( $conn , $query );

В стандартном сценарии пользователи нажимают на ссылки «Вперёд» и «Назад», в URL -адресах которых закодировано смещение, значение которого получает переменная $offset . Скрипт ожидает, что входящее значение для переменной $offset — число. Однако, что если взломщик выполнит попытку взломать систему и добавит к URL -адресу следующее значение:

0; insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) select 'crack', usesysid, 't','t','crack' from pg_shadow where usename='postgres'; --

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

Замечание:

Распространённый приём, который заставляет SQL-парсер игнорировать остальную часть запроса разработчика, — последовательность символов — , которая играет в SQL роль синтаксиса комментариев.

Ещё один способ раскрыть пароли учётных записей в БД — атаковать страницы с результатами поиска. Злоумышленнику требуется только проверить, есть ли в запросе к серверу переменные, которые попадут в SQL-запрос и которые не обрабатываются правильно. Эти фильтры обычно устанавливают в форме перед выполнением поиска, чтобы изменить поведение условий WHERE, ORDER BY, LIMIT и OFFSET в запросе SELECT . Если база данных поддерживает конструкцию UNION , злоумышленник попробует добавить к оригинальному запросу ещё один, чтобы вывести список паролей из произвольной таблицы. Настоятельно рекомендуется хранить только зашифрованные пароли.

Пример #2 Вывод списка статей… и ряда паролей (любой сервер базы данных)

$query = «SELECT id, name, inserted, size FROM products
WHERE size = ‘ $size ‘» ;
$result = odbc_exec ( $conn , $query );

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

' union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable; --

Инструкции UPDATE и INSERT также подвержены таким атакам.

Пример #3 От сброса пароля до… получения дополнительных привилегий (любой сервер баз данных)

$query = «UPDATE usertable SET pwd=’ $pwd ‘ WHERE uid=’ $uid ‘;» ;

Если злоумышленник отправляет значение ‘ or uid like’%admin%’ для переменной $uid , чтобы изменить пароль администратора, или просто присваивает переменной $pwd значение hehehe’, trusted=100, admin=’yes , чтобы получить дополнительные привилегии, тогда запросы переплетаются:

// Значение переменной $uid: ‘ or uid like ‘%admin%
$query = «UPDATE usertable SET pwd=’. ‘ WHERE uid=» or uid like ‘%admin%’;» ;

// Значение переменной $pwd: hehehe’, trusted=100, admin=’yes
$query = «UPDATE usertable SET pwd=’hehehe’, trusted=100, admin=’yes’ WHERE
. ;» ;

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

Пример #4 Атака на операционную систему сервера базы данных (MSSQL Server)

Страшный пример получения доступа к командам уровня операционной системы на отдельных хостах баз данных.

$query = «SELECT * FROM products WHERE id LIKE ‘% $prod %'» ;
$result = mssql_query ( $query );

Если злоумышленник отправит значение a%’ exec master..xp_cmdshell ‘net user test testpass /ADD’ — для переменной $prod , то переменная $query будет равна:

$query = «SELECT * FROM products
WHERE id LIKE ‘%a%’
exec master..xp_cmdshell ‘net user test testpass /ADD’ —%'» ;
$result = mssql_query ( $query );

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

Замечание:

Часть приведённых примеров привязана к конкретному серверу базы данных, но это не говорит о невозможности похожей атаки на другие продукты. Сервер базы данных подвержен и другим уязвимостям.

Забавный пример проблем, связанных с SQL-инъекциями

Изображение любезно предоставил сайт веб-комиксов » xkcd

Техники защиты

Рекомендуемый способ избежать SQL-инъекций — связать данные подготовленными инструкциями. Параметризованных запросов недостаточно, чтобы на 100 процентов избежать внедрения SQL-инъекций, но это простейший и безопасный способ передать входные данные SQL-инструкциям. Литералы динамических данных в условиях WHERE , SET и VALUES требуется заменить заполнителями. Сервер базы данных свяжет фактические данные при выполнении и отправит отдельно от SQL-команды.

Сервер применяет связывание параметров только для данных. Другие динамические части SQL-запроса требуется отфильтровать по известному списку разрешённых значений.

Пример #5 Избегание SQL-инъекций средствами подготовленных инструкций модуля PDO

// Динамическая часть SQL-запроса проверяется на соответствие ожидаемым значениям
$sortingOrder = $_GET [ ‘sortingOrder’ ] === ‘DESC’ ? ‘DESC’ : ‘ASC’ ;
$productId = $_GET [ ‘productId’ ];

// SQL-запрос подготавливается с заполнителем
$stmt = $pdo -> prepare ( «SELECT * FROM products WHERE id LIKE ? ORDER BY price < $sortingOrder >» );

// Значение передаётся с подстановочными знаками LIKE
$stmt -> execute ([ «% < $productId >%» ]);

Подготовленные инструкции предоставляют модули PDO, MySQLi и другие библиотеки баз данных.

Атаки с внедрением SQL-инъекций обычно основаны на коде, который написали без учёта требований безопасности. Разработчик не должен доверять никаким входным данным, особенно со стороны клиента, даже если входные значения поступают из HTML-блока SELECT, скрытого поля ввода или из cookie. Первый пример показывает, что такой простой запрос легко приводит к катастрофе.

  • Не подключайтесь к базе данных как суперпользователь или владелец базы данных. Используйте только пользователей с минимальными привилегиями.
  • Проверяйте входные данные на соответствие типу, который ожидался. В PHP содержится много функции для проверки входных данных, от простейших функций для работы с переменными наподобие функции is_numeric() и функций проверки типа символов наподобие функции ctype_digit() и далее к поддержке Perl-совместимых регулярных выражений.
  • Если приложение ожидает числовые входные данные, рассмотрите применимость проверки данных функцией ctype_digit() , незаметно измените тип входных данных функцией settype() или получите числовое представление входных данных функцией sprintf() .
  • Если на уровне базы данных не поддерживается связывание переменных, возьмите в кавычки каждое пользовательское нечисловое значение, которое передаётся в БД через характерную для конкретной базы данных функцию экранирования строки: mysql_real_escape_string() , sqlite_escape_string() и т. д. Общие функции наподобие addslashes() полезны только в конкретных средах (например, СУБД MySQL в однобайтовой кодировке с отключённым режимом NO_BACKSLASH_ESCAPES ), поэтому при работе с БД лучше избегать таких функций.
  • Ни в каком случае не выводите информации о БД, особенно о структуре. Дополнительную информацию дают разделы «Сообщения об ошибках» и «Функции обработки и логирования ошибок».

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

Защита от SQL иньекций в php

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

Commented 6 мая 2015 в 1:43
Это не лишний код.
Commented 6 мая 2015 в 3:26
@mikelsv по этой парадигме php сам по себе лишний
Commented 6 мая 2015 в 7:59
Если вам дан исчерпывающий ответ, отметьте его как верный (галка напротив выбранного ответа).
Commented 6 мая 2015 в 8:56

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Разумеется, вся работа с чистым SQL должна производиться только через плейсхолдеры. Конечно, более предпочтительным вариантом является ORM (идеальный вариант для примитивных запросов, нужных автору) либо прочие квери билдеры — в этом случае пользователь РНР не видит SQL совсем, и следовательно, инъекцию при всем желании устроить не может. Но поскольку новичков эти слова обычно пугают до смерти, то этот вариант подходит только опытным пользователям. При работе же с чистым SQL, повторюсь, любые данные должны попадать в запрос только через плейсхолдеры.

Разумеется, защита от инъекций должна производиться способом, отличным от «мы тут сейчас на жувачку прилепим пару функций, там из гуана и палок сварганим другую, потом наваяем ещё кода — и все полетит!». Защита должна быть единообразной и систематической.

Разумеется, слой работы с БД должен быть отделен от слоя работы с прикладными данными, и предоставлять удобные и безопасные методы для работы с БД, чтобы не приходилось в бизнес-логике видеть какие-то непонятные вложенные по 30 раз друг в друга array_map, implode, intval и пр. ORM здесь опять на первом месте, а сырой SQL, по-хорошему, имеет смысл использовать только для сложных запросов. Но даже и для чистого SQL DB-layer обязан предоставлять безопасные методы, основанные на плейсхолдерах.

Разумеется, в общем случае, использование PDO — это на самом деле, не лишний код, а сокращение кода. Поскольку PDO — единственный из драйверов mysql в РНР, который является высокоуровневой абстракцией, позволяющей сокращать рутинные операции.

Разумеется, «implode(‘,’, $ids) в подготовленных запросах» НИКАК НЕ спасёт ситуацию, а лишь вернёт неверные данные.

Разумеется, все эти прекрасные рассуждения разбиваются о простой случай с WHERE id IN() .
Можно, конечно, наваять кода, как предлагается по ссылке в комментариях. Но это, на самом деле, будет профанация. Нам не нужны на самом деле эти плейсхолдеры для каждого значения. Нам нужен способ тупо безопасно поместить массив в запрос, не перемешивая бизнес-логику с логикой защиты от инъекций. Для этого надо просто распространить практику работы с плейсхолдерами на несколько дополнительных типов данных. Этим и должен заниматься DB-layer. поскольку ПДО, увы, не предоставляет нужного функционала, можно воспользоваться другими библиотеками. Такими как DBSimple или Safemysql. В обоих случаях мы сможем решить все поставленные выше задачи. Вот, к примеру, для safemysql:

Нужен запрос с WHERE > с проверкой на int?

$name = $db->getOne("SELECT name FROM users WHERE $id); 

Будет выброшена ошибка, если в $id — не число.

Нужен запрос со строковой переменной?

$row = $db->getRow("SELECT * FROM users WHERE email=?s", $email); 

Переменная $email будет корректно отформатирована, и никакой мусор в ней не приведет к инъекции.

Нужен запрос с пресловутым WHERE id IN() ? Ради бога — всего лишь указываем нужный тип:

$data = $db->getAll("SELECT * FROM users WHERE id IN(?a)", $ids); 

И опять драйвер сам отформатирует массив так, что инъекция не пройдет. Но сделает это все незаметно для пользователя. В этом и состоит смысл программирования — делегировать различные задачи разным сервисам, чтобы каждый занимался своим делом. Чтобы «лишний код» был инкапсулирован в свой сервис, а не писался в одной большой куче, как это принято в похапе.

Нужно добавить в БД строку прямиком из джейсона? Нет проблем.

$json = ''; $db->query("INSERT INTO table SET ?u", json_decode($json,true)); 

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

Защита от SQL-инъекций

Внедрение SQL-кода (SQL инъекция) — один из распространённых способов взлома сайтов, работающих с базами данных. Способ основан на внедрении в запрос произвольного SQL-кода. Внедрение SQL позволяет хакеру выполнить произвольный запрос к базе данных (прочитать содержимое любых таблиц, удалить, изменить или добавить данные).

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

Принцип атаки внедрения SQL

Допустим, на нашем сайте есть страница показа истории погодных наблюдений для одного города. Идентификатор этого города передаётся в ссылке в параметре запроса: /weather.php?city_id= , где ID — это первичный ключ города. В PHP-сценарии используем этот параметр для подстановки в SQL запрос:

$city_id = $_GET['city_id']; $res = mysqli_query($link, "SELECT * FROM weather_log WHERE city_id text language-text">SELECT * FROM weather_log WHERE city_id = 10 

Но если злоумышленник передаст в качестве параметра id строку -1 OR 1=1 , то выполнится запрос:

SELECT * FROM weather_log WHERE city_id = -1 OR 1=1 

Добавление во входные параметры конструкций языка SQL (вместо простых значений) изменяет логику выполнения всего SQL запроса. В этом примере вместо показа данных по одному городу, будут получены данные по всем городам, потому что выражение 1 = 1 всегда истинно. Вместо выражения SELECT . могло быть выражение на обновление данных, и тогда последствия были бы ещё серьезнее.

Отсутствие должной обработки параметров SQL-запроса — это одна из самых серьёзных уязвимостей. Никогда не вставляйте данные от пользователя в SQL запросы «как есть»!

Приведение к целочисленному типу

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

PHP умеет присваивать переменной новый тип. Этот код принудительно назначит переменной целочисленный тип:

$city_id = $_GET['city_id']; settype($city_id, 'integer'); 

После преобразования переменную $city_id можно без опаски использовать в SQL-запросах.

Экранирование значений

Что делать, если в SQL запрос требуется подставить строковое значение? Например, на сайте есть возможность поиска города по его названию. Форма поиска передаст поисковый запрос в GET-параметр, а мы используем его в SQL-запросе:

$city_name = $_GET['search']; $sql = "SELECT * FROM cities WHERE name LIKE('%$city_name%')"; 

Но если в параметре city_name будет символ кавычки, то смысл запроса можно кардинально изменить. Передав в search_text значение ')+and+(id<>'0 , мы выполним запрос, что выведет список всех городов:

SELECT * FROM cities WHERE name LIKE('%') AND (id<>'0%')) 

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

Экранирование добавляет в строке перед кавычками (и другими спецсимволами) знак обратного слэша \ . Такая обработка лишает кавычки их статуса — они больше не определяют конец значения и не могут повлиять на логику SQL-выражения.

За экранирование значений отвечает функция mysqli_real_escape_string() . Этот код обработает значение из параметра, сделав его безопасным для использования в запросе:

$city_name = mysqli_real_escape_string($link, $_GET['search']); $sql = "SELECT * FROM cities WHERE name LIKE('%$city_name%')"; 

Подготовленные выражения

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

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

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

Этап подготовки. На этапе подготовки формируется SQL-запрос, где на месте значений будут находиться знаки вопроса — плейсхолдеры. Эти плейсхолдеры в дальнейшем будут заменены на реальные значения. Шаблон запроса отправляется на сервер MySQL для анализа и синтаксической проверки. Пример:

$sql = "SELECT * FROM cities WHERE name = ?"; $stmt = mysqli_prepare($link, $sql); 

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

За подготовкой идёт выполнение. Во время запуска запроса PHP привязывает к плейсхолдерам реальные значения и посылает их на сервер. За передачу значений в подготовленный запрос отвечает функция mysqli_stmt_bind_param() . Она принимает тип и сами переменные:

mysqli_stmt_bind_param($stmt, 's', $_GET['search']); 

После выполнения запроса получить его результат в формате mysqli_result можно функцией mysqli_stmt_get_result() :

$res = mysqli_stmt_get_result($stmt); // чтение данных while ($row = mysqli_fetch_assoc($res)) < // ассоциативный массив с очередной записью из результата var_dump($row); >

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

«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.

Connecting to the Server

The topics in this section describe the options and procedures for connecting to SQL Server with the Microsoft Drivers for PHP for SQL Server.

The Microsoft Drivers for PHP for SQL Server can connect to SQL Server by using Windows Authentication or by using SQL Server Authentication. By default, the Microsoft Drivers for PHP for SQL Server try to connect to the server by using Windows Authentication.

In This Section

Topic Description
How to: Connect Using Windows Authentication Describes how to establish a connection by using Windows Authentication.
How to: Connect Using SQL Server Authentication Describes how to establish a connection by using SQL Server Authentication.
How to: Connect Using Microsoft Entra authentication Describes how to set the authentication mode and connect using identities in Microsoft Entra ID (formerly Azure Active Directory).
How to: Connect on a Specified Port Describes how to connect to the server on a specific port.
Connection Pooling Provides information about connection pooling in the driver.
How to: Disable Multiple Active Resultsets (MARS) Describes how to disable the MARS feature when making a connection.
Connection Options Lists the options that are permitted in the associative array that contains connection attributes.
Support for LocalDB Describes Microsoft Drivers for PHP for SQL Server support for the LocalDB feature, which was added in SQL Server 2012 (11.x).
Support for High Availability, Disaster Recovery Discusses how your application can be configured to take advantage of the high-availability, disaster recovery features added in SQL Server 2012 (11.x).
Connecting to Microsoft Azure SQL Database Discusses how to connect to an Azure SQL Database.
Connection Resiliency Discusses the connection resiliency feature that reestablishes broken connections.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *