Cara menggunakan mysql socket connection refused

Sebagian besar dari kita, apalagi para programer dan sysadmin Web Unix, pasti sudah cukup familiar dengan software ini. MySQL—yang pernah dibahas di edisi 4 mwmag—merupakan server RDBMS open source terpopuler, terutama untuk aplikasi Web. Artikel kali ini akan membahas isu-isu keamanan seputar menjalankan daemon MySQL di lingkungan multiuser, seperti di server shared hosting. Dalam lingkungan multiuser ada beberapa hal yang perlu diperhatikan, terutama yang berkaitan dengan isolasi user yang satu dengan user lain. Meskipun ada pula sebagian poin-poin yang diterangkan—seperti pengaturan permission MySQL—yang dapat diterapkan untuk Windows, namun jelas fokus artikel ini adalah pada lingkungan Unix/Linux.

Tip 1: Pasang mysqld Di Mesin Terpisah

Kita langsung mulai saja dengan tip yang pertama. Jika Anda punya budget cukup, pasanglah daemon MySQL (mysqld) di mesin terpisah. Cara ini adalah termasuk yang paling aman, karena mesin terpisah ini dapat kita taruh di belakang firewall sehingga tidak menerima koneksi dari Internet melainkan dari mesin-mesin tertentu saja di jaringan lokal. Juga, tiap user yang menggunakan database tidak perlu kita beri akses shell. Semua port dapat kita tutup kecuali port default mysqld saja (3306) yang kita buka. Tidak ada yang bisa menyentuh langsung file-file database maupun log kecuali user mysql di mesin database tersebut, dan juga admin mesin tersebut.

Tapi tentu tidak semua orang bisa membeli mesin dedicated. Jadi…

Tip 2: Jalankan mysqld Terpisah Di Mesin Yang Sama

Meskipun MySQL merupakan database multiuser, tapi daemon mysqld hanya berjalan sebagai satu user Unix saja (defaultnya adalah mysql). Tidak ada wrapping seperti halnya pada Apache + suexec/cgiwrap. User Unix ini memiliki akses ke semua database di direktori data (defaultnya adalah /var/lib/mysql). Tentu saja MySQL memiliki sistem access privilege yang cukup mendetil untuk mengatur klien mana yang dapat mengakses tabel atau database mana. Namun bug pada kode mysqld atau miskonfigurasi sistem privilegenya bisa saja mengakibatkan sebuah database berisi data rahasia terbuka bagi user MySQL lain. Misalnya lewat LOAD DATA INFILE atau LOAD LOCAL DATA INFILE maupun lewat SELECT INTO OUTFILE, yang memungkinkan penyerang berpotensi menimpa file database milik user lain.

Jika Anda mempunyai sebuah database yang datanya sensitif atau harus benar-benar private, Anda bisa dapat menjalankan mysqld terpisah. Tiap mysqld berjalan sebagai user Unix yang berbeda, sehingga satu mysqld tidak dapat mengganggu mysqld yang lainnya. Bug pada mysqld yang satu tidak dapat mengganggu mysqld lainnya, karena masing-masing tidak bisa menyentuh direktori data daemon lain.

Contoh berikut adalah user steven yang menjalankan sendiri daemon MySQL pribadi, berjalan sebagai user steven dan port 3366. Daemon MySQL utama, sebagai user mysql, tidak dapat menyentuh database.

$ mkdir /home/steven/mysql
$ mysql_install_db --datadir=/home/steven/mysql
$ /usr/sbin/mysqld --datadir=/home/steven/mysql --port=3366 \
--socket=/home/steven/mysql/mysql.sock &

Anda dapat mengganti port 3366 dengan port kesukaan sendiri. Sebelum memakai, Anda perlu memberi password pada user MySQL root tentunya:

$ mysqladmin -u root -S /home/steven/mysql/mysql.sock password RAHASIA

Ganti RAHASIA dengan password sebenarnya. Selanjutnya nanti sewaktu melakukan koneksi ke mysqld ini dengan command line client mysql misalnya, Anda perlu menyebutkan opsi -h (host) dan -P (port) atau -S (socket). Misalnya, koneksi via soket Unix:

$ mysql -u root -S /home/steven/mysql/mysql.sock

