Уязвимость - «Ахиллесова пята» современных информационных систем

01 мая 2004

УЯЗВИМОСТЬ - «АХИЛЛЕСОВА ПЯТА» СОВРЕМЕННЫХ ИНФОРМАЦИОННЫХ СИСТЕМ

Ни для кого не секрет, что современные информационные системы (ИС) являются одним из краеугольных камней, на основе которых строят бизнес-процессы компании и предприятия различных форм и назначений. Однако присущая для всех ИС «Ахиллесова пята» - уязвимость заставляет уделять особое внимание их защите от несанкционированных воздействий, способных привести к нарушению конфиденциальности, целостности или доступности всей системы.

Любые атаки нарушителей реализуются путём активизации той или иной уязвимости, которая присутствует в системе. Это в свою очередь создаёт условия для успешной реализации информационных атак на ИС. Примерами уязвимостей могут являться некорректным образом составленная политика безопасности, отсутствие определённых средств защиты или ошибки в используемом программном обеспечении (ПО). На рис. 1 показаны примеры уязвимостей, атак и возможные последствия их успешной реализации.


Рис. 1. Примеры уязвимостей, информационных атак и их последствий

Значимость «Ахиллесовой пяты» для ИС, а так же эффективность методов выявления и устранения уязвимостей легче проследить на ряде широко известных примеров.

Уязвимости «buffer overflow»

В основе уязвимости типа «buffer overflow» (переполнение буфера) лежит возможностью переполнения стека атакуемой подпрограммы, в результате чего нарушитель получает возможность выполнить любые команды на стороне хоста, где запущена эта подпрограмма. Наличие именно такой возможности позволило провести первую крупномасштабную атаку в сети Интернет в 1988 году, которая впоследствии получила название – «Интернет-червь Ч. Морриса». Базируясь на уязвимости в сетевой службе fingerd, буквально за несколько дней, эта атака фактически парализовала работу более половины всех компьютеров, подключённых к сети Интернет. И по сей день уязвимость класса «buffer overflow» считается одним из наиболее распространённых и весьма опасных типов уязвимостей, имеющихся в общесистемном и прикладном ПО. Для более полного представления особенностей уязвимости этого типа рассмотрим основные принципы организации стека процессора семейства Intel x86.

Стек представляет собой область памяти, специально выделенной для временного хранения данных подпрограмм. В защищённом режиме работы микропроцессора максимальный размер стека ограничивается четырьмя Гигабайтами. Структура стека организована в соответствии с принципом LIFO (Last In First Out – «последним пришел, первым ушел»). Это означает, что при чтении информации из стека извлекается блок данных, который был записан в стек последним, а не первым. Для записи информации в стек используется инструкция процессора PUSH, а для чтения – POP. При этом одна из особенностей стека заключается в том, что при записи данных в стек он увеличивается в сторону младших адресов памяти.

Для работы со стеком используются три регистра процессора (рис. 2):

  • ss – сегментный регистр, содержащий адрес начала сегмента стека;
  • sp / esp – регистр указателя стека, который всегда указывает на вершину стека, т.е. содержит смещение, по которому в стек был занесен последний элемент данных. Если стек пуст, то значение sp/esp равно адресу последнего байта сегмента, выделенного под стек;
  • bp / ebp – регистр указателя базы кадра стека, который обычно используется для хранения адреса стека, где хранятся локальные переменные. При использовании регистров sp/esp и bp/ebp их значения являются смещениями относительно сегментного регистра ss.


Рис. 2. Схема организации стека

Рассмотрим порядок записи данных в стек при вызове функций на примере программы, исходный текст которой приведён в листинге 1.

void test(int a, int b, int c){
char p1[6];
char p2[9];
}
void main() {
test(1,2,3);
}

Листинг 1. Пример программы, в которой вызывается функция test()

