Keamanan Siber

Account MariaDB dengan Least Privilege untuk Aplikasi PHP Kecil - Biarkan Aplikasi Memakai yang Dibutuhkan Saja

Account MariaDB dengan Least Privilege untuk Aplikasi PHP Kecil - Biarkan Aplikasi Memakai yang Dibutuhkan Saja

Sebuah aplikasi PHP kecil mungkin hanya memerlukan empat tindakan database sehari-hari: membaca baris, menambahkan baris, memperbaruinya, dan terkadang menghapusnya. Namun, koneksinya bisa saja diam-diam memakai account yang mampu mengubah tabel, membuat user, atau mengakses setiap database di server. Aplikasi tetap berjalan, sehingga ketimpangan ini tidak terlihat sampai bug, credential yang bocor, atau celah injection mengubah permission yang tidak diperlukan menjadi kerusakan tambahan.

Pertanyaan yang berguna bukan, “GRANT mana yang membuat error hilang?” Pertanyaannya adalah, “Apa kewenangan database paling sempit yang dibutuhkan proses ini untuk menjalankan tugas yang terdokumentasi?” Artikel ini menyusun metode review berdasarkan pertanyaan tersebut untuk aplikasi kecil berbasis PHP dan MariaDB. SQL di dalamnya memakai layanan catatan fiktif; contoh ini perlu disesuaikan setelah memeriksa query aplikasi, bukan klaim tentang konfigurasi website ini.

Least privilege membatasi kewenangan, bukan menghilangkan kerentanan

Glosarium NIST mendefinisikan least privilege sebagai pembatasan akses user, atau proses yang bertindak atas nama user, hingga batas minimum yang diperlukan untuk fungsi yang ditugaskan. Jika diterapkan pada koneksi database, proses PHP seharusnya mendapatkan permission yang cukup untuk request normal dan tidak lebih.

Ini adalah pembatasan dampak, bukan kekebalan. Jika penyerang menemukan SQL injection, least privilege tidak memperbaiki query yang rentan. Jika aplikasi memang boleh membaca semua alamat email pelanggan, runtime account-nya tidak dapat membuat alamat tersebut tidak terbaca oleh aplikasi yang sudah dikompromikan. Yang dapat dilakukan grant yang lebih sempit adalah menghapus kemampuan yang tidak berkaitan: endpoint read-only tidak perlu menghapus baris, request normal tidak perlu mengubah schema, dan satu aplikasi tidak perlu membuka database milik aplikasi lain.

Perbedaan ini penting karena “bukan root” baru langkah awal. Account dengan ALL PRIVILEGES pada satu database aplikasi memang lebih sempit daripada administrator server, tetapi mungkin masih memiliki kewenangan CREATE, ALTER, dan DROP yang tidak pernah dibutuhkan request web biasa.

Inventarisasi perilaku sebelum menulis GRANT

Daftar privilege yang disalin dari tutorial hanyalah tebakan. Mulailah dari jalur aktual aplikasi: controller, repository, queue worker, scheduled job, dan file migration. Kelompokkan setiap operasi database berdasarkan proses yang menjalankannya. Inventaris sederhana bisa terlihat seperti ini:

ProsesPekerjaan yang diharapkanPrivilege data yang mungkin dibutuhkanPerubahan schema
Runtime web publikMenampilkan daftar dan membaca catatan terbitSELECTTidak ada
Runtime web terautentikasiMembuat dan mengedit catatanSELECT, INSERT, UPDATE; DELETE hanya jika produk benar-benar menghapusTidak ada
Migration saat deploymentMenerapkan revisi schema yang sudah ditinjauBergantung pada migrationPrivilege DDL tertentu selama proses deployment
Administrasi manusiaMengelola account dan maintenance khususBergantung pada tugasBergantung pada tugas

Kata “mungkin” dipakai dengan sengaja. Fitur pencarian bisa menggunakan view; queue mungkin memperbarui baris status; alur upload mungkin menulis metadata; desain soft delete bisa memakai UPDATE, bukan DELETE. Review code statis dapat menemukan banyak hal, sedangkan pengujian staging mengungkap jalur yang terlewat. Keduanya tidak membuktikan kelengkapan jika berdiri sendiri.

Jangan menyimpulkan privilege permanen dari langkah instalasi satu kali. Jika proses setup membuat tabel lalu aplikasi menghabiskan berbulan-bulan untuk melayani request, pembuatan schema adalah tugas deployment, bukan tugas setiap request.

Berikan identitas berbeda untuk pekerjaan berbeda

OWASP Database Security Cheat Sheet menyarankan account terpisah untuk setiap aplikasi atau service, tidak memakai account administrator bawaan, membatasi akses ke host yang diizinkan, serta hanya memberikan database dan permission yang diperlukan. Penerapan praktisnya adalah memisahkan identitas menurut peran operasional:

  • Runtime account: dipakai PHP-FPM dan background job biasa; umumnya dibatasi pada operasi data aplikasi.
  • Migration account: dipakai oleh langkah deployment yang terkontrol, tidak disimpan di environment runtime web.
  • Administrative account: dipakai secara interaktif untuk mengelola account atau maintenance khusus, tidak pernah ditanam di konfigurasi aplikasi.

