Ringkasan
Dalam Relational Database Management System (RDBMS), ada bahasa tertentu yang disebut SQL (Structured Query language) yang digunakan untuk berkomunikasi dengan database. Pernyataan kueri yang ditulis dalam SQL digunakan untuk memanipulasi konten dan struktur database. Pernyataan SQL tertentu yang membuat dan memodifikasi struktur database disebut pernyataan DDL (Data Definition Language) dan pernyataan yang memanipulasi konten database disebut pernyataan DML (Data Manipulation Language). Mesin yang terkait dengan paket RDBMS mem-parsing dan menafsirkan pernyataan SQL dan mengembalikan hasilnya sesuai dengan itu. Ini adalah proses komunikasi yang khas dengan RDBMS — jalankan pernyataan SQL dan dapatkan kembali hasilnya, itu saja. Sistem tidak menilai maksud dari pernyataan apa pun yang mematuhi sintaks dan struktur semantik bahasa. Ini juga berarti bahwa tidak ada proses otentikasi atau validasi untuk memeriksa siapa yang mengeluarkan pernyataan dan hak istimewa yang dimiliki untuk mendapatkan output. Seorang penyerang dapat dengan mudah menembakkan pernyataan SQL dengan niat jahat dan mendapatkan kembali informasi yang seharusnya tidak didapatnya. Misalnya, penyerang dapat mengeksekusi pernyataan SQL dengan muatan berbahaya dengan kueri yang tampak tidak berbahaya untuk mengontrol server database aplikasi Web.
Cara Kerjanya
Seorang penyerang dapat memanfaatkan kerentanan ini dan menggunakannya untuk keuntungannya sendiri. Misalnya, seseorang dapat melewati mekanisme otentikasi dan otorisasi aplikasi dan mengambil apa yang disebut konten aman dari seluruh database. Injeksi SQL dapat digunakan untuk membuat, memperbarui, dan menghapus catatan dari database. Oleh karena itu, seseorang dapat merumuskan kueri yang terbatas pada imajinasinya sendiri dengan SQL.
Biasanya, aplikasi sering kali menjalankan kueri SQL ke database untuk berbagai tujuan, baik itu untuk mengambil catatan tertentu, membuat laporan, mengautentikasi pengguna, transaksi CRUD, dan sebagainya. Penyerang hanya perlu menemukan kueri input SQL dalam beberapa formulir input aplikasi. Kueri yang disiapkan oleh formulir kemudian dapat digunakan untuk mengikat konten berbahaya sehingga, saat aplikasi menjalankan kueri, aplikasi tersebut juga membawa muatan yang disuntikkan.
Salah satu situasi yang ideal adalah ketika aplikasi meminta pengguna untuk memasukkan seperti nama pengguna atau id pengguna. Aplikasi membuka tempat yang rentan di sana. Pernyataan SQL dapat dijalankan tanpa disadari. Penyerang mengambil keuntungan dengan menyuntikkan muatan yang akan digunakan sebagai bagian dari kueri SQL dan diproses oleh database. Misalnya, kode semu sisi server untuk operasi POST untuk formulir login dapat berupa:
uname = getRequestString("username"); pass = getRequestString("passwd"); stmtSQL = "SELECT * FROM users WHERE user_name = '" + uname + "' AND passwd = '" + pass + "'"; database.execute(stmtSQL);
Kode sebelumnya rentan terhadap serangan injeksi SQL karena input yang diberikan ke pernyataan SQL melalui variabel 'uname' dan 'pass' dapat dimanipulasi dengan cara yang akan mengubah semantik pernyataan.
Misalnya, kita dapat memodifikasi kueri untuk dijalankan terhadap server database, seperti di MySQL.
stmtSQL = "SELECT * FROM users WHERE user_name = '" + uname + "' AND passwd = '" + pass + "' OR 1=1";
Ini menghasilkan modifikasi pernyataan SQL asli ke tingkat yang memungkinkan seseorang untuk melewati otentikasi. Ini adalah kerentanan serius dan harus dicegah dari dalam kode.
Pertahanan Terhadap Serangan Injeksi SQL
Salah satu cara untuk mengurangi kemungkinan serangan injeksi SQL adalah memastikan bahwa string teks yang tidak difilter tidak boleh ditambahkan ke pernyataan SQL sebelum dieksekusi. Misalnya, kita dapat menggunakan PreparedStatement untuk melakukan tugas-tugas database yang diperlukan. Aspek menarik dari PreparedStatement adalah bahwa ia mengirimkan pernyataan SQL yang telah dikompilasi sebelumnya ke database, bukan string. Ini berarti bahwa kueri dan data secara terpisah dikirim ke database. Ini mencegah akar penyebab serangan injeksi SQL, karena dalam injeksi SQL, idenya adalah untuk mencampur kode dan data di mana data sebenarnya merupakan bagian dari kode dalam kedok data. Dalam PreparedStatement , ada beberapa setXYZ() metode, seperti setString() . Metode ini digunakan untuk menyaring karakter khusus seperti kutipan yang terdapat dalam pernyataan SQL.
Misalnya, kita dapat mengeksekusi pernyataan SQL dengan cara berikut.
String sql = "SELECT * FROM employees WHERE emp_no = "+eno;
Alih-alih menempatkan, katakanlah, eno=10125 sebagai nomor karyawan di input, kita dapat memodifikasi query dengan input seperti:
eno = 10125 OR 1=1
Ini sepenuhnya mengubah hasil yang dikembalikan oleh kueri.
Contoh
Dalam kode contoh berikut, kami telah menunjukkan bagaimana PreparedStatement dapat digunakan untuk melakukan tugas database.
package org.mano.example; import java.sql.*; import java.time.LocalDate; public class App { static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver"; static final String DB_URL = "jdbc:mysql://localhost:3306/employees"; static final String USER = "root"; static final String PASS = "secret"; public static void main( String[] args ) { String selectQuery = "SELECT * FROM employees WHERE emp_no = ?"; String insertQuery = "INSERT INTO employees VALUES (?,?,?,?,?,?)"; String deleteQuery = "DELETE FROM employees WHERE emp_no = ?"; Connection connection = null; try { Class.forName(JDBC_DRIVER); connection = DriverManager.getConnection (DB_URL, USER, PASS); }catch(Exception ex) { ex.printStackTrace(); } try(PreparedStatement pstmt = connection.prepareStatement(insertQuery);){ pstmt.setInt(1,99); pstmt.setDate(2, Date.valueOf (LocalDate.of(1975,12,11))); pstmt.setString(3,"ABC"); pstmt.setString(4,"XYZ"); pstmt.setString(5,"M"); pstmt.setDate(6,Date.valueOf(LocalDate.of(2011,1,1))); pstmt.executeUpdate(); System.out.println("Record inserted successfully."); }catch(SQLException ex){ ex.printStackTrace(); } try(PreparedStatement pstmt = connection.prepareStatement(selectQuery);){ pstmt.setInt(1,99); ResultSet rs = pstmt.executeQuery(); while(rs.next()){ System.out.println(rs.getString(3)+ " "+rs.getString(4)); } }catch(Exception ex){ ex.printStackTrace(); } try(PreparedStatement pstmt = connection.prepareStatement(deleteQuery);){ pstmt.setInt(1,99); pstmt.executeUpdate(); System.out.println("Record deleted successfully."); }catch(SQLException ex){ ex.printStackTrace(); } try{ connection.close(); }catch(Exception ex){ ex.printStackTrace(); } } }
Sekilas tentang PreparedStatement
Pekerjaan ini juga dapat diselesaikan dengan Pernyataan JDBC antarmuka, tetapi masalahnya adalah kadang-kadang bisa sangat tidak aman, terutama ketika pernyataan SQL dinamis dijalankan untuk menanyakan database di mana nilai input pengguna digabungkan dengan kueri SQL. Ini bisa menjadi situasi yang berbahaya, seperti yang telah kita lihat. Dalam kebanyakan keadaan biasa, Pernyataan cukup tidak berbahaya, tapi PreparedStatement tampaknya menjadi pilihan yang lebih baik di antara keduanya. Ini mencegah string jahat digabungkan karena pendekatannya yang berbeda dalam mengirimkan pernyataan ke database. Pernyataan yang Disiapkan menggunakan substitusi variabel daripada penggabungan. Menempatkan tanda tanya (?) dalam kueri SQL menandakan bahwa variabel pengganti akan menggantikannya dan memberikan nilai saat kueri dijalankan. Posisi variabel substitusi mengambil tempatnya sesuai dengan posisi indeks parameter yang ditetapkan di setXYZ() metode.
Teknik ini mencegahnya dari serangan injeksi SQL.
Selanjutnya, PreparedStatement mengimplementasikan AutoCloseable. Ini memungkinkannya untuk menulis dalam konteks coba-dengan-sumber daya memblokir dan menutup secara otomatis saat berada di luar jangkauan.
Kesimpulan
Serangan injeksi SQL hanya dapat dicegah dengan menulis kode secara bertanggung jawab. Faktanya, dalam keamanan solusi perangkat lunak apa pun sebagian besar dilanggar karena praktik pengkodean yang buruk. Di sini, kami telah menjelaskan apa yang harus dihindari dan bagaimana PreparedStatement dapat membantu kami dalam menulis kode aman. Untuk ide lengkap tentang injeksi SQL, lihat materi yang sesuai; Internet penuh dengan mereka, dan, untuk PreparedStatement , lihat Dokumentasi Java API untuk penjelasan lebih detail.