Atau koneksi via TCP:

$ mysql -u root -h 127.0.0.1 -P 3366

Tapi, lagi-lagi, jika jumlah user banyak dan masing-masing ingin menjalankan mysqld-nya masing-masing, tentu saja overheadnya besar. Karena itu…

Tip 3: Kenali Sistem Access Privilege MySQL

Sebagai seorang admin database, mau tidak mau hal yang satu ini harus dikuasai baik-baik. Sistem Access Privilege adalah cara utama MySQL dalam membatasi user dan host mana saja yang bisa melakukan koneksi, menyimpan password masing-masing user, dan membatasi kemampuan user dalam memanipulasi database (apakah hanya bisa melakukan SELECT saja, atau SELECT dan UPDATE, dst). Apa artinya daemon MySQL yang terisolasi dari mesin maupun daemon lain apabila ia dengan cerobohnya menerima koneksi dari sembarang user dan host?

Sistem privilege MySQL cukup mendetil dan menurut saya lebih simpel tapi mendetil daripada sistem privilege PostgreSQL, meskipun dari segi otentikasi sistem PostgreSQL sebetulnya lebih fleksibel. Pada dasarnya seluruh informasi pengaturan privilege disimpan dalam sebuah database khusus bernama mysql. Penjelasan mendetil mengenai di luar cakupan artikel ini, tapi sebagai tip beberapa hal berikut ini sebaiknya diikuti. Pertama, sebaiknya tiap user diberi password. Kedua, tabel user di database mysql ini sangat sensitif karena berisi password, jadi jangan biarkan user manapun memiliki privilege SELECT terhadap. Ketiga, batasi privilege FILE maupun PROCESS, jangan berikan kecuali benar-benar perlu. Keempat, jangan biarkan satu user database melihat database lain kecuali benar-benar perlu.

