Kisah bagaimana membuat mesin waktu untuk database dan secara tidak sengaja menulis exploit

Selamat siang, Habr.

Pernahkah Anda bertanya-tanya bagaimana cara mengubah waktu di dalam database? Mudah? Ya, dalam beberapa kasus, ya, itu mudah - perintah tanggal linux dan masalahnya sudah siap. Dan jika Anda perlu mengubah waktu hanya di dalam satu contoh dari database jika ada beberapa dari mereka di server? Dan untuk satu proses basis data? DAN? Eh, itu dia, temanku, itu intinya.Seseorang akan mengatakan bahwa ini adalah sur lain, tidak terkait dengan kenyataan, yang secara berkala dituangkan pada HabrΓ©. Tapi tidak, tugasnya cukup nyata dan ditentukan oleh kebutuhan produksi - pengujian kode. Meskipun saya setuju, test case ini bisa sangat eksotis - periksa bagaimana kode berperilaku untuk tanggal tertentu di masa depan. Pada artikel ini, saya akan memeriksa secara rinci bagaimana tugas ini diselesaikan, dan pada saat yang sama menangkap sedikit proses pengorganisasian tes dan dev singkatan dari basis Oracle. Menjelang bacaan yang panjang, merasa nyaman dan minta kucing.

Latar Belakang


Mari kita mulai dengan pengantar singkat untuk menunjukkan mengapa ini perlu. Seperti yang sudah diumumkan, kami menulis tes saat menerapkan pengeditan di database. Sistem di mana tes ini dilakukan dikembangkan pada awal (atau mungkin sedikit sebelum awal) dari nol, jadi semua logika bisnis ada di dalam database dan ditulis dalam bentuk prosedur tersimpan dalam bahasa pl / sql. Dan ya, itu membawa kita kesakitan dan penderitaan. Tetapi ini adalah warisan, dan Anda harus hidup dengannya. Dalam kode dan model tabular, dimungkinkan untuk menentukan bagaimana parameter di dalam sistem berevolusi dari waktu ke waktu, dengan kata lain, mengatur aktivitas dari tanggal mana dan tanggal berapa mereka dapat diterapkan. Apa yang harus dilakukan jauh - perubahan terbaru dalam tingkat PPN adalah contoh nyata dari ini. Dan agar perubahan dalam sistem dapat diperiksa terlebih dahulu,database dengan perubahan tersebut perlu ditransfer ke tanggal tertentu di masa mendatang, parameter kode dalam tabel akan menjadi aktif pada "saat ini". Dan karena spesifik dari sistem yang didukung, Anda tidak dapat menggunakan tes tiruan yang hanya akan mengubah nilai kembali dari tanggal sistem saat ini dalam bahasa ketika sesi tes dimulai.

Jadi, kami menentukan alasannya, maka kami perlu menentukan bagaimana tujuan itu tercapai. Untuk melakukan ini, saya akan membuat sedikit retrospektif dari opsi untuk membangun bangku tes untuk pengembang dan bagaimana setiap sesi tes dimulai.

Jaman Batu


Dahulu kala, ketika pohon-pohon kecil, dan mainframe besar, hanya ada 1 server untuk pengembangan dan juga sedang melakukan tes. Dan pada prinsipnya, semua ini sudah cukup untuk semua orang ( 640K sudah cukup untuk semua orang! )

