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

Как избежать sql инъекции

  • автор:

Как избежать sql инъекции

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 );
?>

Обычно пользователи нажимают по ссылкам ‘вперёд’ и ‘назад’, вследствие чего значение переменной $offset заносится в URL . Скрипт ожидает, что $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.

Ещё один вероятный способ получить пароли учётных записей в БД — атака страниц, предоставляющих поиск по базе. Злоумышленнику нужно лишь проверить, используется ли в запросе передаваемая на сервер и необрабатываемая надлежащим образом переменная. Это может быть один из устанавливаемых на предыдущей странице фильтров, таких как 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-инъекций — связывание всех данных с помощью подготовленных запросов. Использование подготовленных запросов недостаточно для полного предотвращения 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-инъекций в основном основаны на использовании кода, написанного без учёта требований безопасности. Никогда не доверяйте любому вводу, особенно со стороны клиента, даже если он поступает из поля выбора, скрытого поля ввода или cookie. Первый пример показывает, что такой простой запрос может привести к катастрофе.

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

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

Что такое SQL-инъекция? Как предотвратить данную атаку и защитить свою систему

Что такое SQL-инъекция? Как предотвратить данную атаку и защитить свою систему

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

Как предотвратить появление уязвимостей для SQL-инъекций?

Лучший способ предотвратить появление уязвимостей для SQL-инъекций – это использовать фреймворк, который позволяет безопасно создавать и настраивать запросы. ORM (Object Relational Mapper) прекрасно подойдет в данных целях. Для получения дополнительных уровней безопасности следует проверить все входные данные и использовать инструменты WAF (Web Application Firewall).

Простой пример

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

String query = "SELECT * FROM documents WHERE ownerId=" + authContext.getUserId() + " AND documentName = '" + request.getParameter("docName") + "'"; executeQuery(query);

Итак, если идентификатор пользователя равен 25, а URL-адрес похож на этот «https://www.example.com/documents/?docName=ABC123», то запрос будет примерно следующим:

SELECT * FROM documents WHERE ownerId=25 AND documentName='ABC123';

Пока все идет хорошо. Но что нужно предпринять, если URL-адрес другой? К примеру, этот – «https://www.example.com/documents/?docName=ABC123’OR’1’=’1».

Пользователь вводит запрос, с помощью которого можно вернуть все документы всем пользователям. Это становится возможным благодаря тому, что «1=1», а это выражение, которое всегда верно (или истинно).

SELECT * FROM documents WHERE ownerId=25 AND documentName='ABC123' OR '1'='1';

Ой, какая неловкость. Но как пользователь может этого избежать?

Object Relational Mappers

Используя Java в качестве примера, пользователь прибегает к применению ORM, такого как hibernate. Собственно говоря, он реализует JPA (Java Persistence API), что может выглядеть следующим образом.

Во-первых, следует определить модель.

@Entity public class Document

Во-вторых, пользователь выбирает класс репозитория.

@Repository public interface DocumentRepository extends JpaRepository  < ListfindByDocumentNameAndOwnerId(String documentName, Integer ownerId); >

Наконец, он может использовать его.

Пользователь теперь может получить документы, если ввести данную команду:

List docs = documentRepository.findByDocumentNameAndOwnerId(request.getParameter("docName"), authContext.getUserId());

ORM (Object Relational Mappers) позаботится о безопасной обработке всех параметров. Теперь следует предположить, что человек хочет лучше контролировать свои запросы. В этом случае многие ORM могут предоставить составители («строители») запросов. Человек будет использовать любой из них, к примеру, Hibernate Criteries API.

  • https://spring.io/guides/gs/accessing-data-jpa/
  • https://docs.jboss.org/hibernate/orm/3.5/javadocs/org/hibernate/Criteria.html

Если человек использует Python, у Django есть отличный ORM. Если пользователь не применяет Django, то sqlalchemy станет отличным вариантом.

  • https://docs.djangoproject.com/en/3.1/topics/db/queries/
  • https://www.sqlalchemy.org/

