Bagaimana saya bisa mencegah injeksi SQL dalam PHP?


2773

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 value'); DROP TABLE table;--, dan kueri menjadi:

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

Apa yang bisa dilakukan untuk mencegah hal ini terjadi?

Jawaban:


8960

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.

Anda pada dasarnya memiliki dua opsi untuk mencapai ini:

  1. Menggunakan PDO (untuk driver basis data yang didukung):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute([ 'name' => $name ]);
    
    foreach ($stmt as $row) {
        // Do something with $row
    }
  2. 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 PDOuntuk mengakses database MySQL, pernyataan yang disiapkan secara nyata 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 Fatal Errorketika ada masalah. Dan itu memberi pengembang kesempatan untuk catchsetiap kesalahan (s) yang thrown sebagai PDOExceptions.

Apa yang wajib , bagaimanapun, adalah yang pertama setAttribute()line, yang memberitahu PDO untuk menonaktifkan pernyataan siap ditiru dan menggunakan nyata pernyataan siap. 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 charsetopsi pada 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 preparediuraikan dan dikompilasi oleh server database. Dengan menentukan parameter (parameter a ?atau bernama seperti :namepada contoh di atas) Anda memberi tahu mesin basis data tempat Anda ingin memfilter. Kemudian ketika Anda menelepon execute, pernyataan yang disiapkan dikombinasikan dengan nilai parameter yang Anda tentukan.

Yang penting di sini adalah bahwa nilai parameter digabungkan 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 mengirim 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). Pada contoh di atas, jika $namevariabel berisi 'Sarah'; DELETE FROM employeeshasil hanya akan menjadi pencarian untuk string "'Sarah'; DELETE FROM employees", 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 diuraikan dan dikompilasi satu kali, memberi Anda beberapa keuntungan kecepatan.

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

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

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

Apakah pernyataan yang disiapkan dapat digunakan untuk kueri 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';
}

86
Hanya untuk menambahkan karena saya tidak melihatnya di tempat lain di sini, garis pertahanan lain adalah firewall aplikasi web (WAF) yang dapat mengatur aturan untuk mencari serangan injeksi sql:
jkerak

37
Juga, dokumentasi resmi mysql_query hanya memungkinkan untuk mengeksekusi satu query, jadi ada query lain selain itu; diabaikan. Bahkan jika ini sudah usang, ada banyak sistem di bawah PHP 5.5.0 dan yang dapat menggunakan fungsi ini. php.net/manual/en/function.mysql-query.php
Randall Valenciano

12
Ini adalah kebiasaan buruk tetapi merupakan solusi pasca-masalah: Tidak hanya untuk injeksi SQL tetapi untuk semua jenis injeksi (misalnya ada lubang injeksi templat tampilan dalam kerangka kerja F3 v2) jika Anda memiliki situs web atau aplikasi lama yang sedang bermasalah dari cacat injeksi, salah satu solusinya adalah dengan menetapkan kembali nilai vars yang telah ditentukan supperglobal Anda seperti $ _POST dengan nilai yang lolos pada bootstrap. Oleh PDO, masih dimungkinkan untuk melarikan diri (juga untuk kerangka kerja hari ini): substr ($ pdo-> quote ($ str, \ PDO :: PARAM_STR), 1, -1)
Alix

15
Jawaban ini tidak memiliki penjelasan tentang apa yang merupakan pernyataan yang dipersiapkan - satu hal - ini adalah pencapaian kinerja jika Anda menggunakan banyak pernyataan yang disiapkan selama permintaan Anda dan kadang-kadang menyumbang hit kinerja 10x. Kasus yang lebih baik akan menggunakan PDO dengan parameter mengikat, tetapi persiapan pernyataan tidak aktif.
donis

6
Menggunakan PDO lebih baik, jika Anda menggunakan kueri langsung pastikan Anda menggunakan mysqli :: escape_string
Kassem Itani

1652

PeringatanMySQL yang sudah ditinggalkan : Kode sampel jawaban ini (seperti kode sampel pertanyaan) menggunakan ekstensi PHP , yang sudah tidak digunakan lagi dalam PHP 5.5.0 dan dihapus seluruhnya dalam PHP 7.0.0.

Peringatan Keamanan : Jawaban ini tidak sejalan dengan praktik terbaik keamanan. Escaping tidak cukup untuk mencegah injeksi SQL , sebagai gantinya gunakan pernyataan yang sudah disiapkan . Gunakan strategi yang diuraikan di bawah ini dengan risiko Anda sendiri. (Juga, mysql_real_escape_string()dihapus dalam PHP 7.)

Jika Anda menggunakan versi PHP terbaru, mysql_real_escape_stringopsi yang diuraikan di bawah ini tidak akan lagi tersedia (meskipun mysqli::escape_stringsetara modern). Saat ini mysql_real_escape_stringopsi hanya masuk akal untuk kode lama pada versi lama PHP.


Anda punya dua opsi - keluar dari karakter khusus di Anda unsafe_variable, atau menggunakan kueri parameterisasi. Keduanya akan melindungi Anda dari injeksi SQL. Kueri parameterisasi dianggap praktik yang lebih baik tetapi akan memerlukan perubahan ke ekstensi MySQL yang lebih baru di PHP sebelum Anda dapat menggunakannya.

Kami akan membahas string dampak terendah yang lolos terlebih dahulu.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

Lihat juga, detail mysql_real_escape_stringfungsi.

Untuk menggunakan kueri parameter, Anda harus menggunakan MySQLi daripada fungsi MySQL . Untuk menulis ulang contoh Anda, kami membutuhkan sesuatu seperti yang berikut ini.

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

Fungsi kunci yang ingin Anda baca di sana adalah mysqli::prepare.

Juga, seperti yang disarankan orang lain, Anda mungkin merasa berguna / lebih mudah untuk meningkatkan lapisan abstraksi dengan sesuatu seperti PDO .

Harap dicatat bahwa kasus yang Anda tanyakan adalah kasus yang cukup sederhana dan bahwa kasus yang lebih kompleks mungkin memerlukan pendekatan yang lebih kompleks. Khususnya:

  • Jika Anda ingin mengubah struktur SQL berdasarkan input pengguna, query parameter tidak akan membantu, dan pelarian yang diperlukan tidak tercakup oleh mysql_real_escape_string. Dalam kasus seperti ini, Anda akan lebih baik melewati input pengguna melalui daftar putih untuk memastikan hanya nilai 'aman' yang diizinkan.
  • Jika Anda menggunakan bilangan bulat dari input pengguna dalam kondisi dan mengambil mysql_real_escape_stringpendekatan, Anda akan menderita masalah yang dijelaskan oleh Polinomial dalam komentar di bawah ini. Kasing ini lebih rumit karena bilangan bulat tidak akan dikelilingi oleh tanda kutip, sehingga Anda dapat mengatasinya dengan memvalidasi bahwa input pengguna hanya berisi digit.
  • Mungkin ada kasus lain yang tidak saya sadari. Anda mungkin menemukan ini adalah sumber yang berguna pada beberapa masalah yang lebih halus yang dapat Anda temui.

1
menggunakan mysql_real_escape_stringsudah cukup atau saya harus menggunakan parameter juga?
peiman F.

