Dynamic SQL di Oracle PL/SQL yang diimplementasikan melalui EXECUTE IMMEDIATE atau paket DBMS_SQL rentan terhadap serangan SQL Injection jika parameter input digabungkan langsung melalui konkatenasi string. Bind variable merupakan solusi standar untuk mitigasi injeksi pada nilai data, tetapi mesin SQL Oracle melarang penggunaan bind variable untuk dynamic identifier seperti nama tabel, nama kolom, klausa order, atau nama skema.

Untuk mengamankan identifier dinamis tanpa mengorbankan fleksibilitas kode, Oracle menyediakan paket bawaan DBMS_ASSERT. Paket ini memvalidasi dan membersihkan input sebelum string dieksekusi oleh mesin SQL dinamis.

Batasan Bind Variable pada Dynamic Identifier

Oracle Database memerlukan struktur metadata query (seperti tabel dan kolom) saat fase parse. Bind variable hanya menggantikan konstanta literal pada fase execution. Contoh kode berikut akan selalu memicu kegagalan sintaksis runtime:

-- Error: ORA-00903: invalid table name
PROCEDURE get_total_records(
    p_table IN VARCHAR2,
    p_count OUT NUMBER
) IS
BEGIN
    EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM :1' INTO p_count USING p_table;
END;

Karena pembatasan arsitektural ini, pengembang sering kembali menggunakan konkatenasi string ('SELECT COUNT(*) FROM ' || p_table). Pendekatan tersebut membuka celah eksploitasi jika p_table dikontrol oleh input eksternal.

Fungsi Validasi Utama dalam DBMS_ASSERT

Paket DBMS_ASSERT menyediakan fungsi pembersihan dan validasi struktural untuk identifier maupun literal:

  • SQL_OBJECT_NAME(str VARCHAR2): Memverifikasi bahwa string merujuk pada objek database SQL yang benar-benar ada dan dapat diakses oleh konteks sesi saat ini. Menghasilkan error jika objek fiktif atau mengandung payload berbahaya.
  • ENQUOTE_NAME(str VARCHAR2, capitalize BOOLEAN DEFAULT TRUE): Mengapit string dengan tanda petik ganda (double quotes) untuk memastikan karakter khusus tidak memecah token SQL, serta mengubahnya menjadi huruf kapital secara default.
  • ENQUOTE_LITERAL(str VARCHAR2): Mengapit literal dengan tanda petik tunggal dan menduplikasi setiap tanda petik tunggal di dalam string untuk mencegah quote escaping.
  • SCHEMA_NAME(str VARCHAR2): Memvalidasi bahwa input adalah nama skema (user database) yang valid di instans database.
  • SIMPLE_SQL_NAME(str VARCHAR2): Memeriksa apakah input mengikuti aturan penamaan identifier sederhana Oracle (panjang maksimal, karakter alfanumerik dasar, dimulai dengan alfabet) tanpa memeriksa eksistensi objek di kamus data.

Perbandingan Kode: Pola Rentan vs Pola Aman

Berikut adalah perbandingan prosedur pencarian data dinamis berdasarkan kolom dan filter tertentu.

1. Pola Rentan (Vulnerable Pattern)

CREATE OR REPLACE PROCEDURE query_logs_vulnerable(
    p_column IN VARCHAR2,
    p_filter IN VARCHAR2
) IS
    v_count NUMBER;
    v_sql   VARCHAR2(1000);