PHP имеет свою доктрину. Следует просто найти в Google ORM для выбранной технологии.

Предупреждение

Нужно быть аккуратным с фреймворками ORM по двум причинам.

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

Во-вторых, фреймворки ORM время от времени склонны к появлению уязвимостей, как и любой другой программный пакет, поэтому необходимо выполнять различные дополнительные практики безопасности. Пользователи обычно проверяют все входные данные, используют инструменты WAF и поддерживают версии своих пакетов в актуальном состоянии, чтобы не попасть впросак.

Prepared statements

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

String query = "SELECT * FROM documents WHERE ownerId=? AND documentName = ?"; PreparedStatement ps = conn.prepareStatement(query); ps.setString(1, authContext.getUserId()); ps.setString(2, request.getParameter("docName")); ResultSet rs = ps.executeQuery();

Теоретически это безопасно. Однако, по опыту многих специалистов, по мере того, как кодовая база будет расти, ошибки начнут появляться все чаще. Пользователю нужен только один промах, чтобы сделать свою систему полностью уязвимой. К примеру, те же массивы («foo, bar») – очень опасная зона, где могут возникнуть проблемы.

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

Web Application Firewalls

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

Отличным решением с открытым исходным кодом является развертывание Apache с ModSecurity CRS для защиты своего веб-приложения.

Database Firewalls

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

Заключение

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

ORM безопаснее, чем prepared statements. И если человеку нужен низкоуровневый контроль запросов, следует использовать ORM более низкого уровня, который еще называют «строителем» запросов. Продукты WAF помогут добавить дополнительный уровень безопасности, но не следует полностью полагаться только на возможности данных инструментов.

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

Лабораторная работа №1 «SQL-инъекции»

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

В зависимости от типа используемой СУБД и особенностей уязвимого приложения, SQL-инъекция может дать возможность атакующему выполнить произвольный запрос к БД, получить возможность чтения и/или записи локальных файлов или даже выполнения произвольных команд на атакуемом сервере.

Атака может быть реализована против любой СУБД, поддерживающей SQL: MySQL, PostgreSQL, Oracle и т.д.

Далее рассмотрим ряд уязвимостей, приводящих к возможности реализации SQL-инъекции.

Некорректная обработка входных параметров

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

Пример уязвимого кода на PHP:

$username = $_GET['username']; $result = mysql_query("SELECT * FROM users WHERE username = '$username'");

Этот код получает значение параметра username, переданное пользователем. Затем это значение непосредственно используется для запроса к БД без какой-либо дополнительной обработки.

Для реализации SQL-инъекции в данном случае достаточно передать в качестве username следующую строку: ‘ OR ‘1’ = ‘1 . При этом выражение в условии запроса примет вид: username = » OR ‘1’ = ‘1’ . Очевидно, что оно всегда верно, поскольку всегда верно выражение ‘1’ = ‘1’ . Таким образом, запрос вернёт все строки из таблицы users.

Демонстрация

SELECT * FROM users WHERE username = ' '

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

Следует отметить, что приложение может получать входные данные не только из пользовательских форм, но и из идентификаторов cookie, заголовков протокола HTTP и других источников. Кроме того, параметры могут быть переданы посредством «невидимых» (type="hidden") полей в веб-формах. Для просмотра данных, передаваемых от браузера на сервер и обратно, можно воспользоваться анализатором трафика или специализированные отладочным средством (например, Firebug).

Несанкционированный доступ к данным

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