5
@peimanF. tetap berlatih menggunakan kueri parametrized, bahkan pada proyek lokal. Dengan pertanyaan parametrized Anda dijamin tidak akan ada injeksi SQL. Tetapi perlu diingat Anda harus membersihkan data untuk menghindari pengambilan palsu (yaitu injeksi XSS, seperti menempatkan kode HTML dalam teks) dengan htmlentitiesmisalnya
Goufalite

2
@peimanF. Praktik bagus untuk kueri parametrik dan nilai ikat, tetapi string escape sebenarnya bagus untuk saat ini
Richard

Saya memahami penyertaan mysql_real_escape_string()untuk kelengkapan, tetapi saya bukan penggemar daftar pendekatan yang paling rawan kesalahan terlebih dahulu. Pembaca mungkin dengan cepat mengambil contoh pertama. Untung sudah usang sekarang :)
Steen Schütt

1
@ SteenSchütt - Semua mysql_*fungsi sudah usang. Mereka digantikan oleh fungsi serupa mysqli_* , seperti mysqli_real_escape_string.
Rick James

1073

Setiap jawaban di sini hanya mencakup sebagian dari masalah. Sebenarnya, ada empat bagian permintaan yang berbeda yang dapat kita tambahkan ke SQL secara dinamis: -

  • Sebuah benang
  • sebuah angka
  • pengidentifikasi
  • kata kunci sintaksis

Dan pernyataan yang disiapkan hanya mencakup dua di antaranya.

Tetapi kadang-kadang kita harus membuat kueri kami lebih dinamis, menambahkan operator atau pengidentifikasi juga. Jadi, kita akan membutuhkan teknik perlindungan yang berbeda.

Secara umum, pendekatan perlindungan seperti itu didasarkan pada daftar putih .

Dalam hal ini, setiap parameter dinamis harus di-hardcode dalam skrip Anda dan dipilih dari set itu. Misalnya, untuk melakukan pemesanan dinamis:

$orders  = array("name", "price", "qty"); // Field names
$key = array_search($_GET['sort'], $orders)); // if we have such a name
$orderby = $orders[$key]; // If not, first one will be set automatically. 
$query = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

Untuk memudahkan proses saya menulis fungsi pembantu daftar putih yang melakukan semua pekerjaan dalam satu baris:

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe

Ada cara lain untuk mengamankan pengidentifikasi - melarikan diri tetapi saya lebih suka tetap masuk daftar putih sebagai pendekatan yang lebih kuat dan eksplisit. Namun selama Anda memiliki pengenal yang dikutip, Anda dapat menghindari karakter kutipan untuk membuatnya aman. Misalnya, secara default untuk mysql Anda harus menggandakan karakter kutipan untuk menghindarinya . Untuk aturan pelolosan DBMS lainnya akan berbeda.

Namun, ada masalah dengan kata kunci sintaks SQL (seperti AND, DESCdan semacamnya), tetapi daftar putih tampaknya satu-satunya pendekatan dalam kasus ini.

Jadi, rekomendasi umum dapat disebut sebagai

  • Setiap variabel yang mewakili literal data SQL, (atau, sederhananya - string SQL, atau angka) harus ditambahkan melalui pernyataan yang disiapkan. Tidak ada pengecualian.
  • Bagian permintaan lainnya, seperti kata kunci SQL, tabel atau nama bidang, atau operator - harus difilter melalui daftar putih.

Memperbarui

Meskipun ada kesepakatan umum tentang praktik terbaik mengenai perlindungan injeksi SQL, masih ada banyak praktik buruk juga. Dan beberapa dari mereka terlalu mengakar di benak pengguna PHP. Misalnya, pada halaman ini ada (walaupun tidak terlihat oleh sebagian besar pengunjung) lebih dari 80 jawaban yang dihapus - semua dihapus oleh komunitas karena kualitas buruk atau mempromosikan praktik-praktik buruk dan ketinggalan jaman. Lebih buruk lagi, beberapa jawaban buruk tidak dihapus, tetapi lebih berkembang.

Misalnya, ada (1) masih (2) masih (3) banyak (4) jawaban (5) , termasuk jawaban kedua yang paling banyak dipilih yang menyarankan Anda melarikan diri secara manual - suatu pendekatan usang yang terbukti tidak aman.

Atau ada jawaban yang sedikit lebih baik yang menyarankan metode pemformatan string lain dan bahkan menawarkannya sebagai obat mujarab. Meskipun tentu saja tidak. Metode ini tidak lebih baik dari pemformatan string biasa, namun tetap memiliki semua kelemahannya: ia hanya berlaku untuk string dan, seperti pemformatan manual lainnya, pada dasarnya opsional, ukuran tidak wajib, rentan terhadap kesalahan manusia dalam bentuk apa pun.

Saya pikir semua ini karena satu takhayul yang sangat tua, didukung oleh otoritas seperti OWASP atau manual PHP , yang menyatakan kesetaraan antara "melarikan diri" apa pun dan perlindungan dari suntikan SQL.

Terlepas dari apa yang dikatakan manual PHP untuk usia, *_escape_stringtidak berarti membuat data aman dan tidak pernah dimaksudkan untuk itu. Selain menjadi tidak berguna untuk setiap bagian SQL selain string, melarikan diri secara manual adalah salah, karena itu manual sebagai lawan dari otomatis.

Dan OWASP membuatnya lebih buruk, menekankan pada melarikan diri input pengguna yang merupakan omong kosong: seharusnya tidak ada kata-kata seperti itu dalam konteks perlindungan injeksi. Setiap variabel berpotensi berbahaya - apa pun sumbernya! Atau, dengan kata lain - setiap variabel harus diformat dengan benar untuk dimasukkan ke dalam kueri - tidak peduli sumbernya lagi. Itu tujuan yang penting. Saat pengembang mulai memisahkan domba dari kambing (berpikir apakah beberapa variabel "aman" atau tidak) ia mengambil langkah pertamanya menuju bencana. Belum lagi bahwa bahkan kata-kata menyarankan sebagian besar melarikan diri di titik masuk, menyerupai fitur kutipan ajaib - sudah dihina, ditinggalkan dan dihapus.

Jadi, tidak seperti apa pun yang "melarikan diri", pernyataan yang disiapkan adalah ukuran yang memang melindungi dari injeksi SQL (bila berlaku).


848

Saya akan merekomendasikan menggunakan PDO (Objek Data PHP) untuk menjalankan query SQL parameter.

Tidak hanya ini melindungi terhadap injeksi SQL, tetapi juga mempercepat permintaan.

Dan dengan menggunakan PDO daripada mysql_,, mysqli_dan pgsql_fungsi, Anda membuat aplikasi Anda sedikit lebih abstrak dari database, dalam kejadian langka yang Anda harus beralih penyedia database.


619

Gunakan PDOdan siapkan kueri.

( $connadalah sebuah PDOobjek)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

549

Seperti yang Anda lihat, orang-orang menyarankan Anda untuk menggunakan pernyataan yang disiapkan paling banyak. Itu tidak salah, tetapi ketika permintaan Anda dieksekusi hanya sekali per proses, akan ada sedikit penalti kinerja.