Kontra: untuk tugas mengubah waktu, perlu melibatkan banyak departemen terkait - administrator sistem (melakukan perubahan waktu pada subd server dari root), administrator DBMS (melakukan restart database), programmer ( perlu untuk memberi tahu bahwa perubahan waktu akan terjadi, karena bagian dari kode berhenti bekerja, misalnya, token web yang sebelumnya dikeluarkan untuk memanggil metode api tidak lagi valid dan ini bisa mengejutkan, penguji (menguji sendiri) ... Ketika Anda mengembalikan waktu ke saat ini semuanya diulang dalam urutan terbalik.

Abad Pertengahan


Seiring waktu, jumlah pengembang di departemen bertambah dan pada beberapa titik 1 server tidak lagi mencukupi. Terutama karena kenyataan bahwa pengembang yang berbeda ingin mengubah paket pl / sql yang sama dan melakukan pengujian untuk itu (bahkan tanpa mengubah waktu). Semakin banyak kemarahan terdengar: "Berapa lama! Cukup mentolerir ini! Pabrik untuk pekerja, tanah untuk petani! Setiap programmer memiliki database! " Namun, jika Anda memiliki beberapa terabyte basis data produk, dan 50-100 pengembang, maka jujur ​​dalam bentuk ini, persyaratannya tidak terlalu nyata. Dan masih semua orang ingin tes dan basis dev tidak ketinggalan jauh di belakang penjualan, baik dalam struktur dan data di dalam tabel. Jadi ada server terpisah untuk pengujian, sebut saja pra-produksi. Itu dibangun dari 2 server yang identik,di mana penjualan dilakukan untuk memulihkan database dari dolar RMAN dan butuh sekitar 2-2,5 hari. Setelah pemulihan, database membuat anonimisasi data pribadi dan penting lainnya dan beban dari aplikasi pengujian diterapkan ke server ini (dan juga programmer sendiri selalu bekerja dengan server yang baru saja dipulihkan). Pekerjaan dengan server yang diperlukan dipastikan menggunakan cluster ip-resource yang didukung melalui corosync (alat pacu jantung). Ketika semua orang bekerja dengan server aktif, pada simpul ke-2, pemulihan basis data dimulai lagi dan setelah 2-3 hari mereka kembali mengubah tempat.Pekerjaan dengan server yang diperlukan dipastikan menggunakan cluster ip-resource yang didukung melalui corosync (alat pacu jantung). Ketika semua orang bekerja dengan server aktif, pada simpul ke-2, pemulihan basis data dimulai lagi dan setelah 2-3 hari mereka kembali mengubah tempat.Pekerjaan dengan server yang diperlukan dipastikan menggunakan cluster ip-resource yang didukung melalui corosync (alat pacu jantung). Ketika semua orang bekerja dengan server aktif, pada simpul ke-2, pemulihan basis data dimulai lagi dan setelah 2-3 hari mereka kembali mengubah tempat.

Dari kekurangan yang jelas: Anda membutuhkan 2 server dan sumber daya 2 kali lebih banyak (terutama disk) daripada prod.

Kelebihan: operasi perubahan waktu dan pengujian - ini dapat dilakukan pada server ke-2, pada server utama saat ini pengembang tinggal dan menjalankan bisnis mereka. Perubahan server hanya terjadi ketika database siap, dan waktu henti lingkungan pengujian minimal.

Era kemajuan ilmiah dan teknologi


Ketika kami beralih ke database 11g Release 2, kami membaca tentang teknologi menarik yang disediakan Oracle dengan nama CloneDB. Intinya adalah bahwa pencadangan basis data produk (ada salinan bit langsung dari file data produk) disimpan di server khusus, yang kemudian menerbitkan kumpulan file data ini melalui DNFS (NFS langsung) pada dasarnya sejumlah server, dan Anda tidak perlu memilikinya di server volume disk yang sama, karena pendekatan Copy-On-Write diimplementasikan: database menggunakan jaringan berbagi dengan file data dari server cadangan untuk membaca data dalam tabel, dan perubahan ditulis ke file data lokal di server dev itu sendiri. Secara berkala, "zeroing the deadline" dilakukan untuk server sehingga file data lokal tidak bertambah banyak dan tempat itu tidak berakhir. Saat memperbarui server, data juga direpersonalisasikan dalam tabel,dalam hal ini, semua pembaruan tabel jatuh ke file data lokal dan tabel tersebut dibaca dari server lokal, semua tabel lainnya dibaca melalui jaringan.

Cons: masih ada 2 server (untuk memastikan pembaruan lancar dengan downtime minimal untuk konsumen), tetapi sekarang volume disk sangat berkurang. Untuk menyimpan dolar pada bola nfs, Anda memerlukan 1 server lebih banyak dalam ukuran + - sebagai sebuah produk, tetapi waktu pelaksanaan pembaruan itu sendiri berkurang (terutama saat menggunakan dolar tambahan). Jaringan dengan bola nfs secara nyata memperlambat operasi baca IO. Untuk menggunakan teknologi CloneDB, pangkalan haruslah Edisi Perusahaan, dalam kasus kami, kami harus melakukan prosedur peningkatan pada basis pengujian setiap kali. Untungnya, basis data pengujian dikecualikan dari kebijakan lisensi Oracle.

Pro: operasi untuk memulihkan basis dari bakup membutuhkan waktu kurang dari 1 hari (saya tidak ingat waktu yang tepat).

Perubahan waktu: tidak ada perubahan besar. Meskipun saat ini skrip sudah dibuat untuk mengubah waktu di server dan me-restart database untuk melakukan ini tanpa menarik perhatian para petugas administrasi.

Era Sejarah Baru


Untuk menghemat ruang disk lebih banyak dan membuat pembacaan data offline, kami memutuskan untuk mengimplementasikan versi CloneDB kami (dengan flashback dan snapshots) menggunakan sistem file dengan kompresi. Selama tes pendahuluan, pilihan jatuh pada ZFS, meskipun tidak ada dukungan resmi untuk itu di kernel Linux (kutipan dari artikel) Sebagai perbandingan, kami juga melihat BTRFS (b-tree fs), yang dipromosikan Oracle, tetapi rasio kompresi kurang dengan konsumsi CPU dan RAM yang sama dalam pengujian. Untuk mengaktifkan dukungan ZFS pada RHEL5, kernelnya sendiri yang berbasis pada UEK (kernel perusahaan yang tidak bisa dipecahkan) dibangun, dan pada kapak dan kernel yang lebih baru Anda cukup menggunakan kernel UEK yang sudah jadi. Implementasi dari basis tes tersebut juga didasarkan pada mekanisme SAP, tetapi pada tingkat snapshot sistem file. 2 perangkat disk dipasok ke server, di satu, zfs pool dibuat, di mana melalui RMAN database siaga tambahan dibuat dari penjualan, dan karena kami menggunakan kompresi, partisi tersebut memakan waktu kurang dari produksi.
Sistem diinstal pada perangkat disk kedua dan sisanya diperlukan agar server dan database itu sendiri berfungsi, misalnya, partisi untuk undo dan temp. Kapan saja, Anda dapat membuat snapshot dari kumpulan zfs, yang kemudian dibuka sebagai basis data terpisah. Membuat snapshot membutuhkan beberapa detik. Itu ajaib! Dan basis data semacam itu dapat dimiringkan pada prinsipnya cukup banyak, jika saja server memiliki cukup RAM untuk semua contoh dan ukuran kumpulan zfs itu sendiri (untuk menyimpan perubahan dalam file data selama depersonalisasi dan selama siklus hidup klon basis data). Waktu utama untuk memperbarui basis tes adalah pengoperasian depersonalisasi data, tetapi juga cocok dalam 15-20 menit. Ada akselerasi yang signifikan.

Kekurangan: pada server Anda tidak dapat mengubah waktu hanya dengan menerjemahkan waktu sistem, karena semua instance database yang berjalan di server ini akan jatuh ke dalam waktu ini sekaligus. Solusi untuk masalah ini telah ditemukan dan akan dijelaskan di bagian yang sesuai. Ke depan, saya akan mengatakan bahwa itu memungkinkan Anda untuk mengubah waktu hanya dalam 1 instance dari database ( per contoh pendekatan waktu perubahan) tanpa mempengaruhi sisanya pada server yang sama. Dan waktu di server itu sendiri tidak berubah. Ini menghilangkan kebutuhan untuk skrip root untuk mengubah waktu di server. Juga pada tahap ini, otomatisasi perubahan waktu untuk contoh melalui Jenkins CI diimplementasikan dan pengguna (tim pengembangan yang relatif berbicara) yang memiliki stan mereka diberikan hak untuk pekerjaan yang mereka dapat ubah waktu, perbarui stan ke keadaan saat ini dengan penjualan, buat snapshot dan restorasi (rollback) dari pangkalan ke snapshot yang dibuat sebelumnya.

Era Sejarah Terakhir


Dengan hadirnya Oracle 12c, muncul teknologi baru - basis data yang dapat dicolokkan dan, sebagai hasilnya, basis data kontainer (cdb). Dengan teknologi ini, dalam satu instance fisik, beberapa basis data "virtual" dapat dibuat yang berbagi area memori umum dari instance tersebut. Kelebihan: Anda dapat menghemat memori untuk server (dan meningkatkan kinerja keseluruhan basis data kami, karena semua memori yang ditempati sebelumnya, misalnya, 5 contoh berbeda, dapat dibagi untuk semua wadah pdb yang digunakan di dalam cdb, dan mereka hanya akan menggunakannya ketika mereka benar-benar membutuhkannya, dan tidak seperti pada fase sebelumnya, ketika setiap contoh "diblokir" memori dialokasikan untuk itu sendiri dan ketika aktivitas salah satu klon rendah, memori tidak digunakan secara efektif, dengan kata lain, itu menganggur).File data dari pdb berbeda masih terletak di kumpulan zfs, dan ketika menggunakan klon, mereka menggunakan mekanisme snapshot zfs yang sama. Pada tahap ini, kami mendekati kemampuan untuk memberikan hampir setiap pengembang database mereka sendiri. Mengubah waktu pada tahap ini tidak memerlukan restart database dan bekerja sangat akurat hanya untuk proses-proses yang membutuhkan perubahan waktu, semua pengguna lain yang bekerja dengan database ini tidak terpengaruh sama sekali.

Minus: Anda tidak dapat menggunakan pendekatan perubahan waktu per instance dari fase sebelumnya, karena kami memiliki satu instance sekarang. Namun, solusi untuk kasus ini ditemukan. Dan justru inilah yang menjadi dorongan untuk menulis artikel ini. Ke depan, saya akan mengatakan bahwa ini adalah perubahan waktu per pendekatan proses yaitu. dalam setiap proses basis data, Anda dapat mengatur waktu unik Anda sendiri secara umum.

Dalam kasus ini, sesi pengujian yang khas segera setelah terhubung ke database menetapkan waktu yang tepat di awal pekerjaannya, melakukan tes dan mengembalikan waktu kembali di akhir. Mengembalikan waktu diperlukan untuk satu alasan sederhana: beberapa proses basis data Oracle tidak berakhir ketika klien basis data terputus dari server, ini adalah proses server yang disebut shared server, yang, tidak seperti proses khusus, berjalan ketika server basis data mulai dan hidup hampir tanpa batas (dalam kondisi ideal) gambar dunia). Jika Anda membiarkan waktu berubah dalam proses server seperti itu, maka koneksi lain yang akan dilayani dalam proses ini akan menerima waktu yang salah.