При вызове функции test в стек сначала записываются значения трёх параметров – a, b и c в обратном порядке. Затем в стек помещается адрес возврата, в котором указывается адрес инструкции, которая должна быть выполнена процессором после завершения работы функции test. При выходе из функции test этот адрес возврата автоматически копируется в регистр EIP (Extended Instruction Pointer), значение которого считывает процессор и передаёт управление на команду по адресу этого регистра. Также в реестре сохраняется значение регистра EBP, который указывает на вершину локальные переменные функции main(). Далее в стек записываются локальные символьные массивы p1 и p2, которые определены в функции test. Итоговая структура стека функции test() показана на рис. 3.


Рис. 3. Структура стека функции test()

Уязвимость «buffer overflow» вызвана отсутствием в программе проверки размерности данных, которые записываются в стек. Это может позволить потенциальному нарушителю записать в стек избыточную информацию, которая смогла бы изменить значение адреса возврата и передать управление на фрагмент ранее внедрённого вредоносного кода. В листинге 2 приведён исходный код программы, который демонстрирует изменения содержимого стека при попытке переполнения одной из его переменных.

#include
int main(int argc, char **argv){
char a1[4]="abc";
char a2[8]="defghij";

strcpy(a2, "0123456789");
printf("%s ", a1);

return 0;
}

Листинг 2. Пример программы, демонстрирующей изменения в стеке при его переполнении

В приведённой программе определено два символьных массива типа char – a1 и a2. Размер первого массива a1 составляет четыре, а второго – восемь байт. После определения переменных в программе при помощи функции strcpy выполняется операция записи строкового значения «0123456789» в массив a2. При этом записываемое значение на семь байт превышает размер массива a2, что приводит к его переполнению и изменению значения массива a1, который размещён в стеке до a2. На рис. 4 показано состояние фрагмента стека с переменными a1 и a2 до и после вызова функции strcpy.


Рис. 4. Состояние фрагмента стека с переменными a1 и a2

В левой части рис. 4 показано расположение в стеке содержимого двух переменных - a1 и a2. Значения обеих переменных завершаются символом «�», что является признаком окончания строки. При этом с учётом последовательности объявления переменных в программе первым в стеке размещается символьный массив a1, а затем массив a2. После вызова функции strcpy происходит переполнение массива a2, поскольку в него записывается на семь символов больше, чем предусмотрено его размером. В результате этого переполнения часть избыточной информации записывается в массив a1, который размещён в стеке сразу вслед за массивом a2. После компиляции и запуска программы, приведённой в листинге 2, на экране будет выведено значение изменённого массива a1, которое будет принимать значение «89».

Таким образом, для активизации уязвимости типа «buffer overflow» нарушитель может переполнить одну из переменных стека с целью изменения адреса возврата и записи в него адреса вредоносного кода, на который будет передано управление после завершения работы функции, в которой был переполнен стек. Вредоносный код, который должен быть выполнен в результате активизации уязвимости, также записывается в стек в процессе его переполнения. Как правило, в его качестве выступает последовательность команд (так называемая «shell code»), выполнение которой позволяет получить удалённую консоль управления компьютером, на котором была активизирована уязвимость.

Уязвимости «buffer overflow» могут содержаться как общесистемном, так и прикладном программном обеспечении. В общем случае можно выделить следующие основные категории программ, которые могут содержать уязвимость этого типа:

  • программы, которые запускаются локально на хосте. Активизация уязвимости в программах этого типа может позволить нарушителю несанкционированно расширить свои права доступа к ресурсам локальной системы. Переполнение буфера программ осуществляется путём их запуска пользователем и ввода данных, приводящих к переполнению стека;
  • сетевые приложения, которые обеспечивают интерактивное взаимодействие с пользователем на основе сетевых протоколов. Для активизации уязвимости в сетевых приложениях нарушитель может сформировать удалённый запрос, содержащий входные данные, необходимые для переполнения стека в уязвимой программе. Примером сетевых программ являются Web-приложения, такие как CGI-модули, PHP-сценарии, активные серверные страницы ASP и др.;
  • хранимые процедуры серверов СУБД. Активизация уязвимости на таких серверах возможна как путём локального запуска хранимых процедур, так и формирования удалённого запросу к соответствующей процедуре.