Saya menghadapi masalah ini, tapi saya pikir saya menyelesaikannya dengan cara yang sangat canggih - cara yang digunakan peretas untuk menghindari penggunaan tanda kutip. Saya menggunakan ini dalam hubungannya dengan pernyataan siap ditiru. Saya menggunakannya untuk mencegah semua jenis kemungkinan serangan injeksi SQL.

Pendekatan saya:

  • Jika Anda mengharapkan input bilangan bulat, pastikan itu benar - benar bilangan bulat. Dalam bahasa tipe-variabel seperti PHP, ini sangat penting. Misalnya, Anda dapat menggunakan solusi yang sangat sederhana namun kuat ini:sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • Jika Anda mengharapkan hal lain dari bilangan bulat hex itu . Jika Anda hex itu, Anda akan benar-benar lepas dari semua input. Di C / C ++ ada fungsi yang disebut mysql_hex_string(), di PHP Anda bisa menggunakan bin2hex().

    Jangan khawatir tentang escaped string akan memiliki ukuran 2x dari panjang aslinya karena meskipun Anda menggunakan mysql_real_escape_string, PHP harus mengalokasikan kapasitas yang sama ((2*input_length)+1), yang sama.

  • Metode hex ini sering digunakan ketika Anda mentransfer data biner, tetapi saya tidak melihat alasan mengapa tidak menggunakannya pada semua data untuk mencegah serangan injeksi SQL. Perhatikan bahwa Anda harus menambahkan data dengan 0xatau menggunakan fungsi MySQL UNHEXsebagai gantinya.

Jadi, misalnya, kueri:

SELECT password FROM users WHERE name = 'root'

Akan menjadi:

SELECT password FROM users WHERE name = 0x726f6f74

atau

SELECT password FROM users WHERE name = UNHEX('726f6f74')

Hex adalah pelarian yang sempurna. Tidak ada cara untuk menyuntikkan.

Perbedaan antara fungsi UNHEX dan awalan 0x

Ada beberapa diskusi dalam komentar, jadi saya akhirnya ingin menjelaskan. Dua pendekatan ini sangat mirip, tetapi mereka sedikit berbeda dalam beberapa hal:

Awalan ** 0x ** hanya dapat digunakan untuk kolom data seperti char, varchar, teks, blok, biner, dll .
Juga, penggunaannya sedikit rumit jika Anda hendak memasukkan string kosong. Anda harus sepenuhnya menggantinya dengan '', atau Anda akan mendapatkan kesalahan.

UNHEX () berfungsi pada kolom apa saja ; Anda tidak perlu khawatir tentang string kosong.


Metode hex sering digunakan sebagai serangan

Perhatikan bahwa metode hex ini sering digunakan sebagai serangan injeksi SQL di mana bilangan bulat seperti string dan lolos hanya dengan mysql_real_escape_string. Maka Anda dapat menghindari penggunaan kutipan.

Misalnya, jika Anda hanya melakukan sesuatu seperti ini:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

serangan dapat menyuntikkan Anda dengan sangat mudah . Pertimbangkan kode yang disuntikkan berikut yang dikembalikan dari skrip Anda:

PILIH ... DI MANA id = -1 gabungan semua table_name pilih dari information_schema.tables

dan sekarang cukup ekstrak struktur tabel:

PILIH ... DI MANA id = -1 persatuan semua pilih column_name dari information_schema.column di mana table_name = 0x61727469636c65

Dan kemudian pilih saja data apa pun yang diinginkan. Bukankah itu keren?

Tetapi jika pembuat kode dari situs yang dapat disuntikkan akan menggandakannya, tidak ada injeksi yang mungkin karena kueri akan terlihat seperti ini: SELECT ... WHERE id = UNHEX('2d312075...3635')


6
@SumitGupta Ya, Anda melakukannya. MySQL tidak digabungkan dengan +tetapi dengan CONCAT. Dan untuk kinerja: Saya tidak berpikir itu mempengaruhi kinerja karena mysql harus mengurai data dan tidak masalah jika asal adalah string atau hex
Zaffy

11
@YourCommonSense Anda tidak mengerti konsepnya ... Jika Anda ingin memiliki string dalam mysql Anda mengutipnya seperti ini 'root'atau Anda dapat menggantinya 0x726f6f74TETAPI jika Anda menginginkan nomor dan mengirimkannya sebagai string, Anda mungkin akan menulis '42' bukan CHAR (42 ) ... '42' di hex akan 0x3432tidak0x42
Zaffy

7
@YourCommonSense Saya tidak punya apa-apa untuk dikatakan ... hanya lol ... jika Anda masih ingin mencoba hex pada bidang numerik, lihat komentar kedua. Saya bertaruh dengan Anda bahwa itu akan berhasil.
Zaffy

2
Jawabannya jelas menyatakan bahwa itu tidak akan bekerja seperti itu dengan nilai integer, alasannya adalah bahwa bin2hex mengubah nilai yang diteruskan ke string (dan dengan demikian bin2hex (0) adalah 0x30, dan bukan 0x03) - itu mungkin bagian yang membingungkan Anda . Jika Anda mengikutinya, ini berfungsi dengan baik (setidaknya di situs saya, diuji dengan 4 versi mysql berbeda di mesin debian, 5.1.x hingga 5.6.x). Lagi pula, heksadesimal hanyalah cara representasi, bukan nilainya;)
griffin

5
@ YourCommonSense Anda masih tidak mengerti? Anda tidak dapat menggunakan 0x dan concat karena jika string kosong Anda akan berakhir dengan kesalahan. Jika Anda ingin alternatif sederhana untuk kueri Anda coba yang iniSELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ')
Zaffy

499

PeringatanMySQL yang sudah ditinggalkan : Kode sampel jawaban ini (seperti kode sampel pertanyaan) menggunakan ekstensi PHP , yang sudah tidak digunakan lagi dalam PHP 5.5.0 dan dihapus seluruhnya dalam PHP 7.0.0.

Peringatan Keamanan : Jawaban ini tidak sejalan dengan praktik terbaik keamanan. Escaping tidak cukup untuk mencegah injeksi SQL , sebagai gantinya gunakan pernyataan yang sudah disiapkan . Gunakan strategi yang diuraikan di bawah ini dengan risiko Anda sendiri. (Juga, mysql_real_escape_string()dihapus dalam PHP 7.)

PENTING

Cara terbaik untuk mencegah SQL Injection adalah dengan menggunakan Pernyataan yang Disiapkan bukannya melarikan diri , seperti yang ditunjukkan oleh jawaban yang diterima .

Ada perpustakaan seperti Aura.Sql dan EasyDB yang memungkinkan pengembang untuk menggunakan pernyataan yang disiapkan lebih mudah. Untuk mempelajari lebih lanjut tentang mengapa pernyataan yang disiapkan lebih baik dalam menghentikan injeksi SQL , lihat bypass inimysql_real_escape_string() dan kerentanan Unicode SQL Injection yang baru - baru ini diperbaiki di WordPress .

Pencegahan injeksi - mysql_real_escape_string ()

PHP memiliki fungsi yang dibuat khusus untuk mencegah serangan ini. Yang perlu Anda lakukan adalah menggunakan seteguk fungsi mysql_real_escape_string,.

