Майкл Кэмпбелл (mike@overachiever.net) — редактор журнала SQL Server Magazine, консультант и разработчик для SQL Server

* Для атаки хакерам достаточно найти одно уязвимое место.

* Одних лишь белых списков недостаточно для защиты от внедрения кода SQL.

* Обеспечьте полное отделение командного языка от данных пользователя.

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

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

Миф 1. Очистка предоставляемых пользователем данных предотвращает внедрение кода SQL

Многие программисты полагают, что внедрению кода SQL можно противопоставить простую «очистку» опасных данных, вводимых злоумышленниками. Руководствуясь этой неверной предпосылкой, программисты защищают базы данных, пропуская все поступающие от пользователей данные через подготовленные с самыми лучшими намерениями вспомогательные функции с целью избавиться от опасных входных данных. Но при этом обычно забывают обработать не столь очевидные входные данные, например строки запросов или cookie-строки в веб-приложениях. Самое главное, что злоумышленники легко преодолевают эту защиту, поскольку в ней не устраняется корневая причина атаки путем внедрения кода SQL.

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

С помощью автоматизированных инструментов внедрения хакеры могут опробовать сложные комбинации уловок кодирования, escape-последовательностей, омографов и редко используемых символов Юникода и ASCII, пытаясь обойти примитивные процедуры замены строк, определяемые разработчиками, не способными мыслить как хакеры. Кроме того, в сложных атаках (в автоматизированном режиме) даже объединяется ввод из нескольких полей, чтобы использовать усечение и другие уловки для подготовки составных атак с гораздо более широким диапазоном векторов внедрения.

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

Миф 2. Белые списки обеспечивают полную безопасность

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

Миф 3. Хранимые процедуры обеспечивают полную безопасность

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

Отражаем внедрение: отделение команд от данных

Единственный верный способ предотвратить внедрение кода SQL — надежно отделить командный язык от данных или пользовательского ввода. Такой же метод применяется для борьбы с большинством разновидностей атак с внедрением. Как показано на рисунке 1, любой сценарий, который позволяет поднять ввод конечного пользователя до уровня командного языка, открывает перед злоумышленниками возможность контроля над командами. Овладев ими, взломщики с помощью автоматизированных инструментов могут перевести предоставленные разработчиками интерфейсы в базовое хранилище данных (то есть существующие инструкции SQL) и узнать из метаданных о текущих разрешениях, среде и структурах данных. Затем они могут манипулировать данными и разрешениями, и в конечном итоге потенциально установить контроль над целыми базами данных, серверами и даже сетями.

 

Четкое разделение данных и командного языка или их объединение
Рисунок 1. Четкое разделение данных и командного языка или их объединение

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

Ошибки параметризации

Структуры доступа к данным, основанные исключительно на параметризации (например, LINQ to SQL, ADO.NET Entity Framework), способствуют защите от внедрения кода SQL. Аналогично пользовательский программный код для доступа можно легко защитить от внедрения кода SQL через правильное использование параметризации. Это можно сделать с помощью хранимых процедур или параметризованных запросов. Но в любом случае возможны ошибки в применении параметризации, приводящие к потере эффекта защиты от внедрения кода SQL.

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

Рассмотрим в качестве примера примитивную подпрограмму доступа к данным, с помощью которой конечные пользователи могут получить сведения о продукте на основе его имени с использованием простой хранимой процедуры, показанной на рисунке 2. Хранимая процедура четко отделяет командный язык (то есть инструкцию SELECT) от данных с помощью параметризации.

 

Корректно определенная хранимая процедура
Рисунок 2. Корректно определенная хранимая процедура

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

 

Код без объекта команды, приводящий к?возможности внедрения кода SQL
Рисунок 3. Код без объекта команды, приводящий к?возможности внедрения кода SQL

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

 

Хранимая процедура с динамическим SQL, что?открывает путь внедрению кода SQL
Рисунок 4. Хранимая процедура с динамическим SQL, что?открывает путь внедрению кода SQL

Следовательно, в редких случаях, когда необходимо иметь дело с динамическим SQL в хранимых процедурах, я обычно рекомендую применять подход, описанный Дэвидом Пентоном в блоге под заголовком «Not-so Dynamic Sql» (http://sqladvice.com/blogs/dpenton/archive/2004/09/08/4233.aspx). Этот подход не только защищает от внедрения кода SQL, но и значительно упрощает обслуживание динамического SQL благодаря удобству чтения исходного текста.

Столь же важно отметить, что хотя в SQL Server 2005 и более новых версиях предусмотрены меры защиты от определенных типов использования динамического SQL из хранимых процедур, эти меры в основном лишь смягчают урон, который могут нанести хакеры, но не исключают внедрения кода SQL. Поэтому, чтобы глубже изучить эти меры защиты и динамический SQL вообще, рекомендую внимательно прочитать работы Эрланда Соммарскога «Giving Permissions through Stored Procedures» и «The Curse and Blessings of Dynamic SQL» (http://www.sommarskog.se/grantperm.html).

Устраняем опасность внедрения кода SQL

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