Apakah htmlspecialchars dan mysql_real_escape_string menjaga kode PHP saya aman dari injeksi?

116

Sebelumnya hari ini sebuah pertanyaan telah diajukan mengenai strategi validasi input di aplikasi web .

Jawaban teratas, pada saat penulisan, menyarankan PHPhanya dengan menggunakan htmlspecialcharsdan mysql_real_escape_string.

Pertanyaan saya adalah: Apakah ini selalu cukup? Apakah ada lagi yang harus kita ketahui? Di mana fungsi-fungsi ini rusak?

Cheekysoft
sumber

Jawaban:

241

Ketika datang ke kueri database, selalu coba dan gunakan kueri berparameter yang disiapkan. The mysqlidan PDOperpustakaan mendukung ini. Ini jauh lebih aman daripada menggunakan fungsi pelolosan seperti mysql_real_escape_string.

Ya, mysql_real_escape_stringsecara efektif hanya fungsi pelarian string. Ini bukan peluru ajaib. Yang akan dilakukannya hanyalah melarikan diri dari karakter berbahaya agar dapat digunakan dengan aman dalam satu string kueri. Namun, jika Anda tidak membersihkan input Anda sebelumnya, maka Anda akan rentan terhadap vektor serangan tertentu.

Bayangkan SQL berikut ini:

$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);

Anda harus dapat melihat bahwa ini rentan untuk dieksploitasi.
Bayangkan idparameter yang berisi vektor serangan umum:

1 OR 1=1

Tidak ada karakter berisiko di sana untuk dikodekan, jadi itu akan melewati filter melarikan diri. Meninggalkan kami:

SELECT fields FROM table WHERE id= 1 OR 1=1

Yang merupakan vektor injeksi SQL yang bagus dan akan memungkinkan penyerang mengembalikan semua baris. Atau

1 or is_admin=1 order by id limit 1

yang menghasilkan

SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1

Yang memungkinkan penyerang mengembalikan detail administrator pertama dalam contoh yang sepenuhnya fiksi ini.

Meskipun fungsi-fungsi ini berguna, mereka harus digunakan dengan hati-hati. Anda perlu memastikan bahwa semua masukan web divalidasi sampai tingkat tertentu. Dalam hal ini, kami melihat bahwa kami dapat dieksploitasi karena kami tidak memeriksa bahwa variabel yang kami gunakan sebagai angka, sebenarnya numerik. Dalam PHP Anda harus banyak menggunakan serangkaian fungsi untuk memeriksa bahwa input adalah integer, float, alfanumerik, dll. Tetapi ketika datang ke SQL, perhatikan sebagian besar nilai dari pernyataan yang disiapkan. Kode di atas akan aman jika itu adalah pernyataan yang disiapkan karena fungsi database akan tahu bahwa 1 OR 1=1itu bukan literal yang valid.

Adapun htmlspecialchars(). Itu adalah ladang ranjau miliknya sendiri.

Ada masalah nyata dalam PHP karena ia memiliki seluruh pilihan fungsi pelolosan terkait html yang berbeda, dan tidak ada panduan yang jelas tentang fungsi mana yang melakukan apa.

Pertama, jika Anda berada di dalam tag HTML, Anda benar-benar dalam masalah. Melihat

echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';

Kita sudah berada di dalam tag HTML, jadi kita tidak perlu <atau> melakukan sesuatu yang berbahaya. Vektor serangan kami mungkin sajajavascript:alert(document.cookie)

Sekarang HTML yang dihasilkan terlihat seperti

<img src= "javascript:alert(document.cookie)" />

Serangan itu langsung masuk.

Lebih buruk. Mengapa? karena htmlspecialchars(jika disebut demikian) hanya menyandikan tanda kutip ganda dan tidak tunggal. Jadi jika kita punya

echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";

Penyerang jahat kami sekarang dapat memasukkan parameter baru

pic.png' onclick='location.href=xxx' onmouseover='...

memberi kami

<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />

Dalam kasus ini, tidak ada peluru ajaib, Anda hanya perlu menyesuaikan masukannya sendiri. Jika Anda mencoba dan menyaring karakter jahat, Anda pasti akan gagal. Ambil pendekatan daftar putih dan biarkan melalui karakter yang bagus. Lihatlah lembar contekan XSS untuk contoh tentang betapa beragamnya vektor

Bahkan jika Anda menggunakan di htmlspecialchars($string)luar tag HTML, Anda masih rentan terhadap vektor serangan charset multi-byte.

Cara yang paling efektif adalah menggunakan kombinasi mb_convert_encoding dan htmlentities sebagai berikut.

$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');

Bahkan ini membuat IE6 rentan, karena cara menangani UTF. Namun, Anda dapat kembali ke pengkodean yang lebih terbatas, seperti ISO-8859-1, hingga penggunaan IE6 berhenti.

Untuk studi yang lebih mendalam tentang masalah multibyte, lihat https://stackoverflow.com/a/12118602/1820

Cheekysoft
sumber
24
Satu-satunya hal yang terlewat di sini, adalah bahwa contoh pertama untuk kueri DB ... intval sederhana () akan menyelesaikan injeksi. Selalu gunakan intval () sebagai pengganti mysqlescape ... () saat membutuhkan angka dan bukan string.
Robert K
11
dan ingat bahwa menggunakan kueri berparameter akan memungkinkan Anda untuk selalu memperlakukan data sebagai data dan bukan kode. Gunakan pustaka seperti PDO dan gunakan kueri berparameter jika memungkinkan.
Cheekysoft
9
Dua komentar: 1. Pada contoh pertama, Anda akan aman jika Anda juga meletakkan tanda kutip di sekitar parameter, seperti $result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";2. Dalam kasus kedua (atribut berisi URL), tidak ada gunanya htmlspecialcharssama sekali; dalam kasus ini, Anda harus menyandikan masukan menggunakan skema pengkodean URL, misalnya, menggunakan rawurlencode. Dengan cara itu, pengguna tidak dapat memasukkan javascript:et al.
Marcel Korpel
7
“Htmlspecialchars hanya menyandikan tanda kutip ganda dan bukan tunggal”: itu tidak benar, itu tergantung pada flag yang disetel, lihat parameternya .
Marcel Korpel
2
Ini harus dicetak tebal: Take a whitelist approach and only let through the chars which are good.Daftar hitam akan selalu melewatkan sesuatu. +1
Jo Smo
10

Selain jawaban bagus Cheekysoft:

  • Ya, mereka akan membuat Anda tetap aman, tetapi hanya jika digunakan dengan benar. Gunakan secara tidak benar dan Anda akan tetap rentan, dan mungkin mengalami masalah lain (misalnya kerusakan data)
  • Silakan gunakan kueri berparameter sebagai gantinya (seperti yang dinyatakan di atas). Anda dapat menggunakannya melalui misalnya PDO atau melalui pembungkus seperti PEAR DB
  • Pastikan bahwa magic_quotes_gpc dan magic_quotes_runtime tidak aktif setiap saat, dan jangan pernah aktif secara tidak sengaja, meskipun hanya sebentar. Ini adalah upaya awal dan sangat sesat oleh pengembang PHP untuk mencegah masalah keamanan (yang menghancurkan data)

Sebenarnya tidak ada solusi ampuh untuk mencegah injeksi HTML (mis. Pembuatan skrip lintas situs), tetapi Anda mungkin dapat mencapainya dengan lebih mudah jika Anda menggunakan pustaka atau sistem template untuk menghasilkan HTML. Baca dokumentasi untuk itu tentang bagaimana melarikan diri dengan tepat.

Dalam HTML, hal-hal perlu di-escape secara berbeda tergantung pada konteksnya. Hal ini terutama berlaku untuk string yang ditempatkan ke Javascript.

MarkR
sumber
3

Saya pasti setuju dengan posting di atas, tetapi saya memiliki satu hal kecil untuk ditambahkan sebagai balasan atas jawaban Cheekysoft, yaitu:

Ketika datang ke kueri database, selalu coba dan gunakan kueri berparameter yang disiapkan. Perpustakaan mysqli dan PDO mendukung ini. Ini jauh lebih aman daripada menggunakan fungsi pelolosan seperti mysql_real_escape_string.