Dalam sistem kami, server bersama banyak digunakan, karena hingga 11g praktis tidak ada solusi yang memadai bagi sistem kami untuk menahan beban tinggi (dalam 11g DRCP muncul - pengumpulan koneksi penduduk basis data). Dan inilah alasannya - dalam sub ada batasan pada jumlah total proses server yang dapat dibuat dalam mode khusus dan bersama. Proses khusus dihasilkan lebih lambat daripada database dapat mengeluarkan proses berbagi yang sudah siap dari kumpulan proses bersama, yang berarti bahwa ketika koneksi baru terus-menerus datang (terutama jika proses melakukan beberapa operasi lambat lainnya), jumlah total proses akan meningkat. Ketika batas sesi / proses tercapai, basis data berhenti untuk melayani koneksi baru dan runtuh terjadi.Transisi ke penggunaan kumpulan proses bersama memungkinkan kami untuk mengurangi jumlah proses baru di server saat menghubungkan.

Di situlah peninjauan teknologi untuk membangun database uji selesai, dan kami akhirnya dapat mulai menerapkan algoritma perubahan-waktu untuk database itu sendiri.

Pendekatan per instance palsu


Bagaimana cara mengubah waktu di dalam database?

Hal pertama yang terlintas dalam pikiran adalah membuat skema yang berisi semua kode logika bisnis, fungsinya sendiri, yang tumpang tindih dengan fungsi bahasa yang bekerja dengan waktu (sysdate, current_date, dll.) Dan dalam kondisi tertentu mulai memberikan nilai-nilai lain, misalnya, ia bisa set nilai melalui konteks sesi di awal uji coba. Itu tidak berhasil, fungsi bahasa built-in tidak tumpang tindih dengan yang pengguna.