Уязвимости «buffer overflow» часто используются хакерами для создания Интернет-червей – самореплицирующихся программ, распространяющихся по сети и, как правило, приводящие к нарушению работоспособности ИС. Наиболее яркими примерами Интернет-червей, несанкционированная активность которых наблюдалась во второй половине 2003 года, являются:

  • Интернет-червь «W32.Blaster.Worm», который базировался на уязвимости «buffer overflow» в службе DCOM RPC ОС Windows 2000/XP;
  • Интернет-червь «W32.Slammer», распространение которого по сети осуществлялось на основе переполнения буфера в одной из подпрограмм сервера СУБД Microsoft SQL Server 2000.

Уязвимости «SQL injection»

Уязвимости типа «SQL Injection» («инъекция в SQL-запросы») позволяют нарушителю выполнять несанкционированные операции над содержимым баз данных SQL-серверов путём вставки дополнительных команд в SQL-запросы. Любой SQL-запрос представляет собой последовательность команд для сервера СУБД, сформированную на основе специализированного языка структурированных запросов SQL (Structured Query Language). Данная уязвимость характерна для приложений, которые получают в качестве входных данных параметры доступа к базе данных, после чего на их основе формируют SQL-запрос к серверам СУБД. Уязвимость «SQL Injection» заключается в отсутствии проверки корректности данных, поступающих на вход программе, что потенциально может позволить нарушителю составить входные данные таким образом, что приведёт к искажению искомого SQL-запроса к СУБД. Описание различных примеров использования уязвимостей типа «SQL Injection» приводится ниже.

Активизация уязвимости «SQL Injection» с целью получения несанкционированного доступа к ИС

В ряде случаях уязвимости «SQL Injection» могут иметь место в программах, которые выполняют функции идентификации и аутентификации пользователя в ИС. Активизация таких уязвимостей может позволить нарушителю получить несанкционированный доступ системе путём обхода процедуры аутентификации. В листинге 3 приведён пример исходного текста уязвимой программы, которая проводит аутентификацию пользователя на основе пароля.

SQLQuery = "SELECT Username FROM Users WHERE Username = ^" &
strUsername & "^ AND Password = ^" & strPassword & "^"
strAuthCheck = GetQueryResult(SQLQuery)
If strAuthCheck = "" Then
boolAuthenticated = False
Else
boolAuthenticated = True
End If

Листинг 3. Фрагмент исходного кода уязвимой программы аутентификации пользователей

Алгоритм работы приведённой выше программы выглядит следующим образом. На основе регистрационного имени и пароля, введённого пользователем, программа формирует SQL-запрос, текст которого содержится в переменной SQLQuery. Далее при помощи сформированного запроса программа проводит в таблице Users поиск записи, содержащей имя и пароль, введённые пользователем. Если такая запись находится в таблице, то делается вывод о том, что аутентифицируемый пользователь зарегистрирован в системе и ему разрешается доступ к содержимому ИС. Поиск и извлечение информации из таблицы базы данных осуществляется при помощи SQL-оператора «SELECT».

Сущность уязвимости рассмотренной программы состоит в отсутствии проверки корректности значений регистрационного имени и пароля, вводимых пользователем. Эти значения содержатся в переменных strUsername и strPassword, соответственно. Воспользовавшись этой ошибкой разработчика программы, злоумышленник может путём манипуляциями со значениями переменных strUsername и strPassword модифицировать SQL-запрос. Так, например, в случае если нарушитель в качестве регистрационного имени и пароля введёт значение «’OR ‘’=’», то тогда будет сформирован следующий SQL-запрос:

«SELECT Username FROM Users WHERE Username = ^^
OR ^^=^^ AND Password = ^^ OR ^^=^^»
(полужирным шрифтом выделены команды, которые внедряются
нарушителем в исходный SQL-запрос).

Поскольку в SQL-запрос были добавлены два новых параметра – «OR ^^=^^», то одновременно с поиском пустой учётной записи пользователя в таблице Users, будет возвращена первая строка таблицы. Поскольку в результате выполнения функции GetQueryResult будет возвращено непустое значение, то нарушитель сможет беспрепятственно пройти через процедуру аутентификации и получить доступ к ИС как легальный пользователь.

