Berita
SQL Injection - Cara Mengeksploitasi dan Mencegahnya
Pada video ini kita akan membahas mengenai SQL Injection, salah satu celah keamanan yang paling sering diserang oleh hacker. Dengan memanfaatkan celah ini, maka peretas bisa masuk ke sistem menggunakan akun milik orang lain, bisa melihat data rahasia dan bahkan bisa melakukan manipulasi data.
Namun sebelum kita melanjutkan video ini, saya mesti disklaimer terlebih dahulu ya. Secara channel ini membahas mengenai pemograman, bukan hacking, maka video ini dibuat hanya untuk keperluan edukasi. Bagi teman-teman yang menonton video ini, jangan berharap setelah ini bisa langsung meretas situs orang lain. Sebaliknya, justru video ini dibuat agar programer bisa mengenali bagaimana cara mengeksploitasi celah sql injection pada situsnya, sehingga bisa segera menutupi celah tersebut sebelum dimanfaatkan oleh orang lain.
Oke, sebelum kita bisa mencegah SQL Injection, kita harus tau terlebih dahulu apakah itu SQL Injection? Bagaimana cara mengeksploitasinya? Untuk itu kita lihat sebuah contoh. Misalkan kita memiliki halaman login, dimana kita meminta user untuk memasukkan username dan password. Nah, halaman login ini biasanya menjadi target yang paling banyak diserang oleh SQL Injection. Secara kalau kita berhasil melakukan injection, maka kita akan berhasil masuk ke sistem dengan menggunakan akun milik orang lain.
Setelah user memasukkan username dan password, apa yang dilakukan oleh sistem? Biasanya, sistem akan mengakses database, yang mengandung daftar username dan password dari seluruh user yang sudah terdaftar pada situs tersebut. Apabila ada data username dan password yang sesuai dengan yang dimasukkan oleh user, maka user berhasil masuk ke dalam sistem. Namun apabila tidak ada username dan password yang sesuai, maka user tidak berhasil masuk.
Sistem yang menerima username dan password yang diketikkan oleh user adalah bagian back end. Back end yang kita gunakan pada video ini secara spesifik adalah PHP. Sedangkan database yang kita gunakan disini adalah MySQL. Pada saat hendak memeriksa data, PHP harus berkomunikasi ke MySQL dengan menggunakan bahasa SQL. Bahasa SQL-nya kurang lebih berbentuk seperti dibawah ini:
select id, username, password from user where username='admin' and password='admin123'
Oke. Sekarang kita lebih fokus ke bahasa SQL-nya. SQL kita bagi menjadi 2 warna ya. Yang berwarna kuning adalah bahasa query-nya. Sedangkan yang berwarna putih adalah parameternya. Nilai parameter diambil dari nilai yang dimasukkan oleh user pada form, yaitu admin dan admin123. Perhatikan bahwa parameter disini berbentuk string. String harus didahului oleh tanda petik sebagai awal, dan diakhiri oleh tanda petik lagi sebagai tanda akhir dari string.
Perhatikan kalau misalkan kita memasukkan nilai lain ke dalam form. Kali ini kita masukkan username budi dengan password coba123. Maka perintah SQL-nya berubah. Namun yang berubah hanya nilai parameter yang berwarna putih ya. Sedangkan perintah query-nya tetap sama persis.
Nah, disinilah muncul seni untuk meretas perintah SQL. Bagaimana caranya kita bisa mengubah nilai parameter, yang bisa sekalian merubah perintah query secara keseluruhan? Nah, disini kita coba tambahkan setelah budi, tanda petik diikuti oleh kata or, angka satu, tanda sama dengan, tanda petik dan angka 1. Sekilas, semua tanda tambahan yang kita kirim akan menjadi parameter dari username.
Namun kalau kita perhatikan lagi, ternyata parameter merubah perintah sql-nya. Tanda petik yang pertama mengakhiri parameter username, sehingga nilai parameternya menjadi budi. Sedangkan semua kode dibelakangnya menjadi kondisi baru or dengan nilai 1 sama dengan string 1. Query berubah menjadi memiliki 2 buah kondisi yaitu and dan or. Semua kondisi and dijalankan terlebih dahulu. Artinya perintah username='budi' dan password='coba123' dijalankan terlebih dahulu. Setelah itu hasilnya baru dibandingkan dengan operator or 1 = '1'.
Mengapa harus menggunakan or 1='1'? Kita semua sudah tau ya, kalau 1 dibandingkan dengan 1 akan selalu menghasilkan nilai benar atau true. Dan yang lebih hebatnya lagi, nilai apapun yang kita kondisikan dengan or true, akan menghasilkan true. Jadi sebagai hasilnya, tidak peduli nilai apapun yang kita masukkan ke dalam password, kita tetap berhasil masuk sebagai budi. Inilah celah keamanan yang dikenal dengan nama SQL Injection.
Agar lebih jelas, kita langsung praktek ya.
Misalkan disini kita memiliki form login dimana kita diminta untuk mengisi username dan password untuk masuk ke dalam sistem. Kita coba login ya. Biasanya yang paling dulu dicoba orang adalah user admin dan password admin. Makanya untuk database yang production, disarankan jangan menggunakan password lemah untuk admin. Kita coba submit. Dan kita menerima pesan bahwa login gagal.
Berhubung halaman login disini hanya bertujuan untuk edukasi saja, maka ada beberapa hal disini yang sengaja saya ubah menjadi gampang untuk di-hack ya. Yang pertama bagian password yang seharusnya tidak memperlihatkan isi password-nya, memang sengaja menggunakan input biasa. Kemudian perintah SQL untuk memeriksa username dan password juga secara sengaja ditampilkan di bagian paling bawah form.
Pertama kita copy paste dahulu perintah sql-nya. Lalu kita pindah ke phpMyAdmin. Disini kita bisa melihat ada table user ya. Isinya ada 3 akun, yaitu admin, budi dan andi. Dan kita bisa melihat password dari masing-masing akun. Field password yang asli biasanya selalu menggunakan enkripsi. Berhubung disini hanya contoh, jadi saya menggunakan teks biasa agar lebih mudah untuk dimengerti.
Perintah SQL yang dikirimkan oleh form barusan, bertujuan untuk mencari akun dengan username admin dan password admin. Kalau kita cek pada tabel, akun tersebut tidak ada ya. Memang ada akun dengan username 'admin', namun password-nya tidak cocok sehingga data tidak ditemukan. Kalau ingin mendapatkan akun admin, maka kita harus memasukkan password 'admin123'.
Kita bisa buka tab SQL untuk mencoba perintah SQL-nya. Kalau kita paste dan kita tekan go, maka tidak mendapatkan hasil ya. Kalau kita ingin mendapatkan hasil, maka kita harus mengubah perintah SQL-nya menjadi password='admin123'. Disini barulah kita mendapatkan akun admin.
Pada form, kalau kita ingin berhasil masuk sebagai admin, maka kita harus mengubah password menjadi admin123. Setelah kita submit, barulah kita berhasil masuk sebagai admin. Perhatikan perintah SQL-nya berubah menjadi password='admin123'.
Kita coba login menggunakan akun kedua ya. Yaitu budi. Kita coba password-nya budi123. Ternyata login-nya gagal. Namun perhatikan perintah SQL-nya berubah ya. Parameternya berubah menjadi username='budi' dan password='budi123'. Artinya apa pun yang kita ketik pada form, akan menjadi parameter yang digunakan pada perintah sql.
Kalau kita ingin berhasil login sebagai budi, maka kita harus menggunakan password yang sesuai dengan tabel tadi, yaitu 'pass123'. Sekarang keterangannya adalah kita berhasil masuk sebagai budi.
Namun perhatikan lagi bahwa selama ini kita berhasil masuk disebabkan karena kita tau username dan password dari masing-masing akun. Pertanyaan berikutnya, bagaimana kalau kita hanya tau bahwa username-nya adalah admin. Namun kita tidak tau password-nya. Bagaimana caranya agar kita tetap bisa masuk sebagai admin?
Kita coba dulu ya. Misalkan kita ketik username adalah admin sedangkan password-nya a. Sebagai hasilnya login gagal.
Nah, yang pertama kali dilakukan adalah kita tes dahulu apakah form ini bersifat vurnerable terhadap sql injection? Caranya adalah kita masukkan tanda petik ke dalam salah satu input. Sebagai contoh username-nya kita ubah menjadi admin's. Kita submit. Ternyata hasilnya adalah Query Error. Artinya form ini bersifat vurnerable terhadap sql injection.
Kita cek ke perintah sql-nya. Ternyata tanda petik yang kita kirim pada form dianggap sebagai tanda penutup parameter username. Sehingga nilai username-nya adalah 'admin', sedangkan dibelakangnya masih ada huruf s dan tanda petik menyebabkan sintaks SQL menjadi error.
Yang perlu diperhatikan disini adalah string pada sql bisa menggunakan tanda petik satu dan tanda petik dua. Ada programmer tertentu yang menggunakan tanda petik dua pada string-nya. Sebagai contoh, kita buka source code PHP-nya ya. Kita ubah perintah SQL-nya. Misalkan untuk username kita gunakan tanda petik dua pada string-nya. Kita simpan dan kita coba submit lagi ya.
Perhatikan disini kita mendapatkan hasil login gagal. Disini tanda petik satu tidak menyebabkan error. Kalau kita perhatikan perintah SQL-nya, disini username admin's dapat terbungkus dengan baik sebagai satu buah string. Namun kalau kita ubah tanda petik satu pada form menjadi tanda petik dua, tetap terjadi query error ya. Artinya form ini tetap vurneable terhadap SQL Injection. Yang berbeda hanya tanda petiknya saja ya. Kalau tadinya mau melakukan inject harus menggunakan tanda petik tunggal, sekarang menjadi tanda petik ganda.
Jadi kalau kita hendak melakukan tes vurneability terhadap situs luar, maka kita tes kedua simbol tanda petik tunggal dan ganda. Karena kita tidak tau programmernya menggunakan tanda petik tunggal atau ganda untuk membungkus string-nya. Sekarang kita kembalikan dahulu menjadi tanda petik tunggal ya.
Kita kembali ke permasalahan awal. Misalkan kita hanya tau username admin dan tidak tau password. Bagaimana caranya agar kita tetap berhasil masuk ke dalam sistem sebagai admin? Kita lihat lagi ke perintah SQL-nya ya. Kita mau agar perintah SQL hanya berjalan hingga ke username='admin' dan mengabaikan semua perintah di belakangnya. Kita bisa melakukan hal ini dengan cara memasukkan tanda -- dan spasi setelah tanda petik. Kita coba ya. Dan ternyata kita berhasil masuk ke sistem sebagai admin.
Mengapa bisa berhasil masuk? Karena tanda -- pada mysql berarti komentar. Jadi semua perintah yang berada dibelakang tanda -- sudah tidak dijalankan, selagi perintah tersebut masih berada di baris yang sama.
Agar lebih jelas kita copy paste perintah SQL-nya ke phpMyAdmin ya. Perhatikan bahwa mulai dari tanda -- dan kebelakangnya, tulisnya berubah menjadi berwarna merah. Artinya kode mulai dari -- ke belakangnya sudah tidak dieksekusi ya. MySQL sudah tidak peduli dengan apapun tulisannya walaupun masih terdapat sintaks error. MySQL hanya mencari user dengan username='admin' saja. Kalau kita jalankan maka kita mendapatkan user admin. Oleh karena itu login berhasil.
Oke, kita sudah berhasil masuk sebagai admin ya. Sekarang kasusnya kita persulit lagi. Kita berhasil masuk karena kita tau ada username admin. Bagaimana kalau misalkan kita tidak tau username-nya? Apakah kita bisa masuk menggunakan akun milik orang lain?
Jadi sekarang kita mesti merubah perintah SQL-nya, agar kita harus tetap berhasil masuk tanpa ada username. Bagaimana caranya? Nah, pada bagian username, kita langsung beri tanda petik penutup. Lalu kita tambahkan kondisi or 1=1 dan tanda -- spasi. Kita jalankan. Dan sekarang kita berhasil masuk sebagai admin.
Bagaimana cara kerja kodenya sehingga kita berhasil masuk? Perhatikan ke perintah SQL-nya. Perintah pada bagian where adalah username='' or 1=1. Perhatikan bahwa 1=1 selalu menghasilkan true. Sedangkan nilai apapun yang kita gabungkan dengan or true akan menghasilkan true. Artinya MySQL tidak peduli nilai pada username ini benar atau salah, MySQL selalu mengembalikan user. Kebetulan pada kode disini, script selalu menggunakan user pertama, jadi kita berhasil login sebagai admin.
Agar lebih jelas, kita coba copy paste perintah sql-nya ke phpMyAdmin. Ternyata perintah SQL disini berhasil dijalankan dan mengembalikan semua user. Script menggunakan user yang pertama, yaitu admin.
Kebetulan kode untuk login disini tidak melakukan pemeriksaan terhadap jumlah data yang dikembalikan oleh MySQL. Jadi walaupun MySQL mengembalikan banyak data, script tetap berhasil login dengan menggunakan data yang pertama.
Terkadang ada programmer yang melakukan validasi terhadap jumlah data yang dikembalikan oleh database, dan jumlah datanya harus 1 barulah boleh berhasil login. Nah, untuk sistem seperti ini, bagaimana cara mengakalinya agar database hanya mengembalikan satu data? Kita bisa melakukannya dengan cara menambahkan perintah SQL limit 1 sebelum komentar. Maka username kita ubah menjadi ' or 1=1 limit 1--
Kita copy paste perintah sql-nya ke phpMyAdmin ya. Kita bisa lihat bahwa dengan perintah ini maka MySQL hanya mengembalikan satu data pertama yaitu admin. Bagaimana kalau kita ingin login sebagai data kedua? Kita tinggal ganti perintah limitnya menjadi limit 1,1. Kita coba dari phpMyAdmin dulu ya. Maka kita mendapatkan data user kedua yaitu Budi. Kalau kita ingin mendapatkan data user ketiga, maka kita gunakan limit 2,1.
Kalau kita kembali ke form. Kita tinggal gunakan limit 1,1 untuk login sebagai user kedua yaitu Budi. Dan user ketiga dengan menggunakan limit 2,1. Dan begitu seterusnya. Jadi kita bisa masuk menggunakan semua user yang ada, tanpa perlu mengetahui usernamenya.
Oke, kita melihat bagaimana cara melakukan SQL Injection. Pada video ini kelihatannya gampang ya, karena kita mengetahui secara pasti sistem yang sedang digunakan, terutama pada bagian perintah sql-nya. Semakin banyak yang kita ketahui mengenai sistem, maka semakin besar pula peluang kita untuk berhasil melakukan injeksi. Hal ini akan berbeda kalau kita hendak melakukan injeksi ke sistem buatan orang lain, karena kita tidak tau bagaimana cara programmer mengembangkan sistemnya.
Kita sudah belajar mengenai cara melakukan SQL Injection. Selanjutnya kita akan mempelajari apa saja yang bisa kita lakukan untuk mencegah agar situs kita aman dari SQL Injection.
Cara pertama adalah jangan menampilkan informasi sensitif ke layar user. Dari video barusan kita sudah tau ya, bahwa semakin banyak yang kita ketahui mengenai sistem yang sedang digunakan, maka semakin besar pula peluang kita untuk melakukan SQL Injection. Oleh karena itu kita harus mencegah agar user tidak mendapatkan informasi-informasi sensitif mengenai sistem kita. Untuk itu ada beberapa hal yang bisa kita cegah.
Ada fungsi bawaan PHP yang bernama phpinfo, yang dapat menampilkan semua informasi yang berhubungan dengan PHP yang ter-install pada sistem kita. Termasuk informasi seperti versi, modul-modul, direktori maupun konfigurasinya. Ingat bahwa informasi seperti ini tidak boleh ditampilkan pada server production.
Kemudian ada konfigurasi pada php.ini yang bernama display_errors. Pada server production, display_errors harus diset menjadi off. Semua error tidak boleh ditampilkan di layar. Apabila kita memerlukan pesan error, maka kita bisa mengatur agar php menuliskannya ke dalam file.
Ada beberapa programmer yang suka menampilkan perintah sql di layarnya apabila terjadi error. Hal seperti ini mempermudah programmer agar bisa langsung memeriksa apakah terjadi kesalahan typo atau kesalahan lainnya pada sintaks sql-nya. Nah, pada server production, pastikan hal ini tidak terjadi. Karena perintah sql inilah yang membantu peretas dalam menyusun strategi untuk SQL Injection.
Cara kedua adalah dengan melakukan sanitize terhadap nilai yang berikan oleh user. Disini kita bisa mencegah penggunakan simbol-simbol yang berhubungan dengan pemograman seperti tanda petik tunggal, tanda petik ganda, backtick, titik koma, minus, bintang, lebih besar, lebih kecil, tanda dan dan tanda pipe. Sayangnya cara ini justru bertentangan dengan aturan pembuatan password yang aman. Password justru disarankan untuk menggunakan simbol agar lebih susah untuk ditebak.
Cara ketiga adalah menggunakan escape string. Pada PHP sudah ada method bawaan dari mysqli, yaitu real_escape_string. Method ini dapat mencegah user untuk menambahkan tanda penutup string secara ilegal. Agar lebih jelas, kita coba praktekkan.
Kita buka kembali source code dari form kita. Disini ada variable $username dan $password yang diambil dari nilai yang di-post oleh user. Perhatikan bahwa nilai ini digunakan secara langsung sebagai parameter pada perintah SQL. Hal seperti inilah yang menyebabkan situs menjadi bisa di-inject.
Agar lebih aman, kedua nilai post ini harus diproses terlebih dahulu. Misalkan kita buat variable baru $u untuk memanggil method $mysqli->real_escape_string($username). Lakukan hal yang sama dengan variable $p untuk memproses $password. Lalu kita ubah perintah SQL agar menggunakan variable $u dan $p.
Sekarang kita coba inject ya. Misalkan kita masukkan admin'-- . Password a. Tidak berhasil di-inject ya. Perhatikan pada perintah SQL-nya. Pada tanda petik yang kita masukkan, sudah ditambahkan escape string atau tanda backslash. Artinya tanda petik berubah menjadi bagian dari string, bukan lagi menjadi tanda penutup string.
Kita lihat lagi source code. Cara escape string ini hanya bisa berfungsi dengan baik kalau kita ingat untuk memberikan tanda kutip pada perintah sql-nya. Dan kita juga harus ingat untuk memanggil real_escape_string terlebih dahulu sebelum menggunakan parameter di dalam sql. Walaupun cara ini sudah aman, namun kabarnya masih bisa di-inject. Apabila teman-teman ingin teknik yang lebih aman, lebih baik gunakan cara keempat yaitu Prepared Statement.
Cara keempat adalah cara yang paling disarankan untuk mencegah SQL Injection, yaitu menggunakan Prepared Statement. Cara ini adalah yang paling aman karena kita memisahkan antara perintah query dengan parameternya. Semua parameter diganti menjadi simbol tanda tanya. Kemudian nilai dari parameternya dimasukkan dengan menggunakan perintah bind. Jadi disini nilai dari parameter tidak bisa di-inject untuk mengubah perintah query. Agar lebih jelas, kita langsung praktek ya.
Kita buka lagi source code sebelumnya. Untuk mengubah perintah query menjadi Prepared Statement, kita mesti mengganti semua nilai parameternya menjadi tanda tanya. Jadi disini kita sudah tidak menggunakan variable seperti $u dan $p. Kita juga tidak perlu menyediakan tanda kutip untuk parameternya. Perintah SQL-nya menjadi sederhana, yaitu where username=? and password=?. Kemudian kita juga bisa menghapus perintah real_escape_string.
Namun perintah SQL ini tidak bisa langsung kita jalankan. Kita harus membuat object Prepared Statement-nya terlebih dahulu. Caranya? Pertama kita buat dahulu variable $statement untuk menampung hasil dari method $mysqli->prepare($sql). Argument-nya adalah perintah SQL kita.
Setelah itu, kita harus memanggil method $statement->bind_param untuk memasukkan nilai pada parameter kita. Nilai dari argument pertama adalah tipe datanya, disini tersedia 4 pilihan tipe. Huruf 'i' untuk integer atau bilangan bulat, huruf 'd' untuk bilangan desimal, huruf 's' untuk string dan huruf 'b' untuk blob. Berhubung parameter kita ada 2 dan keduanya bertipe data string, maka argument pertama kita adalah 'ss'. Kemudian untuk argument kedua dan seterusnya kita bisa masukkan nilai dari parameter, yaitu $username dan $password.
Kemudian kita mengeksekusi perintah SQL melalui objek Prepared Statement. Langkah berikutnya untuk mengambil hasilnya juga melalui objek Prepared Statement.
$statement->execute(); $result = $statement->get_result();
Kita simpan dan coba jalankan. Pertama kita coba dahulu untuk masuk menggunakan username admin dan password a. Login gagal ya. Perhatikan perintah SQL-nya hanya menggunakan tanda tanya untuk parameter username dan password.
Kita coba perintah injek. Username kita ganti menjadi admin'-- . Login tetap gagal ya. Tidak terjadi error pada query. Kita gagal melakukan injeksi. Dan perhatikan bahwa perintah SQL tetap sama, walaupun kita mengganti isi dari form. Jadi cara ini aman dari SQL Injection karena kita melakukan pemisahan yang jelas antara perintah SQL dengan nilai parameternya. Nilai parameter tidak bisa diinjek untuk merubah perintah SQL.
Dengan menggunakan fasilitas tanya jawab, maka Anda bisa bertanya dan akan dijawab langsung oleh instruktur kursus.
Anda belum terdaftar pada kursus ini sehingga tidak bisa mengajukan pertanyaan.