Ya, mysql_real_escape_string secara efektif hanyalah fungsi pelarian string. Ini bukan peluru ajaib. Yang akan dilakukannya hanyalah melarikan diri dari karakter berbahaya agar dapat digunakan dengan aman dalam satu string kueri. Namun, jika Anda tidak membersihkan input Anda sebelumnya, maka Anda akan rentan terhadap vektor serangan tertentu.

Bayangkan SQL berikut ini:

$ result = "PILIH bidang DARI tabel WHERE id =" .mysql_real_escape_string ($ _ POST ['id']);

Anda harus dapat melihat bahwa ini rentan untuk dieksploitasi. Bayangkan parameter id berisi vektor serangan umum:

1 ATAU 1 = 1

Tidak ada karakter berisiko di sana untuk dikodekan, jadi itu akan melewati filter melarikan diri. Meninggalkan kami:

PILIH bidang DARI tabel DI MANA id = 1 ATAU 1 = 1

Saya membuat kode fungsi kecil cepat yang saya masukkan ke kelas database saya yang akan menghapus apa pun yang bukan angka. Ini menggunakan preg_replace, jadi ada kemungkinan fungsi yang sedikit lebih dioptimalkan, tetapi berfungsi dalam keadaan darurat ...

function Numbers($input) {
  $input = preg_replace("/[^0-9]/","", $input);
  if($input == '') $input = 0;
  return $input;
}

Jadi, alih-alih menggunakan

$ result = "PILIH field DARI tabel WHERE id =" .mysqlrealescapestring ("1 OR 1 = 1");

Saya akan menggunakan

$ result = "PILIH field DARI tabel WHERE id =" .Number ("1 OR 1 = 1");

dan itu akan menjalankan kueri dengan aman

PILIH field DARI tabel WHERE id = 111

Tentu, itu hanya menghentikannya dari menampilkan baris yang benar, tapi saya rasa itu bukan masalah besar bagi siapa pun yang mencoba menyuntikkan sql ke situs Anda;)

Musim Dingin yang Cemerlang
sumber
1
Sempurna! Ini adalah jenis sanitasi yang Anda butuhkan. Kode awal gagal karena tidak memvalidasi bahwa suatu angka adalah numerik. Kode Anda melakukan ini. Anda harus memanggil Numbers () pada semua vars penggunaan integer yang nilainya berasal dari luar basis kode.
Cheekysoft
1
Perlu disebutkan bahwa intval () akan berfungsi dengan baik untuk ini, karena PHP secara otomatis memaksa integer ke string untuk Anda.
Adam Ernst
11
Saya lebih suka intval. Ternyata 1abc2 menjadi 1, bukan 12.
jmucchiello
1
intval lebih baik, terutama pada ID. Seringkali, jika sudah rusak, itu seperti di atas, 1 atau 1 = 1. Anda benar-benar tidak boleh membocorkan ID orang lain. Jadi intval akan mengembalikan ID yang benar. Setelah itu, Anda harus memeriksa apakah nilai asli dan yang dibersihkan sama. Ini cara yang bagus untuk tidak hanya menghentikan serangan, tapi menemukan penyerang.
triunenature
2
Baris yang salah akan menjadi bencana jika Anda menampilkan data pribadi, Anda akan melihat informasi pengguna lain! sebaliknya akan lebih baik untuk memeriksareturn preg_match('/^[0-9]+$/',$input) ? $input : 0;
Frank Forte
2

Bagian penting dari teka-teki ini adalah konteks. Seseorang yang mengirimkan "1 OR 1 = 1" sebagai ID tidak menjadi masalah jika Anda mengutip setiap argumen dalam kueri Anda:

SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"

Yang mengakibatkan:

SELECT fields FROM table WHERE id='1 OR 1=1'

yang tidak efektif. Karena Anda keluar dari string, input tidak dapat keluar dari konteks string. Saya telah menguji ini sejauh versi 5.0.45 dari MySQL, dan menggunakan konteks string untuk kolom integer tidak menimbulkan masalah.