Активизация уязвимости «SQL Injection» с целью несанкционированного извлечения данных из СУБД

Для несанкционированного извлечения данных из СУБД нарушитель может добавить в исходный текст SQL-запроса оператор UNION SELECT, который позволяет формировать одновременно несколько запросов к таблицам СУБД. Рассмотрим процедуру активизации уязвимости «SQL Injection» этого типа на примере следующего запроса, выполняющего выборку из таблицы Employees по названию города, которое вводится пользователем и передаётся в запрос через переменную strCity:

SQLString = "SELECT FirstName, LastName
FROM Employees WHERE City = ^" & strCity & "^"

Для того, чтобы дополнительно извлечь информацию из другой таблицы с именем OtherTable нарушитель может ввести следующее значение через переменную strCity: «^ UNION ALL SELECT OtherField FROM OtherTable WHERE ^^=^». В результате такой вставки получится SQL-запрос, который позволит получить доступ к информации, которая хранится в поле OtherField таблицы OtherTable:

SELECT FirstName, LastName FROM Employees WHERE City =
^^ UNION ALL SELECT OtherField FROM OtherTable WHERE ^^=^^

После выполнения этого SQL-запроса будет сделана выборка данных из таблицы OtherTable. При этом никакой информации из таблицы Employees извлечено не будет, поскольку в сформированном запросе указано, что из этой таблицы необходимо получить только те записи, в которых поле City содержит пустое значение.

Несанкционированное извлечение данных из таблиц СУБД возможно путём несанкционированного искажения не только запроса SELECT, но и INSERT. Оператор INSERT используется для добавления информации в базы данных ИС. Активизация уязвимости «SQL Injection» в операторе INSERT возможна в том случае, если уязвимое приложение предусматривает возможность добавления в СУБД информации, вводимой пользователем, а также позволяет просматривать добавленные данные. Продемонстрируем вышесказанное следующим примером. Предположим, что в приложении проводится процедура регистрации пользователя, где он вводит свои персональные данные, которые затем при помощи INSERT-запроса записываются в таблицу TableName:

