Apakah ada kemungkinan injeksi SQL bahkan ketika menggunakan mysql_real_escape_string()
fungsi?
Pertimbangkan situasi sampel ini. SQL dibuat dalam PHP seperti ini:
$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));
$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";
Saya telah mendengar banyak orang mengatakan kepada saya bahwa kode seperti itu masih berbahaya dan mungkin untuk diretas bahkan dengan mysql_real_escape_string()
fungsi yang digunakan. Tapi saya tidak bisa memikirkan kemungkinan eksploitasi?
Suntikan klasik seperti ini:
aaa' OR 1=1 --
tidak bekerja.
Apakah Anda tahu kemungkinan injeksi yang akan melewati kode PHP di atas?
mysql_*
fungsi dalam kode baru . Mereka tidak lagi dipertahankan dan proses penghinaan telah dimulai. Lihat kotak merah ? Pelajari lebih lanjut tentang pernyataan yang disiapkan , dan gunakan PDO atau MySQLi - artikel ini akan membantu Anda memutuskan yang mana. Jika Anda memilih PDO, berikut adalah tutorial yang bagus .mysql_*
fungsi sudah menghasilkanE_DEPRECATED
peringatan. Theext/mysql
ekstensi tidak dipertahankan selama lebih dari 10 tahun. Apakah Anda benar-benar delusi?Jawaban:
Pertimbangkan pertanyaan berikut:
mysql_real_escape_string()
tidak akan melindungi Anda dari ini. Fakta bahwa Anda menggunakan tanda kutip tunggal (' '
) di sekitar variabel Anda di dalam kueri Anda adalah apa yang melindungi Anda terhadap ini. Berikut ini juga merupakan opsi:sumber
mysql_query()
tidak menjalankan banyak pernyataan, bukan?DROP TABLE
, dalam praktik penyerang lebih cenderungSELECT passwd FROM users
. Dalam kasus terakhir, permintaan kedua biasanya dijalankan dengan menggunakanUNION
klausa.(int)mysql_real_escape_string
- ini tidak masuk akal. Sama sekali tidak berbeda(int)
. Dan mereka akan menghasilkan hasil yang sama untuk setiap inputmysql_real_escape_string
, bukanmysql_real_escape_integer
. Ini tidak berarti digunakan dengan bidang bilangan bulat.Jawaban singkatnya adalah ya, ya ada cara untuk berkeliling
mysql_real_escape_string()
.Untuk KASUS TEPI Sangat OBSCURE !!!
Jawaban panjangnya tidak mudah. Ini didasarkan pada serangan yang ditunjukkan di sini .
Serangan itu
Jadi, mari kita mulai dengan menunjukkan serangan ...
Dalam keadaan tertentu, itu akan mengembalikan lebih dari 1 baris. Mari kita membedah apa yang terjadi di sini:
Memilih Set Karakter
Agar serangan ini dapat berfungsi, kita perlu pengkodean yang diharapkan server pada koneksi baik untuk disandikan
'
seperti dalam ASCII yaitu0x27
dan untuk memiliki beberapa karakter yang byte terakhirnya adalah ASCII\
yaitu0x5c
. Ternyata, ada 5 pengkodean tersebut didukung di MySQL 5.6 secara default:big5
,cp932
,gb2312
,gbk
dansjis
. Kami akan memilih digbk
sini.Sekarang, sangat penting untuk mencatat penggunaan di
SET NAMES
sini. Ini mengatur karakter yang diatur PADA SERVER . Jika kami menggunakan panggilan ke fungsi C APImysql_set_charset()
, kami akan baik-baik saja (pada rilis MySQL sejak 2006). Tetapi lebih lanjut tentang mengapa dalam satu menit ...Payload
Payload yang akan kita gunakan untuk injeksi ini dimulai dengan urutan byte
0xbf27
. Digbk
, itu adalah karakter multibyte yang tidak valid; dilatin1
, itu string¿'
. Perhatikan bahwa dalamlatin1
dangbk
,0x27
dengan sendirinya adalah'
karakter literal .Kami telah memilih payload ini karena, jika kami memanggilnya
addslashes()
, kami akan memasukkan ASCII\
yaitu0x5c
, sebelum'
karakter. Jadi kita akan berakhir dengan0xbf5c27
, yanggbk
merupakan urutan dua karakter:0xbf5c
diikuti oleh0x27
. Atau dengan kata lain, karakter yang valid diikuti oleh yang tidak terhindar'
. Tapi kami tidak menggunakanaddslashes()
. Jadi ke langkah selanjutnya ...mysql_real_escape_string ()
Panggilan C API
mysql_real_escape_string()
berbeda dariaddslashes()
yang ia tahu set karakter koneksi. Sehingga dapat melakukan pelolosan dengan benar untuk set karakter yang diharapkan oleh server. Namun, hingga saat ini, klien berpikir bahwa kami masih menggunakanlatin1
untuk koneksi, karena kami tidak pernah mengatakan sebaliknya. Kami memang memberi tahu server yang kami gunakangbk
, tetapi klien masih berpikir itulatin1
.Karenanya panggilan untuk
mysql_real_escape_string()
memasukkan backslash, dan kami memiliki'
karakter bebas menggantung di konten "lolos" kami! Bahkan, jika kita melihat$var
digbk
set karakter, kita akan melihat:Yang persis apa serangan itu membutuhkan.
The Query
Bagian ini hanya formalitas, tetapi inilah permintaan yang diberikan:
Selamat, Anda baru saja berhasil menyerang sebuah program menggunakan
mysql_real_escape_string()
...Keburukan
Itu semakin buruk.
PDO
default untuk meniru pernyataan yang disiapkan dengan MySQL. Itu berarti bahwa di sisi klien, pada dasarnya melakukan sprintf throughmysql_real_escape_string()
(di C library), yang berarti berikut ini akan menghasilkan suntikan yang sukses:Sekarang, perlu dicatat bahwa Anda dapat mencegahnya dengan menonaktifkan pernyataan yang disiapkan yang ditiru:
Ini biasanya akan menghasilkan pernyataan yang benar disiapkan (yaitu data yang dikirim dalam paket terpisah dari permintaan). Namun, perlu diketahui bahwa PDO akan diam-diam mundur untuk meniru pernyataan bahwa MySQL tidak dapat mempersiapkan secara asli: mereka yang dapat terdaftar dalam manual, tetapi berhati-hatilah untuk memilih versi server yang sesuai).
Jelek
Saya katakan di awal bahwa kita bisa mencegah semua ini jika kita menggunakan
mysql_set_charset('gbk')
alih-alihSET NAMES gbk
. Dan itu benar asalkan Anda menggunakan rilis MySQL sejak 2006.Jika Anda menggunakan rilis MySQL sebelumnya, maka bug di
mysql_real_escape_string()
berarti bahwa karakter multibyte yang tidak valid seperti yang ada di payload kami diperlakukan sebagai byte tunggal untuk melarikan diri bahkan jika klien telah diinformasikan dengan benar tentang pengkodean koneksi sehingga serangan ini akan masih berhasil. Bug diperbaiki di MySQL 4.1.20 , 5.0.22 dan 5.1.11 .Tetapi bagian terburuknya adalah bahwa
PDO
tidak mengekspos C API untukmysql_set_charset()
sampai 5.3.6, jadi dalam versi sebelumnya tidak dapat mencegah serangan ini untuk setiap perintah yang mungkin! Sekarang terbuka sebagai parameter DSN .Rahmat Yang Menyelamatkan
Seperti yang kami katakan di awal, untuk serangan ini berfungsi koneksi database harus dikodekan menggunakan set karakter yang rentan.
utf8mb4
adalah tidak rentan dan belum dapat mendukung setiap karakter Unicode: sehingga Anda bisa memilih untuk menggunakan bahwa alih-alih-tapi itu hanya tersedia sejak MySQL 5.5.3. Alternatifnya adalahutf8
, yang juga tidak rentan dan dapat mendukung keseluruhan Unicode Basic Multane Plane .Atau, Anda dapat mengaktifkan
NO_BACKSLASH_ESCAPES
mode SQL, yang (antara lain) mengubah operasimysql_real_escape_string()
. Dengan mode ini diaktifkan,0x27
akan diganti dengan0x2727
alih - alih0x5c27
dan karenanya proses melarikan diri tidak dapat membuat karakter yang valid di salah satu pengkodean rentan di mana mereka tidak ada sebelumnya (yaitu0xbf27
masih0xbf27
dll) - sehingga server masih akan menolak string sebagai tidak valid . Namun, lihat jawaban @ eggyal untuk kerentanan berbeda yang dapat timbul dari penggunaan mode SQL ini.Contoh Aman
Contoh-contoh berikut ini aman:
Karena server mengharapkan
utf8
...Karena kami telah mengatur set karakter dengan benar sehingga klien dan server cocok.
Karena kita telah mematikan pernyataan yang disiapkan yang ditiru.
Karena kita sudah mengatur set karakter dengan benar.
Karena MySQLi memang benar menyiapkan pernyataan sepanjang waktu.
Membungkus
Jika kamu:
mysql_set_charset()
/$mysqli->set_charset()
/ parameter charset DSN PDO (dalam PHP ≥ 5.3.6)ATAU
utf8
/latin1
/ascii
/ dll)Anda 100% aman.
Jika tidak, Anda rentan meskipun Anda menggunakan
mysql_real_escape_string()
...sumber
addslashes
. Saya mendasarkan kerentanan ini pada yang satu itu. Cobalah sendiri. Dapatkan MySQL 5.0, dan jalankan exploit ini dan lihat sendiri. Sejauh bagaimana memasukkannya ke dalam PUT / GET / POST, itu TRIVIAL. Input data hanyalah stream byte.char(0xBF)
hanyalah cara yang mudah dibaca untuk menghasilkan byte. Saya telah memperagakan kerentanan ini secara langsung di depan banyak konferensi. Percayalah pada saya ... Tapi jika tidak, coba sendiri.?var=%BF%27+OR+1=1+%2F%2A
di URL,$var = $_GET['var'];
dalam kode, dan Bob adalah paman Anda.Ini adalah satu lagi, (mungkin kurang?) KASUS EDGE !!!
Sebagai penghormatan atas jawaban luar biasa @ ircmaxell (sungguh, ini seharusnya pujian dan bukan plagiarisme!), Saya akan mengadopsi formatnya:
Serangan itu
Dimulai dengan demonstrasi ...
Ini akan mengembalikan semua catatan dari
test
tabel. Diseksi:Memilih Mode SQL
Seperti yang didokumentasikan dalam String Literals :
Jika mode SQL server termasuk
NO_BACKSLASH_ESCAPES
, maka ketiga opsi ini — yang merupakan pendekatan yang biasa digunakan olehmysql_real_escape_string()
— tidak tersedia: salah satu dari dua opsi pertama harus digunakan sebagai gantinya. Perhatikan bahwa efek dari peluru keempat adalah bahwa seseorang harus mengetahui karakter yang akan digunakan untuk mengutip literal untuk menghindari munging data seseorang.Payload
Payload memulai injeksi ini secara harfiah dengan
"
karakter. Tidak ada pengkodean tertentu. Tidak ada karakter khusus. Tidak ada byte yang aneh.mysql_real_escape_string ()
Untungnya,
mysql_real_escape_string()
tidak memeriksa mode SQL dan menyesuaikan perilakunya sesuai. Lihatlibmysql.c
:Jadi fungsi dasar yang berbeda
escape_quotes_for_mysql()
,, dipanggil jikaNO_BACKSLASH_ESCAPES
mode SQL sedang digunakan. Seperti disebutkan di atas, fungsi seperti itu perlu tahu karakter mana yang akan digunakan untuk mengutip literal untuk mengulanginya tanpa menyebabkan karakter kutipan lainnya tidak diulang secara harfiah.Namun, fungsi ini secara sewenang-wenang mengasumsikan bahwa string akan dikutip menggunakan
'
karakter kutipan tunggal . Lihatcharset.c
:Jadi, itu membuat
"
karakter kutipan ganda tidak tersentuh (dan menggandakan semua'
karakter kutipan tunggal ) terlepas dari karakter sebenarnya yang digunakan untuk mengutip literal ! Dalam kasus kami$var
tetap persis sama dengan argumen yang diberikan kepadamysql_real_escape_string()
-itu seolah-olah tidak ada melarikan diri telah terjadi sama sekali .The Query
Sesuatu yang formalitas, permintaan yang diberikan adalah:
Seperti kata teman terpelajar saya: selamat, Anda baru saja berhasil menyerang sebuah program menggunakan
mysql_real_escape_string()
...Keburukan
mysql_set_charset()
tidak dapat membantu, karena ini tidak ada hubungannya dengan set karakter; juga tidak bisamysqli::real_escape_string()
, karena itu hanya pembungkus yang berbeda di sekitar fungsi yang sama ini.Masalahnya, jika belum jelas, adalah bahwa panggilan untuk
mysql_real_escape_string()
tidak dapat mengetahui dengan karakter apa literal akan dikutip, karena itu diserahkan kepada pengembang untuk memutuskan di lain waktu. Jadi, dalamNO_BACKSLASH_ESCAPES
mode, secara harfiah tidak ada cara bahwa fungsi ini dapat dengan aman melarikan diri dari setiap input untuk digunakan dengan kuotasi sewenang-wenang (setidaknya, bukan tanpa menggandakan karakter yang tidak memerlukan penggandaan dan dengan demikian menghiasi data Anda).Jelek
Itu semakin buruk.
NO_BACKSLASH_ESCAPES
mungkin tidak semua yang biasa di alam liar karena perlunya penggunaannya untuk kompatibilitas dengan SQL standar (misalnya lihat bagian 5.3 dari spesifikasi SQL-92 , yaitu<quote symbol> ::= <quote><quote>
produksi tata bahasa dan kurangnya makna khusus yang diberikan kepada backslash). Selain itu, penggunaannya secara eksplisit direkomendasikan sebagai solusi untuk bug (lama diperbaiki) yang dijelaskan oleh ircmaxell. Siapa tahu, beberapa DBA bahkan mungkin mengkonfigurasinya untuk diaktifkan secara default sebagai cara mencegah penggunaan metode melarikan diri yang salah sepertiaddslashes()
.Juga, mode SQL dari koneksi baru diatur oleh server sesuai dengan konfigurasinya (yang
SUPER
dapat diubah pengguna kapan saja); dengan demikian, untuk memastikan perilaku server, Anda harus selalu secara eksplisit menentukan mode yang Anda inginkan setelah tersambung.Rahmat Yang Menyelamatkan
Selama Anda selalu secara eksplisit mengatur mode SQL untuk tidak memasukkan
NO_BACKSLASH_ESCAPES
, atau mengutip literal string MySQL menggunakan karakter kutipan tunggal, bug ini tidak dapat memundurkan kepala jeleknya: masingescape_quotes_for_mysql()
- masing tidak akan digunakan, atau asumsi tentang karakter kutipan mana yang perlu diulang akan benar.Untuk alasan ini, saya menyarankan agar siapa pun yang menggunakan
NO_BACKSLASH_ESCAPES
juga mengaktifkanANSI_QUOTES
mode, karena itu akan memaksa penggunaan kebiasaan string literal yang dikutip tunggal. Perhatikan bahwa ini tidak mencegah injeksi SQL jika literal yang dikutip ganda kebetulan digunakan - itu hanya mengurangi kemungkinan itu terjadi (karena permintaan normal, non-berbahaya akan gagal).Dalam PDO, baik fungsi ekivalennya
PDO::quote()
maupun pernyataan emulator yang disiapkan memanggilmysql_handle_quoter()
— yang melakukan hal ini: memastikan literal yang lolos dikutip dalam tanda kutip tunggal, sehingga Anda dapat yakin bahwa PDO selalu kebal dari bug ini.Pada MySQL v5.7.6, bug ini telah diperbaiki. Lihat ubah log :
Contoh Aman
Diambil bersama dengan bug yang dijelaskan oleh ircmaxell, contoh-contoh berikut ini sepenuhnya aman (dengan asumsi bahwa seseorang menggunakan MySQL lebih dari 4.1.20, 5.0.22, 5.1.11; atau seseorang tidak menggunakan pengkodean koneksi GBK / Big5) :
... karena kami telah secara eksplisit memilih mode SQL yang tidak termasuk
NO_BACKSLASH_ESCAPES
.... karena kami mengutip string literal kami dengan tanda kutip tunggal.
... karena pernyataan yang disiapkan PDO kebal dari kerentanan ini (dan ircmaxell juga, asalkan Anda menggunakan PHP≥5.3.6 dan rangkaian karakter telah ditetapkan dengan benar di DSN; atau emulasi pernyataan yang disiapkan telah dinonaktifkan) .
... karena
quote()
fungsi PDO tidak hanya lolos dari literal, tetapi juga mengutipnya (dalam'
karakter tanda kutip tunggal ); perhatikan bahwa untuk menghindari bug ircmaxell dalam kasus ini, Anda harus menggunakan PHP≥5.3.6 dan telah menetapkan set karakter dengan benar di DSN.... karena pernyataan yang disiapkan MySQLi aman.
Membungkus
Jadi, jika Anda:
ATAU
ATAU
di samping untuk menggunakan salah satu solusi dalam ringkasan ircmaxell ini, penggunaan setidaknya salah satu dari:
NO_BACKSLASH_ESCAPES
... maka Anda harus benar-benar aman (kerentanan di luar lingkup string yang keluar).
sumber
"
string terlebih dahulu. SQL mengatakan itu untuk pengidentifikasi. Tapi eh ... hanyalah contoh lain dari MySQL yang mengatakan "standar sekrup, saya akan melakukan apapun yang saya inginkan". (Untungnya, Anda dapat memasukkanANSI_QUOTES
dalam mode untuk memperbaiki kehancuran kutipan. Pengabaian standar yang terbuka, bagaimanapun, adalah masalah yang lebih besar yang mungkin memerlukan langkah-langkah yang lebih parah.)quote()
fungsi PDO — tetapi pernyataan yang disiapkan adalah cara yang jauh lebih aman dan lebih tepat untuk menghindari injeksi secara umum. Tentu saja, jika Anda secara langsung menggabungkan variabel yang tidak terhapus ke dalam SQL Anda, maka Anda tentu saja rentan terhadap injeksi, apa pun metode yang Anda gunakan sesudahnya.Yah, tidak ada yang benar-benar bisa melewati itu, selain
%
wildcard. Ini bisa berbahaya jika Anda menggunakanLIKE
pernyataan sebagai penyerang bisa memasukkan%
sebagai login jika Anda tidak memfilter itu, dan harus hanya memaksa kata sandi dari salah satu pengguna Anda. Orang sering menyarankan menggunakan pernyataan yang disiapkan untuk membuatnya 100% aman, karena data tidak dapat mengganggu permintaan itu sendiri. Tetapi untuk pertanyaan sederhana seperti itu mungkin akan lebih efisien untuk melakukan sesuatu seperti$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);
sumber
more efficient
daripada menggunakan pernyataan yang disiapkan? (Pernyataan yang disiapkan selalu berfungsi, pustaka dapat dengan cepat dikoreksi jika terjadi serangan, tidak mengekspos kesalahan manusia [seperti salah ketik mengetik string pengganti lengkap], dan memiliki manfaat kinerja yang signifikan jika pernyataan tersebut digunakan kembali.)