Lucas Oman
sumber
15
dan kemudian saya akan memulai vektor serangan saya dengan multi-byte char 0xbf27 yang dalam database latin1 Anda akan diubah oleh fungsi filter sebagai 0xbf5c27 - yang merupakan karakter multibyte tunggal diikuti dengan satu kutipan.
Cheekysoft
8
Cobalah untuk tidak melindungi dari satu vektor serangan yang diketahui. Anda akhirnya akan mengejar ekor Anda sampai akhir waktu menerapkan tambalan demi tambalan ke kode Anda. Berdiri di belakang dan melihat kasus umum akan mempelajari kode yang lebih aman dan pola pikir yang lebih berfokus pada keamanan.
Cheekysoft
Saya setuju; idealnya, OP akan menggunakan pernyataan yang telah disiapkan.
Lucas Oman
1
Meskipun kutipan argumen yang disarankan oleh posting ini tidak sangat mudah, itu akan mengurangi banyak serangan tipe 1 ATAU 1 = 1 yang umum sehingga layak untuk disebutkan.
Night Owl pada
2
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];

Bekerja dengan baik, bahkan lebih baik pada sistem 64 bit. Waspadalah terhadap batasan sistem Anda dalam menangani jumlah besar, tetapi untuk id database ini berfungsi dengan baik 99% dari waktu.

Anda harus menggunakan satu fungsi / metode untuk membersihkan nilai-nilai Anda juga. Meskipun fungsi ini hanya pembungkus untuk mysql_real_escape_string (). Mengapa? Karena suatu hari ketika eksploitasi ke metode pembersihan data pilihan Anda ditemukan, Anda hanya perlu memperbaruinya di satu tempat, daripada mencari dan mengganti di seluruh sistem.

cnizzardini.dll
sumber
-3

mengapa, oh MENGAPA, Anda tidak akan menyertakan tanda kutip di sekitar input pengguna dalam pernyataan sql Anda? tampaknya cukup konyol untuk tidak melakukannya! memasukkan tanda kutip dalam pernyataan sql Anda akan membuat "1 atau 1 = 1" usaha yang sia-sia, bukan?

jadi sekarang, Anda akan berkata, "bagaimana jika pengguna menyertakan kutipan (atau tanda kutip ganda) di input?"

baik, perbaikan mudah untuk itu: cukup hapus kutipan yang dimasukkan pengguna. mis input =~ s/'//g;. : . sekarang, menurut saya, input pengguna itu akan diamankan ...

Jarett L.
sumber
"mengapa, oh MENGAPA, Anda tidak akan menyertakan tanda kutip di sekitar input pengguna dalam pernyataan sql Anda?" - Pertanyaannya tidak menjelaskan apa pun tentang tidak mengutip input pengguna.
Quentin
1
"baik, perbaikan mudah untuk itu" - Perbaikan yang buruk untuk itu. Itu membuang data. Solusi yang disebutkan dalam pertanyaan itu sendiri adalah pendekatan yang lebih baik.
Quentin
sementara saya setuju pertanyaan tidak membahas mengutip masukan pengguna, tampaknya masih belum mengutip masukan. dan, saya lebih suka membuang data daripada memasukkan data yang buruk. umumnya, dalam serangan injeksi, Anda TIDAK menginginkan data itu .... kan?
Jarett L
"sementara saya setuju pertanyaan tersebut tidak membahas mengutip masukan pengguna, tampaknya masih belum mengutip masukan tersebut." - Tidak, tidak. Pertanyaannya tidak menunjukkannya dengan satu atau lain cara.
Quentin
1
@JarettL Biasakan menggunakan pernyataan yang disiapkan atau biasakan Tabel Bobby merusak data Anda setiap hari Selasa . SQL parameterized adalah satu-satunya cara terbaik untuk melindungi diri Anda dari injeksi SQL. Anda tidak perlu melakukan "pemeriksaan injeksi SQL" jika Anda menggunakan pernyataan yang sudah disiapkan. Mereka sangat mudah diterapkan (dan menurut saya, membuat kode JAUH lebih mudah dibaca), melindungi dari berbagai keanehan penggabungan string dan injeksi sql, dan yang terbaik, Anda tidak perlu menemukan kembali roda untuk menerapkannya .
Siyual