Kemudian, sistem virtualisasi ringan (Vserver, OpenVZ) dan kontainerisasi melalui buruh pelabuhan diuji. Tidak juga berfungsi, mereka menggunakan kernel yang sama dengan sistem host, yang berarti mereka menggunakan nilai pengatur waktu sistem yang sama. Jatuh lagi.

Dan di sini saya tidak takut untuk menyelamatkan kata ini, sebuah penemuan cemerlang dari dunia Linux - redefinisi / intersepsi fungsi pada tahap pemuatan dinamis objek yang dibagikan. Ini dikenal banyak orang sebagai trik dengan LD_PRELOAD. Dalam variabel lingkungan LD_PRELOAD, Anda bisa menentukan pustaka yang akan memuat sebelum semua yang lain yang dibutuhkan proses, dan jika pustaka ini memiliki karakter dengan nama yang sama seperti misalnya dalam libc standar, yang akan dimuat nanti, maka tabel impor simbol untuk aplikasi akan terlihat seperti apakah fungsi tersebut menyediakan modul pengganti kami. Dan itulah yang dilakukan perpustakaan proyek libfaketimeyang kami mulai gunakan untuk memulai database pada waktu yang berbeda secara terpisah dari sistem. Perpustakaan melewatkan panggilan yang berhubungan dengan timer sistem dan mendapatkan waktu dan tanggal sistem. Untuk mengontrol berapa banyak waktu bergerak relatif ke tanggal server saat ini atau dari titik waktu waktu harus masuk ke dalam proses - semuanya dikendalikan oleh variabel lingkungan yang harus ditetapkan bersama dengan LD_PRELOAD. Untuk mengimplementasikan perubahan waktu, kami mengimplementasikan pekerjaan di server Jenkins, yang memasuki server database dan me-restart DBMS baik dengan atau tanpa variabel lingkungan yang ditetapkan untuk libfaketime.