Dalam server shared hosting yang saya kelola, demi kesederhanaan, tiap user database hanya boleh mengakses satu database, dan daftar privilegenya seperti ini: Entri di tabel user: ('localhost', 'USER', password('PASSWORD'), 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N'). Entri di tabel db: ('localhost', 'DATABASE', 'USER', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'Y', 'Y', 'Y'). Artinya, si user USER hanya dapat melihat database DATABASE, hanya bisa melakukan koneksi dari mesin lokal melalui soket Unix, dan tidak memiliki privilege GRANT. Privilege FILE diberikan, tapi berhubung tiap skrip CGI diwrap, maka setidaknya akan lebih sulit bagi seorang penyerang untuk menciptakan file sebagai user Unix lain.

Tip 4: Hanya Terima Koneksi Lokal

Dalam sebuah server shared hosting misalnya, umumnya database diakses dari skrip CGI/PHP/ASP di server yang sama. Jika kita ingin agar hanya user atau skrip dari mesin lokal saja yang dapat melakukan koneksi ke daemon MySQL maka kita dapat mematikan opsi listening di soket TCP. Seperti kita ketahui, MySQL mendengarkan koneksi di soket Unix (defaultnya di /var/lib/mysql/mysql.sock) dan di port TCP (defaultnya di port 3306). Soket Unix hanya untuk klien lokal, sementara soket TCP dapat digunakan untuk koneksi jaringan.

Untuk membuat mysqld tidak mendengarkan di soket TCP sama sekali, taruh baris berikut di /etc/my.cnf:

[mysqld]
skip-networking

lalu restart mysqld. Atau tambahkan --skip-networking saat menjalankan mysqld.

Setelah memakai opsi ini, bahkan koneksi ke 127.0.0.1 pun akan ditolak dengan pesan Connection Refused. Karena memang tidak ada soket TCP yang mendengarkan. Jadi untuk melakukan koneksi ke daemon MySQL kita harus menggunakan host localhost agar soket Unixlah yang dipakai sebagai metode koneksi.

Keuntungan menggunakan soket Unix, selain sedikit lebih cepat (karena tidak ada overhead protokol TCP/IP), isolasi filesystem pun berlaku sebagai lapisan pelindung. Database milik user steven di contoh Tip 2 tadi misalnya, tidak dapat dicapai sama sekali baik oleh klien di mesin lain maupun oleh user Unix lain di mesin yang sama, karena defaultnya lokasi soket Unix di /home/steven/mysql/mysql.sock ini tertutup dari user lain. Sehingga database ini bersifat pribadi dan hanya bisa diakses oleh user steven sendiri maupun oleh skrip CGI/PHP yang berjalan sebagai user steven. Daemon MySQL utama, di lain pihak, merupakan daemon yang terbuka bagi semua user, karena /var/lib/mysql/mysql.sock secara default dapat diakses oleh semua user.

Tentu saja, kadang-kadang database MySQL ingin diakses selain dari localhost (misalnya, ingin diakses dari klien GUI di komputer Windows). Dalam kasus ini, mau tidak mau kita harus membuka lagi soket TCP dan kembali mengandalkan sistem privilege MySQL dalam membatasi host.

Tip 5: Buang Database test

Dalam lingkungan multiuser, tiap user semestinya diberi database masing-masing dan database default yang diberikan oleh distribusi MySQL, yaitu test, biasanya tidak ada gunanya. Ada baiknya database ini dihapus saja sehingga tidak sembarang user yang bisa mengakses database ini dan lalu memenuhi disk atau melakukan serangan DOS misalnya. Sebab defaultnya adalah semua user database bisa melihat database test. Untuk menghapus database test, berikan perintah ini dari klien command line mysql:

mysql> DELETE FROM db WHERE db='test' OR db='test_%';
mysql> FLUSH PRIVILEGES;

Tip 6: chroot

Chroot adalah fasilitas yang disediakan di banyak sistem operasi Unix untuk membuat filesystem root “virtual” bagi program, misalnya di bawah /home/steven/root, sehingga program menganggap path tersebut adalah / dan tidak bisa melihat path di atasnya (/home/steven maupun /home maupun / asli). Chroot banyak bermanfaat untuk memenjarakan program sehingga tidak bisa menyentuh file-file di luar direktori yang sudah kita tetapkan sebagai penjaranya.

Tip ini cukup berhubungan dengan tip 2 (tiap user memperoleh mysqld masing-masing). Di lingkungan virtual server pun chroot sering dilakukan terhadap tiap-tiap daemon agar masing-masing tidak dapat melihat file milik daemon lain.

Chroot saat ini harus dilakukan sebagai root karena merupakan operasi privileged. Menyambung contoh di tip 2, berikut ini cara menjalankan mysqld milik user steven dan memenjarakannya di /home/steven/mysql. mysqld memiliki opsi --chroot sehingga kita tidak perlu mengkopi /usr/sbin/mysqld-nya itu sendiri, namun terakhir saya coba masih bermasalah, sehingga kita akan melakukan chroot menggunakan /usr/sbin/chroot. Contoh ini untuk RedHat Linux (saya memakai versi 7.2) dan distribusi RPM binary dari MySQL AB (saya memakai 3.23.51).

$ chdir /home/steven
$ mkdir -p fakeroot/{var/lib,etc,tmp,lib,usr/sbin,usr/share}
$ mv mysql fakeroot/var/lib/ # direktori data yg sudah dibuat di tip 2
$ echo -e "root:*:0:0:::\nnobody:*:99:99:::\nsteven:*:500:500:::" \
> fakeroot/etc/passwd
$ echo -e "root::0:\nsteven::500:" > fakeroot/etc/group
$ cp -a /lib/{ld-*,libc.so*,libc-*.so,libnsl*,libnss_files*,libtermcap*} \
fakeroot/lib/
$ cp -a /usr/sbin/mysqld fakeroot/usr/sbin/
$ cp -a /usr/share/mysql fakeroot/usr/share/

# /usr/sbin/chroot /home/steven/fakeroot /usr/sbin/mysqld \
--user=steven \
--datadir=/var/lib/mysql \
--port=3366 \
--socket=/var/lib/mysql/mysql.sock

Untuk instalasi seperti di atas, dibutuhkan overhead ruang disk sebesar sekitar 12MB untuk mengkopi library Linux dan file-file lain yang diperlukan ke bawah /home/steven/fakeroot (karena jika tidak, setelah dilakukan chroot maka mysqld tidak akan dapat mencari library yang dibutuhkan). Kita juga memerlukan /etc/passwd dan /etc/group “dummy”. Ganti UID dan GID 500 sesuai UID dan GID user yang akan menjalankan mysqld. Setelah semua file-file siap, kita memanggil mysqld. Perhatikan bahwa opsi-opsi path di --datadir dan --socket adalah relatif terhadap fakeroot. Program-program yang berada di bawah chroot yang sama akan melihat soket dan direktori data MySQL milik steven ini di /var/lib/mysql, tapi sebetulnya—seperti yang akan dilihat program-program lain yang berada di luar chroot—lokasi asli ada di /home/steven/fakeroot/var/lib/mysql.

Chroot berguna terutama jika Anda tidak mempercayai MySQL versi development atau beta (mis: versi 4.0.2 saat ini). Chroot juga bermanfaat dalam menutup kemungkinan perintah SQL LOAD DATA INFILE dan SELECT INTO OUTFILE mengintip dan merusak file-file yang tidak diinginkan. Chroot juga perlu Anda pertimbangkan bila sebuah server memuat beberapa user yang benar-benar tidak saling mempercayai satu sama lain. Dalam kenyataan sehari-hari, saya jarang menggunakan chroot.

Tip 7: Jangan Hidupkan LOAD DATA LOCAL INFILE

LOAD DATA LOCAL INFILE adalah fasilitas agar klien bisa membaca sebuah file dari filesystem dan memasukkannya ke tabel database. Sejak 3.23.49, LOAD DATA LOCAL INFILE sudah dimatikan secara default. Jika ada skrip CGI atau ASP di mesin shared hosting Anda yang tidak diwrap, maka ada potensi perintah SQL tersebut dipakai untuk membaca file-file sebagai user nobody misalnya (dan mengambil source code skrip-skrip milik user lain lewat memuatnya ke tabel database lalu nanti ke file melalui SELECT INTO OUTFILE atau lewat skrip yang melakukan SELECT dan mencetaknya ke file). Jika Anda cukup yakin bahwa semua skrip terwrap, maka barulah Anda bisa mengaktifkan perintah SQL ini dengan memberikan opsi:

[mysql]
local-infile

di /etc/my.cnf. Skrip pun harus memberikan perintah mysql_options() dulu—saat ini belum didukung oleh PHP—sebelum bisa memberikan perintah SQL LOAD DATA LOCAL INFILE.

Tip 8: Koneksi SSL

Tip ini berkaitan dengan tip nomor 4. Jika koneksi MySQL Anda hanya lokal via soket Unix, Anda cukup aman di poin ini. Tapi jika daemon MySQL Anda harus menerima klien (atau melakukan replikasi) via koneksi TCP dari Internet, maka ada potensi pencurian data melalui sniffing paket. Password dan data-data sensitif bisa melayang dari hop ke hop dan siap disadap. Ada baiknya Anda mempertimbangkan mensetup SSL untuk koneksi MySQL.

MySQL baru mendukung koneksi SSL natif mulai 4.0. Karena itu, kita bisa menggunakan stunnel untuk mengenkripsi saluran komunikasi. Prinsip stunnel adalah, di sisi klien dan server kita memasang gerbang stunnel masuk agar terbentuk “terowongan”. Masing-masing klien dan server asli berkomunikasi secara lokal (via localhost) dengan stunnel. Sementara barulah stunnel di kedua sisi saling melakukan pembicaraan SSL. Berikut sebuah contoh. Di sisi klien (mis, beralamat di klien.com):

# /usr/sbin/stunnel -P/tmp/ -c -d 3306 -r server.com:3307

Di sisi server (yang beralamat mis di server.com):

# cd /usr/share/ssl/certs
# openssl req -new -x509 -days 365 -nodes -out stunnel.pem \
-keyout stunnel.pem
# /usr/sbin/stunnel -P/tmp/ -p stunnel.pem -d 3307 -r localhost:3306

Lalu dari klien.com lakukan koneksi MySQL ke server server.com seperti biasa.

Penutup

Pesan terakhir, jangan lupa membaca kembali manual MySQL bagian General Security Issues and the MySQL Access Privilege System agar—mengutip kata-kata di manual tersebut—Anda “terhindar dari kesalahan-kesalahan security yang paling umum.” (sh)