Pemisahan ini membuat batas tersebut dapat dipaksakan oleh database. Bug PHP tidak bisa memakai permission migration yang tidak pernah diterima proses PHP. Log dan rotasi credential juga menjadi lebih mudah dipahami. Ada biaya operasionalnya: deployment memerlukan secret terpisah dan prosedur yang terdokumentasi. Untuk aplikasi sangat kecil, hal ini mungkin terasa lebih berat daripada satu account serbaguna, tetapi pilihan kedua memindahkan kewenangan deployment ke setiap request web.

Ingat bahwa account MariaDB mencakup host

MariaDB mengenali account berdasarkan nama user dan host, ditulis sebagai 'user'@'host'. Dokumentasi CREATE USER menyebutkan bahwa host yang dihilangkan berarti %, yaitu wildcard. Kemudahan ini lebih luas daripada satu host aplikasi tertentu.

Transport koneksi juga berpengaruh. Di Linux, MariaDB mendokumentasikan 'user'@'localhost' sebagai pasangan untuk koneksi Unix socket lokal; koneksi TCP ke 127.0.0.1 bukan pasangan yang sama. Sebelum membuat account, periksa DSN PHP dan tentukan apakah aplikasi memakai socket, TCP loopback, atau host database terpisah. Batasi account pada jalur yang benar-benar digunakan, alih-alih mengubahnya menjadi % ketika autentikasi gagal.

Pembatasan host adalah satu lapisan, bukan pengganti firewall. OWASP juga menyarankan agar paparan jaringan database dibatasi pada sedikit host yang membutuhkannya. Aturan account dan aturan jaringan seharusnya selaras.

Buat grant runtime yang eksplisit

Dokumentasi MariaDB menyatakan bahwa CREATE USER membuat account tanpa privilege. Ini adalah halaman kosong yang berguna. Misalkan runtime fiktif notes_app memakai socket lokal dan jalur yang sudah ditinjau memerlukan empat operasi data pada database tersebut:

CREATE USER 'notes_runtime'@'localhost'
  IDENTIFIED BY 'REPLACE_WITH_A_GENERATED_SECRET';

GRANT SELECT, INSERT, UPDATE, DELETE
  ON `notes_app`.*
  TO 'notes_runtime'@'localhost';

Jalankan perintah pengelolaan account melalui koneksi administratif yang berwenang, bukan melalui aplikasi web. Placeholder tersebut bukan rekomendasi password. Buat dan kirimkan credential yang sebenarnya melalui proses pengelolaan secret saat deployment, jauhkan dari source control dan public web root, serta hindari paparan di shell history atau dokumentasi.

Referensi GRANT MariaDB membedakan scope global, database, tabel, kolom, dan routine. Di sini, notes_app.* berarti seluruh object dalam database tersebut, termasuk object baru yang relevan di masa depan. Ini bukan berarti setiap database di server. Meskipun demikian, tindakan yang tercantum harus sesuai dengan inventaris. Hilangkan DELETE jika runtime tidak pernah menghapus. Frontend publikasi yang hanya membaca dapat menerima:

GRANT SELECT
  ON `notes_app`.*
  TO 'notes_reader'@'localhost';

Lebih granular tidak otomatis lebih baik. Grant tingkat tabel dapat mencegah reader publik membuka tabel account privat:

GRANT SELECT
  ON `notes_app`.`published_notes`
  TO 'notes_reader'@'localhost';

Namun, puluhan grant tabel yang rapuh bisa rusak setiap kali schema berubah dan menjadi lebih sulit diaudit daripada grant tingkat database yang dipilih dengan baik. Pilih scope paling sempit yang dapat dijaga tetap akurat oleh tim. View atau routine mungkin menawarkan interface yang lebih bersih pada desain tertentu, tetapi keduanya membawa pertanyaan tersendiri tentang ownership dan execution context yang berada di luar pola dasar ini.

Jauhkan kewenangan schema dari request normal

Runtime account biasanya tidak punya alasan untuk memegang CREATE, ALTER, DROP, CREATE USER, FILE, atau GRANT OPTION. Permission migration yang tepat tidak bersifat universal: menambahkan index, membuat tabel, dan mengganti view adalah operasi berbeda. Turunkan grant migration account dari file migration yang sudah ditinjau dan strategi deployment, lalu pastikan credential tersebut tidak tersedia bagi PHP-FPM.