Contoh algoritma untuk memulai database dengan waktu penggantian:

export LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so
export FAKETIME="+1d"
export FAKETIME_NO_CACHE=1

$ORACLE_HOME/bin/sqlplus @/home/oracle/scripts/restart_db.sql

Dan jika Anda berpikir bahwa semuanya bekerja segera, maka Anda salah besar. Karena, ternyata, memvalidasi pustaka yang dimuat ke dalam proses ketika DBMS dimulai. Dan di log peringatan, ia mulai membenci pemalsuan yang diketahui, sementara pangkalan tidak dimulai. Sekarang saya tidak ingat persis bagaimana cara menghilangkannya, ada beberapa parameter yang dapat menonaktifkan pelaksanaan pemeriksaan kewarasan saat startup.

Pendekatan palsu per proses


Gagasan umum untuk mengubah waktu hanya dalam 1 proses tetap sama - gunakan libfaketime. Kami memulai database dengan pustaka yang dimuat di dalamnya, tetapi menetapkan offset waktu nol pada saat startup, yang kemudian disebarkan ke semua proses DBMS. Dan kemudian, di dalam sesi tes, tetapkan variabel lingkungan untuk proses ini saja. Pff, bisnis sesuatu.

Namun, bagi mereka yang terbiasa dengan bahasa pl / sql, seluruh malapetaka ide ini segera jelas. Karena bahasanya sangat terbatas dan pada dasarnya cocok untuk tugas tingkat tinggi. Tidak ada pemrograman sistem yang dapat diterapkan di sana. Meskipun beberapa operasi tingkat rendah (misalnya, bekerja dengan jaringan, bekerja dengan file) hadir dalam bentuk paket sistem / paket utb yang diinstal sebelumnya. Selama saya bekerja dengan Oracle, saya melakukan reverse engineering paket pra-instal beberapa kali, kode beberapa di antaranya disembunyikan dari mata orang asing (mereka disebut dibungkus). Jika Anda dilarang untuk menonton sesuatu, maka godaan untuk mengetahui bagaimana hal itu diatur di dalam hanya meningkat. Tetapi seringkali, bahkan setelah anvrapper, tidak selalu ada sesuatu untuk dilihat, karena fungsi dari paket-paket tersebut diimplementasikan sebagai antarmuka c untuk perpustakaan di disk.
Secara total, kami mendekati satu kandidat untuk implementasi - teknologi dengan prosedur eksternal .
Perpustakaan yang dirancang dengan cara khusus dapat mengekspor metode, yang kemudian dapat dipanggil oleh database Oracle melalui pl / sql. Tampak menjanjikan. Hanya sekali saya bertemu ini di kursus lanjutan plsql, jadi saya ingat sangat jauh cara memasaknya. Dan itu berarti perlu membaca dokumentasi. Saya membacanya - dan segera menjadi depresi. Karena pemuatan pustaka seperti itu, maka pustaka berjalan dalam proses agen terpisah melalui pendengar basis data, dan komunikasi dengan agen ini melalui dlink. Jadi ide kami berteriak untuk mengatur variabel lingkungan di dalam proses database itu sendiri. Dan ini semua dilakukan untuk alasan keamanan.