mysql_real_escape_stringmengambil string yang akan digunakan dalam permintaan MySQL dan mengembalikan string yang sama dengan semua upaya injeksi SQL dengan aman lolos. Pada dasarnya, ini akan menggantikan tanda kutip yang merepotkan (') yang mungkin dimasukkan pengguna dengan pengganti yang aman MySQL, kutipan yang lolos \'.

CATATAN: Anda harus terhubung ke database untuk menggunakan fungsi ini!

// Hubungkan ke MySQL

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

Anda dapat menemukan rincian lebih lanjut di MySQL - SQL Injection Prevention .


30
Ini adalah yang terbaik yang dapat Anda lakukan dengan ekstensi mysql lama. Untuk kode baru, Anda disarankan untuk beralih ke mysqli atau PDO.
Álvaro González

7
Saya tidak setuju dengan ini 'fungsi yang dibuat khusus untuk mencegah serangan ini'. Saya pikir mysql_real_escape_stringtujuan itu memungkinkan untuk membangun query SQL yang benar untuk setiap input data-string. Pencegahan sql-injection adalah efek samping dari fungsi ini.
sectus

4
Anda tidak menggunakan fungsi untuk menulis input-string data yang benar. Anda hanya menulis yang benar yang tidak perlu melarikan diri atau sudah melarikan diri. mysql_real_escape_string () mungkin telah dirancang dengan tujuan yang Anda sebutkan, tetapi nilainya hanya mencegah injeksi.
Nazca

17
PERINGATAN! mysql_real_escape_string() tidak bisa salah .
eggyal

9
mysql_real_escape_stringsekarang sudah tidak digunakan lagi, sehingga tidak lagi menjadi opsi yang layak. Ini akan dihapus di masa depan dari PHP. Yang terbaik adalah beralih ke apa yang direkomendasikan orang PHP atau MySQL.
jww

462

Anda dapat melakukan sesuatu yang mendasar seperti ini:

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Ini tidak akan menyelesaikan setiap masalah, tetapi ini adalah batu loncatan yang sangat bagus. Saya meninggalkan item yang jelas seperti memeriksa keberadaan variabel, format (angka, huruf, dll.).


28
Saya telah mencoba contoh Anda dan itu berfungsi dengan baik untuk saya. Bisakah Anda menghapus "ini tidak akan menyelesaikan setiap masalah"
Chinook

14
Jika Anda tidak mengutip string, itu masih bisa disuntikkan. Ambil $q = "SELECT col FROM tbl WHERE x = $safe_var";contoh. Pengaturan $safe_varuntuk 1 UNION SELECT password FROM usersbekerja dalam hal ini karena kurangnya kutipan. Dimungkinkan juga untuk menyuntikkan string ke dalam kueri menggunakan CONCATdan CHR.
Polinomial

4
@ Polinomial Sepenuhnya benar, tapi saya akan melihat ini hanya sebagai penggunaan yang salah. Selama Anda menggunakannya dengan benar, itu pasti akan berhasil.
glglgl

22
PERINGATAN! mysql_real_escape_string() tidak bisa salah .
eggyal

8
mysql_real_escape_stringsekarang sudah tidak digunakan lagi, sehingga tidak lagi menjadi opsi yang layak. Ini akan dihapus di masa depan dari PHP. Yang terbaik adalah beralih ke apa yang direkomendasikan orang PHP atau MySQL.
jww

380

Apa pun yang Anda akhirnya gunakan, pastikan bahwa Anda memeriksa input Anda belum hancur oleh magic_quotesatau sampah baik lainnya, dan jika perlu, jalankan melalui stripslashesatau apa pun untuk membersihkannya.


11
Memang; berjalan dengan magic_quotes diaktifkan hanya mendorong praktik yang buruk. Namun, kadang-kadang Anda tidak selalu dapat mengendalikan lingkungan ke tingkat itu - baik Anda tidak memiliki akses untuk mengelola server, atau aplikasi Anda harus hidup berdampingan dengan aplikasi yang (gemetar) bergantung pada konfigurasi seperti itu. Karena alasan ini, ada baiknya untuk menulis aplikasi portabel - walaupun jelas usaha ini sia-sia jika Anda memang mengendalikan lingkungan penyebaran, misalnya karena ini adalah aplikasi internal, atau hanya akan digunakan di lingkungan spesifik Anda.
Rob

24
Pada PHP 5.4, kekejian yang dikenal sebagai 'kutipan ajaib' telah mati terbunuh . Dan riddance bagus untuk sampah buruk.
BryanH

363

PeringatanMySQL yang sudah ditinggalkan : Kode sampel jawaban ini (seperti kode sampel pertanyaan) menggunakan ekstensi PHP , yang sudah tidak digunakan lagi dalam PHP 5.5.0 dan dihapus seluruhnya dalam PHP 7.0.0.

Peringatan Keamanan : Jawaban ini tidak sejalan dengan praktik terbaik keamanan. Escaping tidak cukup untuk mencegah injeksi SQL , sebagai gantinya gunakan pernyataan yang sudah disiapkan . Gunakan strategi yang diuraikan di bawah ini dengan risiko Anda sendiri. (Juga, mysql_real_escape_string()dihapus dalam PHP 7.)

Permintaan parameterisasi dan validasi input adalah caranya. Ada banyak skenario di mana injeksi SQL dapat terjadi, meskipun mysql_real_escape_string()telah digunakan.

Contoh-contoh itu rentan terhadap injeksi SQL:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

atau

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

Dalam kedua kasus, Anda tidak dapat menggunakan 'untuk melindungi enkapsulasi.

Sumber : Injeksi SQL yang Tidak Terduga (Ketika Melarikan Diri Tidak Cukup)


2
Anda dapat mencegah injeksi SQL jika Anda mengadopsi teknik validasi input di mana input pengguna diautentikasi terhadap seperangkat aturan yang ditetapkan untuk panjang, jenis dan sintaksis dan juga terhadap aturan bisnis.
Josip Ivic

312

Menurut pendapat saya, cara terbaik untuk secara umum mencegah injeksi SQL dalam aplikasi PHP Anda (atau aplikasi web apa pun) adalah dengan memikirkan arsitektur aplikasi Anda. Jika satu-satunya cara untuk melindungi terhadap injeksi SQL adalah dengan mengingat untuk menggunakan metode atau fungsi khusus yang melakukan The Right Thing setiap kali Anda berbicara dengan database, Anda melakukan kesalahan. Dengan begitu, hanya masalah waktu sampai Anda lupa memformat kueri dengan benar di beberapa titik dalam kode Anda.

Mengadopsi pola MVC dan kerangka kerja seperti CakePHP atau CodeIgniter mungkin merupakan cara yang tepat: tugas umum seperti membuat kueri basis data yang aman telah dipecahkan dan diimplementasikan secara terpusat dalam kerangka kerja tersebut. Mereka membantu Anda untuk mengatur aplikasi web Anda dengan cara yang masuk akal dan membuat Anda berpikir lebih banyak tentang memuat dan menyimpan objek daripada tentang membangun kueri SQL tunggal secara aman.


5
Saya pikir paragraf pertama Anda penting. Memahami adalah kunci. Juga, semua orang tidak bekerja untuk perusahaan. Untuk sebagian besar orang, kerangka kerja sebenarnya bertentangan dengan gagasan pemahaman . Menjadi akrab dengan fundamental mungkin tidak dihargai saat bekerja di bawah tenggat waktu, tetapi do-it-yourselfers di luar sana menikmati tangan mereka kotor. Pengembang framework tidak begitu istimewa sehingga semua orang harus tunduk dan menganggap mereka tidak pernah melakukan kesalahan. Kekuatan untuk mengambil keputusan masih penting. Siapa yang akan mengatakan bahwa kerangka kerja saya tidak akan menggantikan skema lain di masa depan?
Anthony Rutledge

@AnthonyRutledge Anda benar sekali. Sangat penting untuk memahami apa yang sedang terjadi dan mengapa. Namun, peluang bahwa kerangka kerja yang benar dan telah dicoba serta digunakan secara aktif dan dikembangkan telah mengalami dan memecahkan banyak masalah dan menambal banyak lubang keamanan sudah cukup tinggi. Merupakan ide bagus untuk melihat sumbernya untuk merasakan kualitas kode. Jika ini adalah kekacauan yang belum diuji, mungkin itu tidak aman.
Johannes Fahrenkrug

3
Sini. Sini. Poin bagus. Namun, apakah Anda setuju bahwa banyak orang dapat belajar dan belajar untuk mengadopsi sistem MVC, tetapi tidak semua orang dapat mereproduksinya dengan tangan (pengontrol dan server). Seseorang bisa melangkah terlalu jauh dengan poin ini. Apakah saya perlu memahami microwave saya sebelum memanaskan kue pecan selai kacang yang dibuat teman perempuan saya? ;-)
Anthony Rutledge