BEGIN
    -- Konkatenasi langsung pada identifier dan nilai literal
    v_sql := 'SELECT COUNT(*) FROM audit_logs WHERE ' || p_column || ' = ''' || p_filter || '''';
    EXECUTE IMMEDIATE v_sql INTO v_count;
    DBMS_OUTPUT.PUT_LINE('Total: ' || v_count);
END;

Jika p_column diisi STATUS = 'FAIL' OR 1=1 --, logika kueri rusak total dan mengeksekusi kondisi tak diotorisasi.

2. Pola Aman Menggunakan DBMS_ASSERT dan Bind Variables

CREATE OR REPLACE PROCEDURE query_logs_secure(
    p_column IN VARCHAR2,
    p_filter IN VARCHAR2
) IS
    v_clean_col VARCHAR2(128);
    v_count     NUMBER;
    v_sql       VARCHAR2(1000);
BEGIN
    -- Validasi identifier kolom sebagai simple SQL name yang aman
    v_clean_col := DBMS_ASSERT.SIMPLE_SQL_NAME(p_column);

    -- Bangun query: identifier hasil sanitasi, nilai data menggunakan bind variable
    v_sql := 'SELECT COUNT(*) FROM audit_logs WHERE ' || DBMS_ASSERT.ENQUOTE_NAME(v_clean_col, FALSE) || ' = :val';
    
    EXECUTE IMMEDIATE v_sql INTO v_count USING p_filter;
    DBMS_OUTPUT.PUT_LINE('Total: ' || v_count);
END;

Penanganan Exception dan Pola Whitelist

Ketika input gagal memenuhi kriteria validasi, DBMS_ASSERT melempar exception Oracle bawaan:

  • ORA-44001: Invalid schema. Muncul pada kegagalan validasi SCHEMA_NAME.
  • ORA-44002: Invalid object name. Muncul saat SQL_OBJECT_NAME menerima nama objek yang tidak ada atau formatnya salah.
  • ORA-44003: String is not a simple SQL name. Muncul saat SIMPLE_SQL_NAME mendeteksi spasi, semicolon, atau karakter manipulasi sintaksis lainnya.

Untuk tingkat keamanan tertinggi pada kolom atau klausa penyortiran dinamis, padukan DBMS_ASSERT dengan pola whitelist eksplisit:

CREATE OR REPLACE FUNCTION get_ordered_data(
    p_table    IN VARCHAR2,
    p_sort_col IN VARCHAR2
) RETURN SYS_REFCURSOR IS
    v_rc        SYS_REFCURSOR;
    v_clean_tab VARCHAR2(128);
    v_sql       VARCHAR2(1000);
BEGIN
    -- 1. Validasi tabel melalui katalog objek database
    v_clean_tab := DBMS_ASSERT.SQL_OBJECT_NAME(p_table);

    -- 2. Whitelist kolom secara deterministik
    IF UPPER(p_sort_col) NOT IN ('CREATED_AT', 'TRANSACTION_ID', 'AMOUNT') THEN
        RAISE_APPLICATION_ERROR(-20001, 'Kolom sorting tidak diizinkan.');
    END IF;

    v_sql := 'SELECT * FROM ' || v_clean_tab || ' ORDER BY ' || DBMS_ASSERT.ENQUOTE_NAME(p_sort_col);
    OPEN v_rc FOR v_sql;
    RETURN v_rc;
EXCEPTION
    WHEN OTHERS THEN
        IF SQLCODE IN (-44001, -44002, -44003) THEN
            RAISE_APPLICATION_ERROR(-20002, 'Identifier database ilegal terdeteksi.');
        ELSE
            RAISE;
        END IF;
END;

Implikasi Keamanan: AUTHID DEFINER vs AUTHID CURRENT_USER

Mode eksekusi unit PL/SQL memengaruhi mekanisme resolusi objek pada DBMS_ASSERT.SQL_OBJECT_NAME:

  • AUTHID DEFINER (Default): Prosedur berjalan dengan hak istimewa pembuat/pemilik paket (schema owner). SQL_OBJECT_NAME akan memvalidasi keberadaan tabel berdasarkan visibilitas pemilik paket, bukan pemanggil (caller). Jika terjadi eskalasi, penyerang dapat mengakses metadata tabel internal yang seharusnya tidak terlihat oleh caller.
  • AUTHID CURRENT_USER (Invoker Rights): Prosedur berjalan dengan privilese sesi pemanggil. SQL_OBJECT_NAME menyelesaikan resolusi objek melalui sudut pandang caller. Jika caller tidak memiliki hak SELECT pada tabel yang dituju, fungsi langsung gagal sebelum query dieksekusi. Gunakan mode ini untuk prosedur dinamis lintas skema.

Pertimbangan Performa

Meskipun DBMS_ASSERT krusial untuk aspek keamanan, pemilihan fungsi yang tepat memengaruhi throughput sistem:

  • DBMS_ASSERT.SQL_OBJECT_NAME dan SCHEMA_NAME melakukan lookup ke kamus data (data dictionary / library cache). Penggunaan di dalam loop intensif dapat menimbulkan konkurensi pada latch library cache. Validasi cukup dilakukan satu kali di luar loop sebelum proses batch.
  • DBMS_ASSERT.SIMPLE_SQL_NAME dan ENQUOTE_NAME adalah operasi pemrosesan string murni di memori (pure CPU bound) tanpa akses I/O ke kamus data. Jika integritas nama tabel sudah dijamin oleh whitelist aplikasi, gunakan SIMPLE_SQL_NAME untuk menghindari overhead katalog database.