Gambar dari dokumentasi yang menunjukkan cara kerjanya:



Jenis pustaka begitu / dll tidak begitu penting, tetapi untuk beberapa alasan gambar hanya untuk Windows.

Mungkin seseorang memperhatikan peluang potensial lain di sini. Ya, ya, ini Jawa. Oracle memungkinkan Anda untuk menulis kode prosedur tersimpan tidak hanya di plsql, tetapi juga di java, yang diekspor dengan cara yang sama seperti metode plsql. Secara berkala, saya melakukan ini, jadi seharusnya tidak ada masalah dengan ini. Tapi kemudian perangkap lain disembunyikan. Java bekerja dengan salinan lingkungan, dan memungkinkan Anda untuk hanya mendapatkan variabel lingkungan yang proses JVM miliki saat startup. JVM bawaan mewarisi variabel lingkungan dari proses basis data, tetapi hanya itu. Saya melihat tips di internet bagaimana mengubah peta yang hanya dibaca melalui refleksi, tapi apa gunanya, karena itu masih hanya salinan. Artinya, wanita itu lagi-lagi tidak punya apa-apa.

Namun, Jawa bukan hanya bulu yang berharga. Dengan menggunakannya, Anda dapat menelurkan proses dari dalam proses basis data. Meskipun semua operasi yang tidak aman harus diselesaikan secara terpisah melalui mekanisme hibah java, yang dilakukan dengan menggunakan paket dbms_java. Dari dalam kode plsql, Anda bisa mendapatkan proses pid dari proses server saat ini di mana kode berjalan, menggunakan sistem dilihat v $ sesi dan proses v $. Selanjutnya kita dapat menelurkan beberapa proses anak dari sesi kami untuk melakukan sesuatu dengan pid ini. Untuk memulai, saya hanya menyimpulkan semua variabel lingkungan yang ada di dalam proses basis data (untuk menguji hipotesis)

#!/bin/sh

pid=$1

awk 'BEGIN {RS="\0"; ORS="\n"} $0' "/proc/$pid/environ"

Disimpulkan dengan baik, lalu apa. Masih tidak mungkin untuk mengubah variabel dalam file environment, ini adalah data yang ditransfer ke proses ketika dimulai dan mereka hanya dibaca.

Saya mencari di Internet di stackoverflow "Bagaimana cara mengubah variabel lingkungan dalam proses lain." Sebagian besar jawabannya adalah bahwa itu tidak mungkin, tetapi ada satu jawaban yang menggambarkan peluang ini sebagai peretasan di bawah standar dan kotor. Dan jawaban itu adalah Albert Einstein gdb. Debugger dapat menghubungkan ke setiap proses mengetahui pidnya dan menjalankan fungsi / prosedur di dalamnya yang ada di dalamnya sebagai simbol yang diekspor secara publik, misalnya, dari beberapa perpustakaan. Di libc, ada fungsi untuk bekerja dengan variabel lingkungan, dan libc dimuat ke dalam proses apa pun dari database Oracle (dan hampir semua program di linux).

Ini adalah bagaimana variabel lingkungan diatur dalam proses asing (Anda perlu menyebutnya dari root karena ptrace yang digunakan):

#!/bin/sh

pid=$1
env_name=$2
env_val="$3"

out=`gdb -q -batch -ex "attach $pid" -ex 'call (int) setenv("'$env_name'", "'"$env_val"'", 1)' -ex "detach" 2>&1`


Juga, untuk melihat variabel lingkungan di dalam proses gdb juga cocok. Seperti disebutkan sebelumnya, file environment dari / proc / pid / hanya menunjukkan variabel yang ada pada awal proses. Dan jika proses tersebut menciptakan sesuatu dalam perjalanan kerjanya, maka ini hanya dapat dilihat melalui debugger:
#!/bin/sh

pid=$1
var_name=$2

var_value=`gdb -q -batch -ex "attach $pid" -ex 'call (char*) getenv("'$var_name'")' -ex 'detach' | egrep '^\$1 ='`