3
@AnthonyRutledge Saya setuju! Saya pikir kasus penggunaannya membuat perbedaan juga: Apakah saya membangun galeri foto untuk beranda pribadi saya atau apakah saya membangun aplikasi web perbankan online? Dalam kasus terakhir, sangat penting untuk memahami detail keamanan dan bagaimana kerangka kerja yang saya gunakan menangani itu.
Johannes Fahrenkrug

3
Ah, pengecualian keamanan untuk melakukannya sendiri wajar. Lihat, saya cenderung mau mengambil risiko untuk itu semua dan pergi untuk bangkrut. :-) Bercanda. Dengan waktu yang cukup, orang dapat belajar membuat aplikasi yang sangat aman. Terlalu banyak orang yang terburu-buru. Mereka mengangkat tangan dan menganggap bahwa kerangka kerja itu lebih aman . Lagi pula, mereka tidak punya cukup waktu untuk menguji dan mencari tahu. Selain itu, keamanan adalah bidang yang membutuhkan studi khusus. Ini bukan sesuatu yang hanya programmer ketahui secara mendalam berdasarkan pemahaman algoritma dan pola desain.
Anthony Rutledge

298

Saya menyukai prosedur tersimpan ( MySQL telah menyimpan dukungan prosedur sejak 5.0 ) dari sudut pandang keamanan - keuntungannya adalah -

  1. Sebagian besar basis data (termasuk MySQL ) memungkinkan akses pengguna dibatasi untuk menjalankan prosedur tersimpan. Kontrol akses keamanan berbutir halus berguna untuk mencegah eskalasi serangan privilege. Ini mencegah aplikasi yang dikompromikan dari dapat menjalankan SQL langsung terhadap database.
  2. Mereka mengabstraksi kueri SQL mentah dari aplikasi sehingga lebih sedikit informasi tentang struktur basis data yang tersedia untuk aplikasi tersebut. Ini membuat lebih sulit bagi orang untuk memahami struktur dasar dari database dan merancang serangan yang sesuai.
  3. Mereka hanya menerima parameter, sehingga keuntungan dari kueri parameterisasi ada di sana. Tentu saja - IMO Anda masih perlu membersihkan input Anda - terutama jika Anda menggunakan SQL dinamis di dalam prosedur tersimpan.

Kerugiannya adalah -

  1. Mereka (prosedur tersimpan) sulit untuk dirawat dan cenderung berkembang biak dengan sangat cepat. Ini membuat pengelolaan masalah.
  2. Mereka tidak sangat cocok untuk permintaan dinamis - jika mereka dibangun untuk menerima kode dinamis sebagai parameter maka banyak keuntungan yang dinegasikan.

297

Ada banyak cara untuk mencegah injeksi SQL dan peretasan SQL lainnya. Anda dapat dengan mudah menemukannya di Internet (Google Search). Tentu saja PDO adalah salah satu solusi yang baik. Tetapi saya ingin menyarankan Anda beberapa tautan pencegahan yang baik dari injeksi SQL.

Apa itu injeksi SQL dan bagaimana mencegahnya

Manual PHP untuk injeksi SQL

Penjelasan Microsoft tentang injeksi dan pencegahan SQL dalam PHP

Dan beberapa lainnya seperti Mencegah injeksi SQL dengan MySQL dan PHP .

Sekarang, mengapa Anda perlu mencegah permintaan Anda dari injeksi SQL?

Saya ingin memberi tahu Anda: Mengapa kami mencoba mencegah injeksi SQL dengan contoh singkat di bawah ini:

Permintaan untuk pencocokan otentikasi login:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

Sekarang, jika seseorang (peretas) menaruh

$_POST['email']= admin@emali.com' OR '1=1

dan kata sandi apa pun ....

Permintaan akan diuraikan ke dalam sistem hanya hingga:

$query="select * from users where email='admin@emali.com' OR '1=1';

Bagian lainnya akan dibuang. Jadi, apa yang akan terjadi? Pengguna yang tidak berwenang (peretas) akan dapat masuk sebagai administrator tanpa memiliki kata sandinya. Sekarang, ia dapat melakukan apa saja yang dapat dilakukan oleh administrator / orang surel. Lihat, sangat berbahaya jika injeksi SQL tidak dicegah.


267

Saya pikir jika seseorang ingin menggunakan PHP dan MySQL atau server dataBase lainnya:

  1. Pikirkan tentang belajar PDO (Objek Data PHP) - ini adalah lapisan akses basis data yang menyediakan metode seragam untuk akses ke banyak basis data.
  2. Pikirkan tentang belajar MySQLi
  3. Gunakan fungsi PHP asli seperti: strip_tags , mysql_real_escape_string atau jika variabel numerik, adil (int)$foo. Baca lebih lanjut tentang jenis variabel dalam PHP di sini . Jika Anda menggunakan perpustakaan seperti PDO atau MySQLi, selalu gunakan PDO :: quote () dan mysqli_real_escape_string () .

Contoh perpustakaan:

---- PDO

----- Tanpa placeholder - matang untuk injeksi SQL! Itu buruk

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- placeholder yang tidak disebutkan namanya

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- Tempat penunjuk bernama

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

PS :

PDO memenangkan pertempuran ini dengan mudah. Dengan dukungan untuk dua belas driver basis data yang berbeda dan parameter bernama, kita dapat mengabaikan kehilangan kinerja yang kecil, dan terbiasa dengan API-nya. Dari sudut pandang keamanan, keduanya aman selama pengembang menggunakan cara mereka seharusnya digunakan

Tetapi sementara baik PDO dan MySQLi cukup cepat, MySQLi berkinerja lebih cepat secara signifikan dalam benchmark - ~ 2,5% untuk pernyataan yang tidak disiapkan, dan ~ 6,5% untuk yang disiapkan.