Ada juga trade-off reliability. Memberikan lalu mencabut DDL sementara pada setiap deployment bisa sangat presisi, tetapi mudah dilakukan dengan keliru. Migration account khusus dengan secret yang terlindungi mungkin lebih sederhana. Kedua desain tersebut lebih baik daripada diam-diam memberikan setiap privilege database kepada runtime account. Least privilege harus tetap dapat dioperasikan ketika deployment gagal, atau tekanan pada akhirnya akan menghasilkan GRANT ALL darurat.

Verifikasi permission dan penolakannya

Teks konfigurasi saja belum cukup. MariaDB menyediakan SHOW GRANTS untuk menampilkan grant yang tercatat bagi sebuah account:

SHOW GRANTS FOR 'notes_runtime'@'localhost';

Baca setiap baris sambil memperhatikan grant global, grant database yang lebih luas, role, dan komponen host yang tepat. Grant tabel yang sempit tidak membantu jika baris lain sudah memberikan kewenangan luas.

Setelah itu, uji perilaku di staging dengan metode koneksi dan konfigurasi yang sama seperti yang akan dipakai aplikasi. Positive test perlu menjalankan setiap jalur normal. Negative test perlu membuktikan bahwa tindakan terlarang gagal: misalnya, runtime account tidak boleh membuat atau mengubah tabel staging sekali pakai dan tidak boleh membaca database yang tidak berkaitan. Jangan pernah menjalankan destructive denial test pada data produksi.

Aturan penerimaan yang berguna bersifat asimetris: semua jalur aplikasi yang terdokumentasi berhasil, sedangkan contoh tindakan di luar scope gagal. Error pada jalur yang seharusnya berhasil berarti inventaris atau arsitektur perlu ditinjau kembali. Itu tidak otomatis berarti account harus menerima ALL PRIVILEGES.

Lakukan rollout tanpa menebak di bawah tekanan

  1. Buat account baru dan grant eksplisit tanpa mengubah runtime yang sedang digunakan.
  2. Uji di staging, termasuk queue, scheduled job, halaman maintenance, dan jalur penulisan yang jarang dipakai.
  3. Deploy credential baru melalui mekanisme secret yang biasa digunakan.
  4. Ganti koneksi database yang sudah ada agar aplikasi benar-benar melakukan autentikasi sebagai account baru.
  5. Amati error karena operasi ditolak dan kesehatan aplikasi selama rollout yang dibatasi.
  6. Hanya setelah keyakinan terbentuk, hapus credential runtime lama atau cabut grant yang sudah tidak diperlukan.

Dokumentasi REVOKE MariaDB pada intinya memperingatkan bahwa menghapus satu grant belum tentu menghapus kewenangan yang diberikan grant lebih luas. Setelah pencabutan apa pun, periksa kembali grant efektif dan ulangi denial test. Siapkan juga rollback sebelum rollout: ketahui permission spesifik yang dapat memulihkan jalur yang terlewat daripada langsung memberikan akses tanpa batas.

Privilege dapat mengalami drift ketika software berubah. Export job baru mungkin memerlukan pembacaan tabel lain; fitur yang ditinggalkan mungkin menyisakan DELETE. Tinjau grant bersama schema migration dan bandingkan secara berkala dengan jalur query saat ini. Ritmenya sebaiknya mengikuti seberapa sering aplikasi berubah, bukan interval universal yang dipilih tanpa alasan.

Kenali hal yang tidak dapat dilakukan batas ini

  • Ini tidak menggantikan parameterized query atau pertahanan SQL injection lainnya.
  • Ini tidak menentukan user aplikasi mana yang boleh mengakses baris tertentu; hal itu tetap menjadi masalah authorization aplikasi kecuali ada mekanisme database yang dirancang terpisah.
  • Ini tidak dapat menyembunyikan data yang memang harus dibaca runtime account.
  • Ini tidak melindungi password database yang terekspos; penyimpanan credential, rotasi, dan perlindungan jaringan tetap penting.
  • Ini tidak menggantikan backup, restore test, patching, monitoring, atau audit log.

Keterbatasan ini tidak membuat least privilege sekadar kosmetik. Batas tersebut menjelaskan tujuan yang jujur: mengurangi kewenangan yang tersedia ketika kontrol lain gagal.

Kesimpulan

Sebuah database account seharusnya menggambarkan satu pekerjaan. Untuk runtime PHP kecil, pekerjaan itu biasanya berupa akses data sehari-hari dalam satu database aplikasi, dari satu jalur koneksi yang diharapkan. Schema migration dan administrasi manusia adalah pekerjaan berbeda yang layak memperoleh identitas berbeda.

Workflow terkuat juga tidak dramatis: inventarisasi query nyata, buat account tanpa privilege, tambahkan grant eksplisit, verifikasi konfigurasi yang tercatat, buktikan operasi yang diharapkan berhasil, dan buktikan contoh operasi terlarang gagal. Periksa kembali bukti tersebut ketika aplikasi berubah. Tujuannya bukan menghasilkan grant sekecil mungkin di atas kertas, melainkan grant terkecil yang tetap benar, dapat diuji, dan dapat dipelihara.

References