if [ "$var_value" == '$1 = 0x0' ]
then
  # variable empty or does not exist
  echo -n
else
  # gdb returns $1 = hex_value "string value"
  var_hex=`echo "$var_value" | awk '{print $3}'`
  var_value=`echo "$var_value" | sed -r -e 's/^\$1 = '$var_hex' //;s/^"//;s/"$//'`
  
  echo -n "$var_value"
fi


Jadi, solusinya sudah di saku kita - melalui java kita menelurkan proses debugger, yang pergi ke proses yang menghasilkannya dan menetapkan variabel lingkungan yang diinginkan untuk itu dan kemudian berakhir (Moor telah melakukan pekerjaannya - Moor dapat pergi). Tetapi ada perasaan bahwa itu semacam tongkat penyangga. Saya menginginkan sesuatu yang lebih elegan. Entah bagaimana semuanya akan memaksa proses database itu sendiri untuk mengatur variabel lingkungan tanpa serangan eksternal.

Telur di bebek, bebek di kelinci ...


Dan kemudian seseorang datang untuk menyelamatkan, ya, Anda dapat menebaknya dengan benar, sekali lagi Java, yaitu JNI (antarmuka asli java). JNI memungkinkan Anda untuk memanggil metode C asli di dalam JVM. Kode dikeluarkan dengan cara khusus dalam bentuk objek bersama dari perpustakaan, yang kemudian dimuat JVM, sedangkan metode yang ada di perpustakaan memetakan ke metode java di dalam kelas yang dideklarasikan dengan pengubah asli.

Baiklah, kita menulis kelas (sebenarnya, ini hanya benda kerja):

public class Posix {

    private static native int setenv(String key, String value, boolean overwrite);

    private static native String getenv(String key);
    
    public static void stub() 
    {
        
    }
}

Setelah itu, kompilasi dan dapatkan file-h yang dihasilkan dari perpustakaan masa depan:

#  
javac Posix.java

#   Posix.h        JNI
javah Posix

Setelah menerima file header, kami menulis isi untuk setiap metode:

#include <stdlib.h>
#include "Posix.h"

JNIEXPORT jint JNICALL Java_Posix_setenv(JNIEnv *env, jclass cls, jstring key, jstring value, jboolean overwrite)
{
    char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL);
    char* v = (char *) (*env)->GetStringUTFChars(env, value, NULL);

    int err = setenv(k, v, overwrite);

    (*env)->ReleaseStringUTFChars(env, key, k);
    (*env)->ReleaseStringUTFChars(env, value, v);

    return err;
}

JNIEXPORT jstring JNICALL Java_Posix_getenv(JNIEnv *env, jclass cls, jstring key)
{
    char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL);
    char* v = getenv(k);

    return (*env)->NewStringUTF(env, v);
}

dan kompilasi perpustakaan

gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -fPIC Posix.c -shared -o libPosix.so -Wl,-soname -Wl,--no-whole-archive

strip libPosix.so

Agar Java memuat perpustakaan asli, itu harus ditemukan oleh sistem ld sesuai dengan semua aturan Linux. Selain itu, Java memiliki serangkaian properti yang berisi jalur tempat pencarian pustaka berlangsung. Cara termudah untuk bekerja di dalam Oracle adalah dengan menempatkan perpustakaan kami di $ ORACLE_HOME / lib.

Dan setelah kita membuat pustaka, kita perlu mengkompilasi kelas di dalam database dan menerbitkannya sebagai paket plsql. Ada 2 opsi untuk membuat kelas Java di dalam database:

  • memuat file kelas biner melalui utilitas loadjava
  • kompilasi kode kelas dari sumber menggunakan sqlplus

Kami akan menggunakan metode kedua, meskipun mereka pada dasarnya sama. Untuk kasus pertama, perlu segera menulis semua kode kelas di tahap 1, ketika kami menerima kelas rintisan untuk file-h.

Untuk membuat kelas dalam subd, sintaks khusus digunakan:

CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "Posix" AS
...
...
/

Ketika kelas dibuat, perlu dipublikasikan sebagai metode plsql, dan di sini lagi sintaks khusus:

procedure set_env(var_name varchar2, var_value varchar2)
is
language java name 'Posix.set_env(java.lang.String, java.lang.String)';