Dan silakan uji setiap permintaan ke database Anda - ini adalah cara yang lebih baik untuk mencegah injeksi.


bahwa mysqli salah. Param pertama mengekspresikan tipe data.
mickmackusa

257

Jika memungkinkan, masukkan tipe parameter Anda. Tapi itu hanya bekerja pada tipe sederhana seperti int, bool, dan float.

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

3
Ini adalah salah satu dari beberapa kasus di mana saya akan menggunakan "nilai yang lolos" alih-alih pernyataan yang disiapkan. Dan konversi tipe integer sangat efisien.
HoldOffHunger


224

Bagi mereka yang tidak yakin bagaimana menggunakan PDO (berasal dari mysql_fungsi), saya membuat wrapper PDO yang sangat, sangat sederhana yang merupakan file tunggal. Itu ada untuk menunjukkan betapa mudahnya melakukan semua hal umum yang perlu dilakukan aplikasi. Bekerja dengan PostgreSQL, MySQL, dan SQLite.

Pada dasarnya, bacalah saat Anda membaca manual untuk melihat cara menempatkan fungsi PDO untuk digunakan dalam kehidupan nyata agar mudah menyimpan dan mengambil nilai dalam format yang Anda inginkan.

Saya ingin satu kolom

$count = DB::column('SELECT COUNT(*) FROM `user`);

Saya ingin hasil array (key => value) (yaitu untuk membuat kotak pilih)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

Saya ingin satu hasil baris

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

Saya ingin berbagai hasil

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));

222

Menggunakan fungsi PHP ini mysql_escape_string()Anda bisa mendapatkan pencegahan yang baik dengan cara cepat.

Sebagai contoh:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string - Mengosongkan string untuk digunakan di mysql_query

Untuk pencegahan lebih lanjut, Anda dapat menambahkan di akhir ...

wHERE 1=1   or  LIMIT 1

Akhirnya Anda mendapatkan:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1

220

Beberapa pedoman untuk keluar dari karakter khusus dalam pernyataan SQL.

Jangan gunakan MySQL . Ekstensi ini sudah usang. Gunakan MySQLi atau PDO sebagai gantinya.

MySQLi

Untuk keluar secara manual dari karakter khusus dalam sebuah string, Anda dapat menggunakan fungsi mysqli_real_escape_string . Fungsi tidak akan berfungsi dengan baik kecuali set karakter yang benar diatur dengan mysqli_set_charset .

Contoh:

$mysqli = new mysqli('host', 'user', 'password', 'database');
$mysqli->set_charset('charset');

$string = $mysqli->real_escape_string($string);
$mysqli->query("INSERT INTO table (column) VALUES ('$string')");

Untuk keluar secara otomatis dari nilai dengan pernyataan yang disiapkan, gunakan mysqli_prepare , dan mysqli_stmt_bind_param di mana tipe untuk variabel bind yang sesuai harus disediakan untuk konversi yang sesuai:

Contoh:

$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();

Tidak masalah jika Anda menggunakan pernyataan yang disiapkan atau mysqli_real_escape_string, Anda selalu harus mengetahui jenis data input yang Anda kerjakan.

Jadi, jika Anda menggunakan pernyataan yang disiapkan, Anda harus menentukan jenis variabel untuk mysqli_stmt_bind_paramfungsi.

Dan penggunaannya mysqli_real_escape_stringadalah untuk, seperti namanya, keluar dari karakter khusus dalam sebuah string, sehingga tidak akan membuat bilangan bulat aman. Tujuan dari fungsi ini adalah untuk mencegah terputusnya string dalam pernyataan SQL, dan kerusakan pada database yang dapat menyebabkannya. mysqli_real_escape_stringadalah fungsi yang berguna bila digunakan dengan benar, terutama jika dikombinasikan dengan sprintf.

Contoh:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf("SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647

3
Pertanyaannya sangat umum. Beberapa jawaban bagus di atas, tetapi kebanyakan menyarankan pernyataan yang disiapkan. MySQLi async tidak mendukung pernyataan yang disiapkan, sehingga sprintf terlihat seperti pilihan yang bagus untuk situasi ini.
Dustin Graham

183

Alternatif sederhana untuk masalah ini dapat diselesaikan dengan memberikan izin yang sesuai dalam database itu sendiri. Sebagai contoh: jika Anda menggunakan database MySQL maka masukkan ke dalam database melalui terminal atau UI yang disediakan dan cukup ikuti perintah ini:

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

Ini akan membatasi pengguna untuk hanya dibatasi dengan permintaan yang ditentukan saja. Hapus izin hapus dan data tidak akan pernah dihapus dari kueri yang dipecat dari halaman PHP. Hal kedua yang harus dilakukan adalah menyiram hak istimewa sehingga MySQL menyegarkan izin dan pembaruan.

FLUSH PRIVILEGES; 

informasi lebih lanjut tentang flush .

Untuk melihat hak istimewa saat ini bagi pengguna mengaktifkan kueri berikut.

select * from mysql.user where User='username';

Pelajari lebih lanjut tentang GRANT .


25
Jawaban ini pada dasarnya salah , karena tidak membantu mencegah pencegahan injeksi tetapi hanya mencoba melunakkan konsekuensinya. Sia-sia.
Akal Sehat Anda

1
Benar, itu tidak memberikan solusi, tetapi apa yang dapat Anda lakukan sebelumnya untuk menghindari hal-hal.
Apurv Nerlekar

1
@Apurv Jika tujuan saya adalah membaca informasi pribadi dari database Anda, maka tidak memiliki izin DELETE tidak ada artinya.
Alex Holsgrove

1
@AlexHolsgrove: Tenang saja, saya hanya menyarankan praktik yang baik untuk melunakkan konsekuensinya.
Apurv Nerlekar

2
@Apurv Anda tidak ingin "melunakkan konsekuensi", Anda ingin melakukan segala yang mungkin untuk melindunginya. Agar adil, pengaturan akses pengguna yang benar adalah penting, tetapi tidak benar-benar apa yang diminta OP.
Alex Holsgrove

177

Mengenai banyak jawaban yang bermanfaat, saya berharap dapat menambah nilai pada utas ini.

Injeksi SQL adalah serangan yang dapat dilakukan melalui input pengguna (input yang diisi oleh pengguna dan kemudian digunakan di dalam kueri). Pola injeksi SQL adalah sintaks kueri yang benar sementara kita dapat menyebutnya: kueri buruk karena alasan buruk, dan kami berasumsi bahwa mungkin ada orang jahat yang mencoba untuk mendapatkan informasi rahasia (melewati kontrol akses) yang memengaruhi tiga prinsip keamanan (kerahasiaan) , integritas, dan ketersediaan).

Sekarang, poin kami adalah untuk mencegah ancaman keamanan seperti serangan injeksi SQL, pertanyaan yang diajukan (bagaimana mencegah serangan injeksi SQL menggunakan PHP), menjadi lebih realistis, penyaringan data atau membersihkan data input adalah kasus ketika menggunakan input data pengguna di dalam permintaan seperti itu, menggunakan PHP atau bahasa pemrograman lain tidak demikian, atau seperti yang direkomendasikan oleh lebih banyak orang untuk menggunakan teknologi modern seperti pernyataan yang disiapkan atau alat lain yang saat ini mendukung pencegahan injeksi SQL, apakah alat ini tidak tersedia lagi? Bagaimana Anda mengamankan aplikasi Anda?