INSERT INTO TableName VALUES (^" & strValueOne &
"^, ^" & strValueTwo & "^, ^" & strValueThree & "^)"

При этом после прохождения процедуры регистрации пользователь имеет возможность просмотреть ранее введённые данные. С целью модификации исходного SQL-запроса нарушитель может ввести вместо одной из переменных strValueOne, strValueTwo или strValueThree SQL-запрос типа «’ + (SELECT TOP 1 FieldName FROM OtherName) + ‘». Это позволит злоумышленнику добавить в таблицу TableName не только свои персональные данные, но и информацию, извлечённую из СУБД при помощи вставленного запроса SELECT. Нарушитель может получить доступ к извлечённой информации при просмотре своих персональных данных.

Активизация уязвимости «SQL Injection» с целью несанкционированного изменения данных в СУБД

Для несанкционированного изменения содержимого таблиц SQL-сервера нарушитель может воспользоваться уязвимостью, базирующейся на возможности модификации исходного SELECT-запроса. Для внесения изменений в текст запроса необходимо добавить лишь оператор INTO, в параметрах которого указывается имя таблицы БД, куда необходимо добавить сделанную выборку. В результате такого изменения должен получиться запрос следующего типа: «SELECT <критерии выборки> FROM <имя таблицы, в которой делается выборка> INTO <имя таблицы, куда должна быть записана выборка>».

Активизация уязвимости «SQL Injection» с целью нарушения работоспособности SQL-сервера

Работоспособность SQL-сервера может быть нарушена при помощи несанкционированного использования рассмотренных выше запросов SELECT с параметром INTO. Для этого нарушителю достаточно сформировать их последовательность, которая предусматривала бы запись избыточного объёма информации в базу данных. После заполнения свободного дискового пространства работа сервера СУБД будет нарушена.

Уязвимости «format string»

Уязвимости типа «format string» («форматирующая строка»), могут быть характерны для тех приложений, в которых используются функции типа printf() с непроверяемым параметром форматирующей строки. Форматирующая строка используется для определения общего числа параметров вызова функции, а также правил преобразования параметров вызова в символьные значение. В листинге 4 приведён фрагмент исходного кода программы, в котором используется функция printf(), выводящая на экран десятичные значения переменных i и j. Форматирующая строка функции printf(), приведённой в листинге, содержится в строковой переменной format_string.

int i = 10;
int j = 20;
char *format_string = “Переменная i = %d, переменная j = %d”;
printf(format_string, i, j);

Листинг 4. Пример использования функции printf()

Параметры форматирующей строки задаются при помощи управляющего символа «%». Так, например, параметр «%d» используется для вывода десятичного числа, а параметр «%x» - для вывода шестнадцатеричного значения. Каждому параметру форматирования соответствует один аргумент, который указывается сразу после форматирующей строки. Для двух параметров «%d», приведённых в листинге 4, аргументами являются переменные i и j. Необходимо отметить, что если в форматирующей строке определён параметр, но отсутствует его аргумент, то в этом случае на экран будет выведено содержимое участка памяти стека, где должен находиться параметр. В форматирующей строке также может быть использован параметр «%n», который позволяет записывать в произвольный адрес памяти значение, которое определяется до параметра.

Потенциальная уязвимость «format string» возникает в том случае, когда содержимое форматирующей строки задаётся не разработчиком программы, а формируется на основе непроверяемых входных параметров вызова одной из функций программы. Например, для вывода на экран строкового значения переменной str можно использовать два варианта записи функции printf():

  • printf(“%s”, str); – при таком варианте использования функции printf() форматирующая строка «%s» задаётся разработчиком в явном виде и не может быть скомпрометирована нарушителем;
  • printf(str); – такое использование функции printf() может привести к тому, что потенциальный нарушитель сможет самостоятельно сформировать значение форматирующей строки при помощи переменной str и активизировать уязвимость «format string» путём манипуляции со значениями параметров «%x» и «%n».

Несанкционированное использование параметра «%x» может позволить нарушителю получить доступ к содержимому любого участка памяти в стеке, а при помощи параметра «%n» возможно изменение адреса возврата функции путём записи нового значения и передачи управления на вредоносный код, размещённый в памяти компьютера.

Уязвимости класса «format string» могут быть присущи любым приложениям, которые некорректно используют следующие функции: fprintf(), printf(), snprintf(), vprintf(), vsprintf(), vsnprintf(), secproctitle() и syslog().

Эксплуатационные уязвимости конфигурации программного обеспечения

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

  • наличие слабых, не стойких к угадыванию паролей доступа к ресурсам ИС. При активизации этой уязвимости нарушитель может получить несанкционированный доступ к ИС путём взлома пароля при помощи метода полного перебора или подбора по словарю;
  • наличие в системе незаблокированных встроенных учётных записей пользователей, при помощи которых потенциальный нарушитель может собрать дополнительную информацию, необходимую для проведения атаки. Примерами таких учётных записей являются запись «Guest» в операционных системах или запись «Anonymous» в FTP-серверах;
  • неправильным образом установленные права доступа пользователей к информационным ресурсам ИС. В случае если в результате ошибки администратора пользователи, работающие с системой, имеют больше прав доступа, чем это необходимо для выполнения их функциональных обязанностей, то это может привести к несанкционированному использованию дополнительных полномочий для проведения атак. Например, если пользователи будут иметь права доступа на чтение содержимого исходных текстов серверных сценариев, выполняемых на стороне Web-сервера, то этим может воспользоваться потенциальный нарушитель для изучения алгоритмов работы механизмов защиты Web-приложений и поиска в них уязвимых мест;
  • наличие в ИС неиспользуемых, но потенциально опасных сетевых служб и программных компонентов. Так, например, большая часть сетевых серверных служб, таких как Web-серверы и серверы СУБД поставляются вместе с примерами программ, которые демонстрируют функциональные возможности этих продуктов. В некоторых случаях эти программы имеют высокий уровень привилегий в системе или содержат уязвимости, использование которых злоумышленником может привести к нарушению информационной безопасности системы. Примерами таких программ являются образцы CGI-модулей, которые поставляются вместе с Web-приложениями, а также примеры хранимых процедур в серверах СУБД.

Методы выявления и устранения уязвимостей

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

Выявление уязвимостей типа «buffer overflow», «SQL Injection» и «format string» возможно либо путём анализа исходных текстов потенциально уязвимой программы, либо при помощи поведения анализа безопасности уже работающей программы. Первый способ предполагает экспертный анализ исходных текстов программы с целью поиска и исправления ошибок, которые были допущены на этапе её разработки. В большинстве случае для устранения выявленных уязвимостей необходимо добавление новых функций, обеспечивающих проверку корректности входных данных, поступающих в программу. Так, например, для исправления уязвимостей типа «buffer overflow» необходимо добавить процедуру проверки, которая должна следить за тем, чтобы объём входных данных не превышал максимальный размер переменной, для которой они предназначаются. Исправление уязвимости «SQL Injection» возможно путём защиты от вставки символа «’», который в большинстве случаев и позволяет модифицировать исходный SQL-запрос. Для устранения уязвимостей типа «format string» необходимо использовать такой формат вызова функций, в котором форматирующая строка задаётся в явном виде разработчиком программы. Как правило, метод анализа исходных текстов отличается высокой трудоёмкостью и используется только в компаниях, которые занимаются разработкой ПО.

Второй метод выявления уязвимостей используется для анализа защищённости ПО, которое уже установлено и функционирует в ИС. Метод предполагает использование специализированных программных средств – так называемых сканеров безопасности или систем анализа защищённости. Эти средства позволяют обнаруживать уязвимости на основе активного и пассивного методов. При помощи пассивного метода осуществляется сбор информации о настройках ПО, присутствующего в ИС и на основе этих данных делается вывод о наличии или отсутствии в системе уязвимостей. Так, например, если будет выявлено наличие ОС без установленного модуля Service Pack, то это означает, что она подвержена ряду уязвимостей. Активные методы анализа защищённости приложений имитируют информационные атаки и затем на основе анализа результатов делается вывод о наличии уязвимостей в системе. Совместное использование пассивных и активных методов анализа защищённости приложений ИС позволяет выявить не только уязвимости «buffer overflow», «SQL Injection» и «format string», но и эксплуатационные уязвимости конфигурации ПО. Устранение уязвимостей в этом случае возможно путём установки соответствующих модулей обновления (service packs, hotfixes, patches и др.) или изменения настроек используемого ПО. Рассмотренные активные и пассивные методы наиболее часто используются для анализа защищённости ПО, на основе которых функционируют ИС организаций.

Заключение

Изложенные материалы неоспоримо подтверждают существование той самой «Ахиллесовой пяты», которую и в настоящее время можно отнести к одним из основых уязвимых мест ИС. Существует большое число разных типов уязвимостей, потенциальное использование которых злоумышленниками может нанести непоправимый ущерб компаниям, базирующимся на сетях различного уровня и назначений и использующих уязвимые информационные системы. Однако любая потенциальная информационная атака всё же может быть предотвращена при условии своевременного выявления и устранения имеющихся уязвимостей. Для этого необходимо использовать аудит информационной безопасности ИС для проведения которого, как правило, приглашаются внешние коммерческие компании, работающие в области защиты информации. Необходимо так же отметить, что мероприятия по выявлению и устранению уязвимостей должны носить не единичный, а систематический характер. Только в этом случае возможно минимизировать вероятность успешной атаки на информационную систему.

Список литературы:

1. Pierre-Alain Fayolle, Vincent Glaume, “A buffer overflow Study. Attacks and Defenses”, ENSEIRM, 2002
2. Chris Anley, “Advanced SQL Injection in SQL Server Applications”, NISR, 2002
3. Kevin Spett, “SQL Injection. Are your Web applications vulnerable?”, SPI Dynamics, 2002
4. Tim Newsham, “Format String Attacks”, Guardent, 2000

533
;