Saat Anda mencoba memanggil metode yang berpotensi tidak aman di dalam Java, eksekusi akan muncul yang mengatakan bahwa tidak ada hibah java yang telah dikeluarkan untuk pengguna. Memuat metode asli adalah operasi lain yang tidak aman, karena kami menyuntikkan kode asing secara langsung ke dalam proses basis data (exploit yang sama dengan yang diumumkan di header).

Tetapi karena database adalah tes, kami memberikan hibah tanpa masalah koneksi dari sys:

begin
dbms_java.grant_permission( 'SYSTEM', 'SYS:java.lang.RuntimePermission', 'loadLibrary.Posix', '');
commit;
end;
/

Nama pengguna sistem adalah yang saya gunakan untuk mengkompilasi paket kode java dan plsql.
Penting untuk dicatat bahwa ketika memuat pustaka melalui panggilan ke System.loadLibrary, kami menghilangkan awalan lib dan ekstensi (seperti yang dijelaskan dalam dokumentasi) dan tidak melewati jalur apa pun ke mana harus mencari. Ada metode System.load serupa yang hanya bisa memuat perpustakaan menggunakan jalur absolut.

Dan kemudian 2 kejutan yang tidak menyenangkan menunggu kita - saya mendarat di lubang kelinci Oracle berikutnya. Saat mengeluarkan hibah, kesalahan terjadi dengan pesan yang agak berkabut:

ORA-29532: Java call terminated by uncaught Java exception: java.lang.SecurityException: policy table update

Masalahnya adalah googled di Internet dan mengarah ke Dukungan Oracle Saya (alias Metalink). Karena Menurut aturan Oracle, menerbitkan artikel dari metalink tidak diperbolehkan di sumber terbuka, saya hanya akan menyebutkan nomor dokumen - 259471.1 (mereka yang memiliki akses akan dapat membaca sendiri).

Inti masalahnya adalah bahwa Oracle tidak akan membiarkan kami mengizinkan pemuatan kode pihak ketiga yang mencurigakan ke dalam proses kami. Itu logis.

Tetapi karena basis ini adalah tes dan kami yakin dengan kode kami, kami mengizinkan unduhan tanpa ketakutan khusus.
Fuh, misadventures sudah berakhir.

Itu hidup, hidup


Dengan nafas yang tertahan, saya memutuskan untuk mencoba menghembuskan napas ke Frankenstein saya.
Kami memulai database dengan libfaketime yang sudah dimuat sebelumnya dan offset 0.
Hubungkan ke database dan buat panggilan ke kode yang hanya menampilkan waktu sebelum dan sesudah mengubah variabel lingkungan:

begin
dbms_output.enable(100000);
dbms_java.set_output(100000);
dbms_output.put_line('old time: '||to_char(sysdate, 'dd.mm.yyyy hh24:mi:ss'));
system.posix.set_env('FAKETIME','+1d');
dbms_output.put_line('new time: '||to_char(sysdate, 'dd.mm.yyyy hh24:mi:ss'));
end;
/


Ini berhasil, sial! Jujur, saya mengharapkan beberapa kejutan lagi, seperti kesalahan ORA-600. Namun, peringatan memiliki seluruh nomor dan kode terus berfungsi.
Penting untuk dicatat bahwa jika koneksi ke database dilakukan sebagai dedicated, maka setelah koneksi selesai, proses akan dihancurkan dan tidak akan ada jejak. Tetapi jika kita menggunakan koneksi bersama, maka dalam hal ini proses siap pakai dialokasikan dari kumpulan server, kami mengubah waktu di dalamnya melalui variabel lingkungan dan ketika diputuskan akan tetap berubah di dalam proses. Dan ketika kemudian sesi database lain jatuh ke proses server yang sama, itu akan menerima waktu yang salah dengan kejutan yang cukup besar. Oleh karena itu, pada akhir sesi tes, lebih baik untuk selalu mengembalikan waktu kembali ke nol offset.

Kesimpulan


Saya harap ceritanya menarik (dan mungkin bahkan bermanfaat bagi seseorang).

Semua kode sumber tersedia di Github .

Dokumentasi libfaketime juga .

Bagaimana Anda melakukan pengujian? Dan bagaimana Anda membuat database dev dan pengujian di perusahaan?

Bonus untuk mereka yang membaca sampai akhir


All Articles