Pendekatan saya terhadap injeksi SQL adalah: membersihkan data input pengguna sebelum mengirimnya ke database (sebelum menggunakannya di dalam permintaan apa pun).

Pemfilteran data untuk (mengonversi data yang tidak aman ke data yang aman)

Pertimbangkan bahwa PDO dan MySQLi tidak tersedia. Bagaimana Anda bisa mengamankan aplikasi Anda? Apakah Anda memaksa saya untuk menggunakannya? Bagaimana dengan bahasa lain selain PHP? Saya lebih suka memberikan gagasan umum karena dapat digunakan untuk perbatasan yang lebih luas, bukan hanya untuk bahasa tertentu.

  1. Pengguna SQL (membatasi hak pengguna): operasi SQL yang paling umum adalah (SELECT, UPDATE, INSERT), lalu, mengapa memberikan hak istimewa UPDATE kepada pengguna yang tidak memerlukannya? Misalnya, halaman login, dan pencarian hanya menggunakan SELECT, lalu, mengapa menggunakan pengguna DB di halaman ini dengan hak istimewa tinggi?

ATURAN: jangan membuat satu pengguna basis data untuk semua hak istimewa. Untuk semua operasi SQL, Anda dapat membuat skema seperti (deluser, selectuser, updateuser) sebagai nama pengguna untuk penggunaan yang mudah.

Lihat prinsip privilege paling tidak .

  1. Pemfilteran data: sebelum membangun input pengguna kueri apa pun, harus divalidasi dan difilter. Untuk programmer, penting untuk mendefinisikan beberapa properti untuk setiap variabel input pengguna: tipe data, pola data, dan panjang data . Bidang yang merupakan angka antara (x dan y) harus benar-benar divalidasi menggunakan aturan yang tepat, dan untuk bidang yang berupa string (teks): pola adalah kasusnya, misalnya, nama pengguna harus berisi hanya beberapa karakter, mari katakan [a-zA-Z0-9_-.]. Panjangnya bervariasi antara (x dan n) di mana x dan n (bilangan bulat, x <= n). Aturan: membuat filter yang tepat dan aturan validasi adalah praktik terbaik bagi saya.

  2. Gunakan alat lain: Di sini, saya juga akan setuju dengan Anda bahwa pernyataan yang disiapkan (permintaan parametrized) dan prosedur tersimpan. Kerugiannya di sini adalah cara-cara ini membutuhkan keterampilan tingkat lanjut yang tidak ada untuk sebagian besar pengguna. Ide dasar di sini adalah untuk membedakan antara permintaan SQL dan data yang digunakan di dalamnya. Kedua pendekatan dapat digunakan bahkan dengan data yang tidak aman, karena data input pengguna di sini tidak menambahkan apa pun ke permintaan asli, seperti (apa saja atau x = x).

Untuk informasi lebih lanjut, silakan baca Lembar Cheat Pencegahan Injeksi SQL OWASP .

Sekarang, jika Anda adalah pengguna tingkat lanjut, mulailah menggunakan pertahanan ini sesuka Anda, tetapi, untuk pemula, jika mereka tidak dapat dengan cepat menerapkan prosedur tersimpan dan menyiapkan pernyataan, lebih baik menyaring data input sebanyak yang mereka bisa.

Akhirnya, mari pertimbangkan bahwa pengguna mengirim teks ini di bawah alih-alih memasukkan nama pengguna:

[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

Input ini dapat diperiksa lebih awal tanpa pernyataan yang disiapkan dan prosedur tersimpan, tetapi untuk berada di sisi yang aman, menggunakannya dimulai setelah penyaringan dan validasi data pengguna.

Poin terakhir adalah mendeteksi perilaku tak terduga yang membutuhkan lebih banyak upaya dan kompleksitas; itu tidak disarankan untuk aplikasi web normal.

Perilaku tak terduga dalam input pengguna di atas adalah SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA, dan root. Setelah kata-kata ini terdeteksi, Anda dapat menghindari input.

PEMBARUAN 1:

Seorang pengguna berkomentar bahwa posting ini tidak berguna, OK! Inilah yang disediakan OWASP.ORG :

Pertahanan utama:

Opsi # 1: Penggunaan Pernyataan yang Disiapkan (Kueri Parameterized)
Opsi # 2: Penggunaan Prosedur yang Disimpan
Opsi # 3: Melarikan Diri dari Semua Input yang Disuplai Pengguna

Pertahanan tambahan:

Juga Menegakkan: Least Privilege
Juga Melakukan: Validasi Input Daftar Putih

Seperti yang Anda ketahui, mengklaim sebuah artikel harus didukung oleh argumen yang valid, setidaknya dengan satu referensi! Kalau tidak, itu dianggap sebagai serangan dan klaim buruk!

Pembaruan 2:

Dari manual PHP, PHP: Pernyataan Disiapkan - Manual :

Melarikan diri dan injeksi SQL

Variabel terikat akan lolos secara otomatis oleh server. Server memasukkan nilai yang diloloskan di tempat yang sesuai ke dalam templat pernyataan sebelum dieksekusi. Petunjuk harus disediakan ke server untuk jenis variabel terikat, untuk membuat konversi yang sesuai. Lihat fungsi mysqli_stmt_bind_param () untuk informasi lebih lanjut.

Pelolosan nilai secara otomatis di dalam server terkadang dianggap sebagai fitur keamanan untuk mencegah injeksi SQL. Tingkat keamanan yang sama dapat dicapai dengan pernyataan yang tidak disiapkan jika nilai input diloloskan dengan benar.

Pembaruan 3:

Saya membuat kasus uji untuk mengetahui bagaimana PDO dan MySQLi mengirim kueri ke server MySQL saat menggunakan pernyataan yang disiapkan:

PDO:

$user = "''1''"; // Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));

Log Kueri:

    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit

MySQLi:

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();

Log Kueri:

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit

Jelas bahwa pernyataan yang dipersiapkan juga melarikan diri dari data, tidak ada yang lain.

Seperti juga disebutkan dalam pernyataan di atas,

Pelolosan nilai secara otomatis di dalam server terkadang dianggap sebagai fitur keamanan untuk mencegah injeksi SQL. Tingkat keamanan yang sama dapat dicapai dengan pernyataan yang tidak disiapkan, jika nilai input diloloskan dengan benar

Oleh karena itu, ini membuktikan bahwa validasi data seperti intval()ide yang baik untuk nilai integer sebelum mengirim permintaan apa pun. Selain itu, mencegah data pengguna jahat sebelum mengirim kueri adalah pendekatan yang benar dan valid .

Silakan lihat pertanyaan ini untuk lebih detail: PDO mengirimkan permintaan mentah ke MySQL sementara Mysqli mengirimkan permintaan yang sudah disiapkan, keduanya menghasilkan hasil yang sama

Referensi:

  1. Lembar Cheat Injeksi SQL
  2. Injeksi SQL
  3. Informasi keamanan
  4. Prinsip Keamanan
  5. Validasi data

175

Peringatan Keamanan : Jawaban ini tidak sejalan dengan praktik terbaik keamanan. Escaping tidak cukup untuk mencegah injeksi SQL , sebagai gantinya gunakan pernyataan yang sudah disiapkan . Gunakan strategi yang diuraikan di bawah ini dengan risiko Anda sendiri. (Juga, mysql_real_escape_string()dihapus dalam PHP 7.)

Peringatan Terhenti : Ekstensi mysql sudah tidak digunakan saat ini. kami sarankan untuk menggunakan ekstensi PDO

Saya menggunakan tiga cara berbeda untuk mencegah aplikasi web saya rentan terhadap injeksi SQL.

  1. Penggunaan mysql_real_escape_string(), yang merupakan fungsi yang telah ditetapkan dalam PHP , dan kode pengaya ini backslashes untuk karakter berikut: \x00, \n, \r, \, ', "dan\x1a . Lewati nilai input sebagai parameter untuk meminimalkan kemungkinan injeksi SQL.
  2. Cara paling canggih adalah dengan menggunakan PDO.

Saya harap ini akan membantu Anda.

Pertimbangkan pertanyaan berikut:

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string () tidak akan dilindungi di sini. Jika Anda menggunakan tanda kutip tunggal ('') di sekitar variabel Anda di dalam kueri Anda adalah apa yang melindungi Anda dari ini. Berikut ini solusi untuk ini:

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

Pertanyaan ini memiliki beberapa jawaban bagus tentang ini.

Saya sarankan, menggunakan PDO adalah pilihan terbaik.

Edit:

mysql_real_escape_string()sudah ditinggalkan pada PHP 5.5.0. Gunakan mysqli atau PDO.

Alternatif untuk mysql_real_escape_string () adalah

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Contoh:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");

169

Cara sederhana adalah dengan menggunakan kerangka kerja PHP seperti CodeIgniter atau Laravel yang memiliki fitur bawaan seperti pemfilteran dan rekaman aktif sehingga Anda tidak perlu khawatir dengan nuansa ini.


7
Saya pikir inti dari pertanyaannya adalah menyelesaikan ini tanpa menggunakan kerangka kerja seperti itu.
Sanke

147

Peringatan: pendekatan yang dijelaskan dalam jawaban ini hanya berlaku untuk skenario yang sangat spesifik dan tidak aman karena serangan injeksi SQL tidak hanya mengandalkan kemampuan untuk menyuntikkan X=Y .

Jika penyerang mencoba meretas formulir melalui $_GETvariabel PHP atau dengan string kueri URL, Anda akan dapat menangkap mereka jika mereka tidak aman.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

Karena 1=1, 2=2, 1=2, 2=1, 1+1=2, dll ... adalah pertanyaan umum untuk database SQL dari penyerang. Mungkin juga digunakan oleh banyak aplikasi peretasan.

Tetapi Anda harus berhati-hati, bahwa Anda tidak boleh menulis ulang permintaan yang aman dari situs Anda. Kode di atas memberi Anda tip, untuk menulis ulang atau mengalihkan (tergantung Anda) bahwa string kueri dinamis khusus peretasan ke halaman yang akan menyimpan alamat IP penyerang , atau BAHKAN COOKIES, riwayat, peramban, atau sensitif lainnya. informasi, sehingga Anda dapat menangani mereka nanti dengan melarang akun mereka atau menghubungi pihak berwenang.


Ada apa dengan ini 1-1=0? :)
Rápli András