$id = $_GET['id']; $result = mysql_query("SELECT author, title, text FROM news WHERE color="#990000">);

Приведённый выше код предназначен для отображения новости с заданным id из таблицы news.

Если передать в качестве id строку -1 UNION ALL SELECT username, password, NULL FROM users , то запрос не вернёт ни одной строки из таблицы news, поскольку строки с идентификатором -1 заведомо не существует. При этом посредством оператора UNION будут выбраны все строки из таблицы users.

Демонстрация

SELECT author, title, text FROM news WHERE >

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

Слепая SQL-инъекция

SQL-инъекция называется слепой (англ. blind SQL injection) в том случае, когда результат выполнения запроса недоступен злоумышленнику. При этом уязвимый веб-сайт по-разному реагирует на различные логические выражения, подставляемые в уязвимый параметр. Таким образом, злоумышленник может подобрать значения некоторых параметров (версия СУБД, текущее имя и права пользователя и т. д.), подставляя в запрос соответствующие логические выражения.

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

$id = $_GET['id']; $result = mysql_query("SELECT * FROM catalog WHERE color="#990000">); if (mysql_num_rows($result) != 0) // Вывод описания товара… else echo "Товар не найден!";

Его задача — вывести описание товара из таблицы catalog по указанному id. В случае, если товара с заданным id не существует, будет выведено соответствующее сообшение.

Если в качестве id передать строку 1 AND 1 = 1 , то условие запроса не изменится, поскольку выражение 1 = 1 всегда истинно. Если товар с id равным 1 существует, то в ответ будет получена страница с его описанием. Если затем в качестве id передать строку 1 AND 1 = 2 с заведомо ложным условием, то будет получено сообщение о том, что запрошенный товар не существует. Таким образом, можно подобрать значения некоторых параметров БД, например, условие SUBSTR(@@version, 1, 1) = 5 будет верным только если версия СУБД равна 5.

Особенности реализации SQL-инъекций

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

SELECT CONCAT(username, ':', password) FROM users

Различные реализации СУБД могут предоставлять другие способы объединения строк. За подробной информацией обращайтесь к документации по СУБД.

В некоторых случаях запрос, подверженный SQL-инъекции, имеет структуру, усложняющую или препятствующую внедрению операторов SQL. Например, следующий код:

$author_id = $_GET['author_id']; $result = mysql_query("SELECT author, title, text FROM news WHERE author = $author_id ORDER BY date DESC LIMIT 20");

выбирает строки с указанным идентификатором author_id, сортирует их по дате и выводит 20 первых записей. Простая подстановка оператора UNION вместо author_id приведёт к ошибке из-за оставшейся части запроса: ORDER BY date DESC LIMIT 20 . В этом случае часть запроса необходимо экранировать при помощи символов комментария (--, /* или # в зависимости от СУБД). Часть строки, отделённая этими символами, будет проигнорирована и запрос успешно выполнится.

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

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

$id = $_GET['id']; $result = mysql_query("SELECT * FROM news WHERE color="#990000">);

Передав в качестве id строку -1; INSERT INTO users(username, password) VALUES (‘foo’, ‘bar’) , злоумышленник может несанкционированно вставить новую строку в таблицу users.

Не все языки программирования и СУБД поддерживают эту возможность. За подробной информацией обращайтесь к документации по СУБД.

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

Наиболее общими способами предотвращения SQL-инъекций являются фильтрация, экранирование и проверка всех входных данных. В языке PHP для этого могут применяться такие функции, как addslashes() и mysql_real_escape_string().

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

mysql_real_escape_string() работает аналогично addslashes(), но учитывает особенности диалекта MySQL, экранируя некоторые дополнительные символы. Как и addslashes(), она работает только в случае, когда экранируемая строка заключена в кавычки.

Функции, аналогичные mysql_real_escape_string(), реализованы и для других СУБД. За подробной информацией обращайтесь к руководству по PHP.

Кроме того, в языке PHP существует ряд функций, позволяющих привести значение параметра к определённому типу. Например, функции intval() и floatval() позволяют преобразовать переменную к целому числу и числу с плавающей запятой соответственно. Это делает невозможным подстановку произвольных строк в запрос к БД.

Дополнительные материалы
  1. Подборка материалов по SQL Injection
  2. SQL Injection Wiki

Атака путем внедрения кода SQL

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

Принцип действия атаки путем внедрения кода SQL

Основная форма атаки SQL Injection состоит в прямой вставке кода в пользовательские входные переменные, которые объединяются с командами SQL и выполняются. Менее явная атака внедряет небезопасный код в строки, предназначенные для хранения в таблице или в виде метаданных. Когда впоследствии сохраненные строки объединяются с динамической командой SQL, происходит выполнение небезопасного кода.

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

Следующий скрипт показывает простую атаку SQL Injection. Скрипт формирует SQL-запрос, выполняя объединение жестко запрограммированных строк со строкой, введенной пользователем:

var ShipCity; ShipCity = Request.form ("ShipCity"); var sql = "select * from OrdersTable where ShipCity = '" + ShipCity + "'"; 

Пользователю выводится запрос на ввод названия города. Если пользователь вводит Redmond , то запрос, построенный с помощью скрипта, выглядит приблизительно так:

SELECT * FROM OrdersTable WHERE ShipCity = 'Redmond' 

Предположим, однако, что пользователь вводит следующее:

Redmond'; drop table OrdersTable-- 

В этом случае запрос, построенный скриптом, будет следующим:

SELECT * FROM OrdersTable WHERE ShipCity = 'Redmond';drop table OrdersTable--' 

Точка с запятой «;» обозначает конец одного запроса и начало другого. А последовательность двух дефисов (—) означает, что остальная часть текущей строки является комментарием и не должна обрабатываться. Если измененный код будет синтаксически правилен, то он будет выполнен сервером. Когда SQL Server обрабатывает эту инструкцию, SQL Server сначала выбирает все записи, в которых OrdersTable ShipCity находится Redmond . Затем SQL Server упадет OrdersTable .

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

Проверка достоверности всех вводимых данных

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

  • Не делайте никаких предположений о размере, типе или содержимом данных, получаемых приложением. Например, рекомендуется оценить следующее.
    • Как приложение будет вести себя, если пользователь по ошибке или по злому умыслу вставит MPEG-файл размером 10 МБ там, где приложение ожидает ввод почтового индекса?
    • Как приложение будет вести себя, если в текстовое поле будет внедрена инструкция DROP TABLE ?

    По возможности отклоняйте вводимые данные, содержащие следующие символы:

    Входной символ Значение в языке Transact-SQL
    ; Разделитель запросов.
    Разделитель строк символьных данных.
    Разделитель однострочного комментария. Текст после и до конца этой строки не обрабатывается сервером.
    /* . */ Разделители комментариев. Сервер не обрабатывает текст между знаками /* и */ .
    xp_ Используется в начале имени расширенных хранимых процедур каталога, например xp_cmdshell .

    Использование SQL-параметров безопасных типов

    Коллекция параметров в SQL Server обеспечивает проверку типов и проверку длины. Если используется коллекция Parameters , то вводимые данные обрабатываются как буквенное значение, а не исполняемый код. Дополнительное преимущество использования коллекции Parameters состоит в том, что можно использовать принудительные проверки типа и длины данных. Если значение выходит за рамки диапазона, будет вызвано исключение. В следующем фрагменте кода демонстрируется использование коллекции Parameters :

    SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", conn); myCommand.SelectCommand.CommandType = CommandType.StoredProcedure; SqlParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11); parm.Value = Login.Text; 

    В этом примере параметр @au_id обрабатывается как буквенное значение, а не исполняемый код. Это значение проверяется по типу и длине. Если значение @au_id не соответствует указанным ограничениям типа и длины, то будет вызвано исключение.

    Использование параметризованного ввода с хранимыми процедурами

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

    SqlDataAdapter myCommand = new SqlDataAdapter("LoginStoredProcedure '" + Login.Text + "'", conn); 

    Если используются хранимые процедуры, то в качестве их входных данных следует использовать параметры.

    Использование коллекции Parameters с динамическим SQL

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

    SqlDataAdapter myCommand = new SqlDataAdapter( "SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id", conn); SQLParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11); Parm.Value = Login.Text; 

    Фильтрация ввода

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

    private string SafeSqlLiteral(string inputSQL)

    Предложения LIKE

    Обратите внимание, что при использовании предложения LIKE подстановочные знаки по-прежнему нужно выделять escape-символами:

    s = s.Replace("[", "[[]"); s = s.Replace("%", "[%]"); s = s.Replace("_", "[_]"); 

    Просмотр кода на предмет возможности атаки SQL Injection

    Необходимо просматривать все фрагменты кода, вызывающие инструкции EXECUTE , EXEC или sp_executesql . Чтобы выявить процедуры, содержащие эти инструкции, можно использовать запросы, подобные следующему. Этот запрос проверяет наличие 1, 2, 3 или 4 пробелов после слов EXECUTE и EXEC .

    SELECT object_Name(id) FROM syscomments WHERE UPPER(text) LIKE '%EXECUTE (%' OR UPPER(text) LIKE '%EXECUTE (%' OR UPPER(text) LIKE '%EXECUTE (%' OR UPPER(text) LIKE '%EXECUTE (%' OR UPPER(text) LIKE '%EXEC (%' OR UPPER(text) LIKE '%EXEC (%' OR UPPER(text) LIKE '%EXEC (%' OR UPPER(text) LIKE '%EXEC (%' OR UPPER(text) LIKE '%SP_EXECUTESQL%'; 

    Упаковка параметров с помощью функций QUOTENAME() и REPLACE()

    Убедитесь, что в каждой выбранной хранимой процедуре все используемые в динамическом Transact-SQL переменные обрабатываются правильно. Данные, поступающие через входные параметры хранимой процедуры или считываемые из таблицы, должны быть помещены в функции QUOTENAME() или REPLACE(). Помните, что значение @variable, передаваемое функции QUOTENAME(), принадлежит к типу sysname и имеет ограничение длины в 128 символов.

    @variable Рекомендуемый упаковщик
    Имя защищаемого объекта QUOTENAME(@variable)
    Строка, содержащая не более 128 знаков. QUOTENAME(@variable, »»)
    > Строка из 128 символов REPLACE(@variable,»», »»»)

    При использовании этого метода инструкция SET может быть исправлена следующим образом:

    -- Before: SET @temp = N'SELECT * FROM authors WHERE au_lname =''' + @au_lname + N''''; -- After: SET @temp = N'SELECT * FROM authors WHERE au_lname = ''' + REPLACE(@au_lname,'''','''''') + N''''; 

    Атака Injection, проводимая с помощью усечения данных

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

    CREATE PROCEDURE sp_MySetPassword @loginname sysname, @old sysname, @new sysname AS -- Declare variable. -- Note that the buffer here is only 200 characters long. DECLARE @command varchar(200) -- Construct the dynamic Transact-SQL. -- In the following statement, we need a total of 154 characters -- to set the password of 'sa'. -- 26 for UPDATE statement, 16 for WHERE clause, 4 for 'sa', and 2 for -- quotation marks surrounded by QUOTENAME(@loginname): -- 200 - 26 - 16 - 4 - 2 = 154. -- But because @new is declared as a sysname, this variable can only hold -- 128 characters. -- We can overcome this by passing some single quotation marks in @new. SET @command= 'update Users set password=' + QUOTENAME(@new, '''') + ' where username=' + QUOTENAME(@loginname, '''') + ' AND password = ' + QUOTENAME(@old, '''') -- Execute the command. EXEC (@command) GO 

    Передавая 154 знака в буфер, рассчитанный на 128 знаков, злоумышленник может установить новый пароль для sa, не зная старого пароля.

    EXEC sp_MySetPassword 'sa', 'dummy', '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012''''''''''''''''''''''''''''''''''''''''''''''''''' 

    По этой причине следует использовать большой буфер для переменной команды или напрямую выполнить динамический Transact-SQL внутри инструкции EXECUTE .

    Усечение при использовании функций QUOTENAME(@variable, »») и REPLACE()

    Если строки, возвращаемые функциями QUOTENAME() и REPLACE(), не умещаются в выделенном пространстве, они усекаются без взаимодействия с пользователем. Хранимая процедура, создаваемая в следующем примере, показывает, что может произойти.

    CREATE PROCEDURE sp_MySetPassword @loginname sysname, @old sysname, @new sysname AS -- Declare variables. DECLARE @login sysname DECLARE @newpassword sysname DECLARE @oldpassword sysname DECLARE @command varchar(2000) -- In the following statements, the data stored in temp variables -- will be truncated because the buffer size of @login, @oldpassword, -- and @newpassword is only 128 characters, but QUOTENAME() can return -- up to 258 characters. SET @login = QUOTENAME(@loginname, '''') SET @oldpassword = QUOTENAME(@old, '''') SET @newpassword = QUOTENAME(@new, '''') -- Construct the dynamic Transact-SQL. -- If @new contains 128 characters, then @newpassword will be '123. n -- where n is the 127th character. -- Because the string returned by QUOTENAME() will be truncated, -- it can be made to look like the following statement: -- UPDATE Users SET password ='1234. . .[127] WHERE username=' -- other stuff here SET @command = 'UPDATE Users set password = ' + @newpassword + ' where username =' + @login + ' AND password = ' + @oldpassword; -- Execute the command. EXEC (@command); GO 

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

    EXEC sp_MyProc '--', 'dummy', '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678' 

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

    CREATE PROCEDURE sp_MySetPassword @loginname sysname, @old sysname, @new sysname AS -- Declare variables. DECLARE @login sysname DECLARE @newpassword sysname DECLARE @oldpassword sysname DECLARE @command varchar(2000) -- In the following statements, data will be truncated because -- the buffers allocated for @login, @oldpassword and @newpassword -- can hold only 128 characters, but QUOTENAME() can return -- up to 258 characters. SET @login = REPLACE(@loginname, '''', '''''') SET @oldpassword = REPLACE(@old, '''', '''''') SET @newpassword = REPLACE(@new, '''', '''''') -- Construct the dynamic Transact-SQL. -- If @new contains 128 characters, @newpassword will be '123. n -- where n is the 127th character. -- Because the string returned by QUOTENAME() will be truncated, it -- can be made to look like the following statement: -- UPDATE Users SET password='1234. [127] WHERE username=' -- other stuff here SET @command= 'update Users set password = ''' + @newpassword + ''' where username=''' + @login + ''' AND password = ''' + @oldpassword + ''''; -- Execute the command. EXEC (@command); GO 

    Как и в случае с функцией QUOTENAME(), усечения строки с помощью функции REPLACE() можно избежать, объявив временные переменные, достаточно большие для всех случаев. По возможности необходимо вызвать QUOTENAME() или REPLACE() непосредственно внутри динамического Transact-SQL. Или же необходимый размер буфера можно рассчитать следующим образом. Для @outbuffer = QUOTENAME(@input) размер буфера переменной @outbuffer должен составлять 2*(len(@input)+1) . При использовании функции REPLACE() и двойных кавычек, как в предыдущем примере, достаточно буфера размером 2*len(@input) .

    Следующий расчет применим ко всем случаям.

    WHILE LEN(@find_string) > 0, required buffer size = ROUND(LEN(@input)/LEN(@find_string),0) * LEN(@new_string) + (LEN(@input) % LEN(@find_string)) 

    Усечение при использовании функции QUOTENAME(@variable, ‘]’)

    Усечение может возникать, когда имя защищаемого объекта SQL Server передается в инструкции, использующие форму QUOTENAME(@variable, ‘]’) . В следующем примере приведена иллюстрация этого.

    CREATE PROCEDURE sp_MyProc @schemaname sysname, @tablename sysname, AS -- Declare a variable as sysname. The variable will be 128 characters. -- But @objectname actually must allow for 2*258+1 characters. DECLARE @objectname sysname SET @objectname = QUOTENAME(@schemaname)+'.'+ QUOTENAME(@tablename) -- Do some operations. GO 

    При сцеплении значений типа sysname рекомендуется использовать временные переменные достаточно больших размеров, чтобы они могли вмещать до 128 знаков на одно значение. По возможности вызовите QUOTENAME() непосредственно внутри динамического Transact-SQL. Или же необходимый размер буфера можно рассчитать, как это показано в предыдущем разделе.

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

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