Tips untuk mencegah injeksi SQL dalam PHP

Tips untuk mencegah injeksi SQL dalam PHP

iis

July 30, 2019

Jika input pengguna dimasukkan tanpa modifikasi ke dalam query SQL, maka aplikasi menjadi rentan terhadap injeksi SQL, seperti dalam contoh berikut:

$unsafe_variable = $_POST['user_input'];

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

Itu karena pengguna dapat memasukkan sesuatu seperti nilai ‘); Tabel DROP TABLE; -, dan kueri menjadi:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

Apa yang bisa dilakukan untuk mencegah hal ini terjadi?

Gunakan pernyataan yang disiapkan dan pertanyaan parameter. Ini adalah pernyataan SQL yang dikirim ke dan diurai oleh server database secara terpisah dari parameter apa pun. Dengan cara ini tidak mungkin bagi penyerang untuk menyuntikkan SQL jahat.

sqli

Anda pada dasarnya memiliki dua opsi untuk mencapai ini:

Menggunakan PDO (untuk driver basis data yang didukung):

$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');

$stmt->execute(array('name' => $name));

foreach ($stmt as $row) {
// Do something with $row
}

Menggunakan MySQLi (untuk MySQL):

$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
$stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'

$stmt->execute();

$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
// Do something with $row
}

Jika Anda terhubung ke database selain MySQL, ada opsi kedua khusus driver yang dapat Anda lihat (misalnya, pg_prepare () dan pg_execute () untuk PostgreSQL). PDO adalah opsi universal.
Menyiapkan koneksi dengan benar

Perhatikan bahwa ketika menggunakan PDO untuk mengakses database MySQL, pernyataan yang disiapkan sebenarnya tidak digunakan secara default. Untuk memperbaiki ini, Anda harus menonaktifkan emulasi pernyataan yang disiapkan. Contoh membuat koneksi menggunakan PDO adalah:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Dalam contoh di atas mode kesalahan tidak sepenuhnya diperlukan, tetapi disarankan untuk menambahkannya. Dengan cara ini skrip tidak akan berhenti dengan Kesalahan Fatal ketika terjadi kesalahan. Dan itu memberi pengembang kesempatan untuk menangkap kesalahan yang dilemparkan sebagai PDOExceptions.

Apa yang wajib, bagaimanapun, adalah baris setAttribute () pertama, yang memberi tahu PDO untuk menonaktifkan pernyataan yang telah disiapkan dan menggunakan pernyataan yang sudah disiapkan. Ini memastikan pernyataan dan nilai-nilai tidak diuraikan oleh PHP sebelum mengirimnya ke server MySQL (memberikan kemungkinan penyerang tidak ada kesempatan untuk menyuntikkan SQL berbahaya).

Meskipun Anda dapat mengatur charset di dalam opsi konstruktor, penting untuk dicatat bahwa versi PHP ‘lama’ (sebelum 5.3.6) secara diam-diam mengabaikan parameter charset di DSN.
Penjelasan

Pernyataan SQL yang Anda lewati untuk dipersiapkan diuraikan dan dikompilasi oleh server database. Dengan menentukan parameter (baik parameter? Atau parameter bernama seperti: nama pada contoh di atas) Anda memberi tahu mesin basis data tempat Anda ingin memfilter. Kemudian ketika Anda memanggil eksekusi, pernyataan yang disiapkan dikombinasikan dengan nilai parameter yang Anda tentukan.

Yang penting di sini adalah bahwa nilai parameter dikombinasikan dengan pernyataan yang dikompilasi, bukan string SQL. Injeksi SQL bekerja dengan mengelabui skrip ke dalam memasukkan string berbahaya ketika itu menciptakan SQL untuk dikirim ke database. Jadi dengan mengirimkan SQL aktual secara terpisah dari parameter, Anda membatasi risiko berakhir dengan sesuatu yang tidak Anda inginkan.

Setiap parameter yang Anda kirim saat menggunakan pernyataan yang disiapkan hanya akan diperlakukan sebagai string (meskipun mesin database dapat melakukan beberapa optimasi sehingga parameter mungkin berakhir sebagai angka juga, tentu saja). Dalam contoh di atas, jika variabel $ name berisi ‘Sarah’; HAPUS DARI karyawan, hasilnya hanya akan menjadi pencarian untuk string “‘Sarah’; HAPUS DARI karyawan”, dan Anda tidak akan berakhir dengan tabel kosong.

Manfaat lain dari menggunakan pernyataan yang disiapkan adalah bahwa jika Anda mengeksekusi pernyataan yang sama berkali-kali dalam sesi yang sama hanya akan diurai dan dikompilasi satu kali, memberi Anda beberapa keuntungan kecepatan.

Oh, dan karena Anda bertanya tentang cara melakukannya untuk menyisipkan, berikut ini sebuah contoh (menggunakan PDO):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute(array('column' => $unsafeValue));

Dapatkah pernyataan yang disiapkan digunakan untuk permintaan dinamis?

Meskipun Anda masih dapat menggunakan pernyataan yang disiapkan untuk parameter kueri, struktur kueri dinamis itu sendiri tidak dapat ditentukan dan fitur kueri tertentu tidak dapat ditentukan.

Untuk skenario khusus ini, hal terbaik untuk dilakukan adalah menggunakan filter daftar putih yang membatasi nilai yang mungkin.

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
$dir = 'ASC';
}