@ RápliAndrás Semacam ([0-9\-]+)=([0-9]+).
5ervant

127

Ada begitu banyak jawaban untuk PHP dan MySQL , tetapi di sini ada kode untuk PHP dan Oracle untuk mencegah injeksi SQL serta penggunaan driver oci8 secara teratur:

$conn = oci_connect($username, $password, $connection_string);
$stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
oci_bind_by_name($stmt, ':xx', $fieldval);
oci_execute($stmt);

Tolong jelaskan parameter oci_bind_by_name.
Jahanzeb Awan

127

Ide yang bagus adalah dengan menggunakan mapper objek-relasional seperti Idiorm :

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

Ini tidak hanya menyelamatkan Anda dari suntikan SQL, tetapi dari kesalahan sintaks juga! Ini juga mendukung koleksi model dengan metode chaining untuk memfilter atau menerapkan tindakan ke beberapa hasil sekaligus dan beberapa koneksi.


124

PeringatanMySQL yang sudah ditinggalkan : Kode sampel jawaban ini (seperti kode sampel pertanyaan) menggunakan ekstensi PHP , yang sudah tidak digunakan lagi dalam PHP 5.5.0 dan dihapus seluruhnya dalam PHP 7.0.0.

Peringatan Keamanan : Jawaban ini tidak sejalan dengan praktik terbaik keamanan. Escaping tidak cukup untuk mencegah injeksi SQL , sebagai gantinya gunakan pernyataan yang sudah disiapkan . Gunakan strategi yang diuraikan di bawah ini dengan risiko Anda sendiri. (Juga, mysql_real_escape_string()dihapus dalam PHP 7.)

Menggunakan PDO dan MYSQLi adalah praktik yang baik untuk mencegah injeksi SQL, tetapi jika Anda benar-benar ingin bekerja dengan fungsi dan kueri MySQL, akan lebih baik menggunakan

mysql_real_escape_string

$unsafe_variable = mysql_real_escape_string($_POST['user_input']);

Ada lebih banyak kemampuan untuk mencegah hal ini: seperti mengidentifikasi - jika inputnya berupa string, angka, char atau array, ada begitu banyak fungsi bawaan untuk mendeteksi hal ini. Selain itu, akan lebih baik menggunakan fungsi-fungsi ini untuk memeriksa data input.

is_string

$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');

is_numeric

$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');

Dan jauh lebih baik menggunakan fungsi-fungsi itu untuk memeriksa data input mysql_real_escape_string.


10
Juga, sama sekali tidak ada gunanya memeriksa anggota array $ _POST dengan is_string ()
Your Common Sense

21
PERINGATAN! mysql_real_escape_string() tidak bisa salah .
eggyal

10
mysql_real_escape_stringsekarang sudah tidak digunakan lagi, sehingga tidak lagi menjadi opsi yang layak. Ini akan dihapus dari PHP di masa depan. Yang terbaik adalah beralih ke apa yang direkomendasikan orang PHP atau MySQL.
jww

2
Tema: Jangan percaya data yang dikirimkan pengguna. Apa pun yang Anda harapkan adalah data sampah dengan karakter khusus atau logika boolean, yang seharusnya menjadi bagian dari kueri SQL yang Anda jalankan. Simpan nilai $ _POST sebagai data saja, bukan bagian SQL.
Bimal Poudel

88

Saya telah menulis fungsi kecil ini beberapa tahun yang lalu:

function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

Ini memungkinkan menjalankan pernyataan dalam String C # -ish one-liner.Format seperti:

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

Itu luput mempertimbangkan jenis variabel. Jika Anda mencoba membuat parameter tabel, nama kolom, itu akan gagal karena menempatkan setiap string dalam tanda kutip yang merupakan sintaks yang tidak valid.

PEMBARUAN KEAMANAN: Versi sebelumnya str_replacememungkinkan injeksi dengan menambahkan token {#} ke dalam data pengguna. Ini preg_replace_callbackversi tidak menyebabkan masalah jika pengganti berisi token tersebut.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.