Katakanlah saya memiliki kode seperti ini:
$dbh = new PDO("blahblah");
$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );
Dokumentasi PDO mengatakan:
Parameter untuk pernyataan yang disiapkan tidak perlu dikutip; pengemudi menanganinya untuk Anda.
Apakah hanya itu yang harus saya lakukan untuk menghindari suntikan SQL? apa benar semudah itu?
Anda dapat mengasumsikan MySQL jika ada bedanya. Juga, saya benar-benar hanya ingin tahu tentang penggunaan pernyataan yang disiapkan terhadap injeksi SQL. Dalam konteks ini, saya tidak peduli dengan XSS atau kemungkinan kerentanan lainnya.
php
security
pdo
sql-injection
Mark Biek
sumber
sumber
Jawaban:
Jawaban singkatnya adalah TIDAK , PDO mempersiapkan tidak akan membela Anda dari semua kemungkinan serangan SQL-Injection. Untuk kasus tepi tertentu yang tidak jelas.
Saya mengadaptasi jawaban ini untuk berbicara tentang PDO ...
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 . Ada cara lain untuk melakukannya, tetapi kita akan segera sampai di sana.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 ...$ stmt-> execute ()
Yang penting untuk disadari di sini adalah bahwa PDO secara default TIDAK melakukan pernyataan yang benar. Ini mengemulasi mereka (untuk MySQL). Oleh karena itu, PDO secara internal membangun string kueri, memanggil
mysql_real_escape_string()
(fungsi MySQL C API) pada setiap nilai string terikat.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:Persis seperti itulah yang dibutuhkan serangan itu.
The Query
Bagian ini hanya formalitas, tetapi inilah permintaan yang diberikan:
Selamat, Anda baru saja berhasil menyerang sebuah program menggunakan Pernyataan Disiapkan PDO ...
Perbaikan Sederhana
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).
Perbaikan yang Benar
Masalahnya di sini adalah bahwa kami tidak memanggil API C
mysql_set_charset()
sebagai gantinyaSET NAMES
. Jika kami melakukannya, kami akan baik-baik saja asalkan kami 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 , yang harus digunakan alih-alihSET NAMES
...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 (meskipun tidak dengan PDO).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:
ATAU
utf8
/latin1
/ascii
/ dll)ATAU
NO_BACKSLASH_ESCAPES
mode SQLAnda 100% aman.
Jika tidak, Anda rentan meskipun Anda menggunakan Pernyataan Disiapkan PDO ...
Tambahan
Saya perlahan-lahan mengerjakan tambalan untuk mengubah default agar tidak meniru persiapan untuk versi PHP yang akan datang. Masalah yang saya hadapi adalah BANYAK tes pecah ketika saya melakukan itu. Satu masalah adalah bahwa prepared yang diemulasi hanya akan melempar kesalahan sintaks pada eksekusi, tetapi true prepares akan melempar kesalahan pada preparasi. Sehingga bisa menimbulkan masalah (dan merupakan bagian dari alasan tes borking).
sumber
mysql_real_escape_string
tidak akan menangani kasus dengan benar di mana koneksi diatur dengan benar ke BIG5 / GBK. Jadi sebenarnya bahkan memanggilmysql_set_charset()
mysql <5.0.22 akan rentan terhadap bug ini! Jadi tidak, postingan ini masih berlaku untuk 5.0.22 (karena mysql_real_escape_string hanya charset untuk panggilan darimysql_set_charset()
, yang mana posting ini berbicara tentangNO_BACKSLASH_ESCAPES
juga dapat memperkenalkan kerentanan baru: stackoverflow.com/a/23277864/1014813Pernyataan yang disiapkan / pertanyaan parameter umumnya cukup untuk mencegah injeksi urutan pertama pada pernyataan itu * . Jika Anda menggunakan sql dinamis yang tidak dicentang di tempat lain di aplikasi Anda, Anda masih rentan terhadap injeksi pesanan kedua .
Injeksi urutan 2 berarti data telah disikluskan melalui basis data satu kali sebelum dimasukkan dalam kueri, dan jauh lebih sulit untuk dilakukan. AFAIK, Anda hampir tidak pernah melihat serangan urutan ke-2 yang direkayasa nyata, karena biasanya penyerang lebih mudah untuk merekayasa sosial dalam perjalanan mereka, tetapi kadang-kadang Anda memiliki bug urutan ke-2 muncul karena
'
karakter yang lebih jinak atau serupa.Anda bisa melakukan serangan injeksi urutan ke-2 saat Anda dapat menyebabkan nilai disimpan dalam database yang kemudian digunakan sebagai literal dalam kueri. Sebagai contoh, katakanlah Anda memasukkan informasi berikut sebagai nama pengguna baru saat membuat akun di situs web (dengan asumsi MySQL DB untuk pertanyaan ini):
Jika tidak ada batasan lain pada nama pengguna, pernyataan yang disiapkan masih akan memastikan bahwa kueri tertanam di atas tidak dijalankan pada saat memasukkan, dan menyimpan nilai dengan benar dalam database. Namun, bayangkan bahwa nanti aplikasi mengambil nama pengguna Anda dari database, dan menggunakan penggabungan string untuk memasukkan nilai itu ke kueri baru. Anda mungkin bisa melihat kata sandi orang lain. Karena beberapa nama pertama dalam tabel pengguna cenderung merupakan admin, Anda mungkin juga baru saja memberikan farm. (Perhatikan juga: ini adalah satu lagi alasan untuk tidak menyimpan kata sandi dalam teks biasa!)
Kami melihat, kemudian, bahwa pernyataan yang disiapkan cukup untuk satu permintaan, tetapi dengan sendirinya pernyataan tersebut tidak cukup untuk melindungi terhadap serangan injeksi sql di seluruh aplikasi, karena mereka tidak memiliki mekanisme untuk menegakkan semua akses ke database dalam aplikasi yang menggunakan safe kode. Namun, digunakan sebagai bagian dari desain aplikasi yang baik - yang dapat mencakup praktik-praktik seperti tinjauan kode atau analisis statis, atau penggunaan ORM, lapisan data, atau lapisan layanan yang membatasi pernyataan sql dinamis yang disiapkan adalah alat utama untuk menyelesaikan Sql Injection masalah.Jika Anda mengikuti prinsip-prinsip desain aplikasi yang baik, sehingga akses data Anda dipisahkan dari sisa program Anda, menjadi mudah untuk menegakkan atau mengaudit bahwa setiap kueri dengan benar menggunakan parameterisasi. Dalam hal ini, injeksi sql (urutan pertama dan kedua) sepenuhnya dicegah.
* Ternyata MySql / PHP (oke, dulunya) hanya bodoh tentang penanganan parameter ketika karakter lebar terlibat, dan masih ada kasus yang jarang dijabarkan dalam jawaban pilihan tinggi lainnya di sini yang dapat memungkinkan injeksi untuk melewati parameter pertanyaan.
sumber
Tidak, mereka tidak selalu.
Itu tergantung pada apakah Anda mengizinkan input pengguna ditempatkan di dalam kueri itu sendiri. Sebagai contoh:
akan rentan terhadap injeksi SQL dan menggunakan pernyataan yang disiapkan dalam contoh ini tidak akan berfungsi, karena input pengguna digunakan sebagai pengidentifikasi, bukan sebagai data. Jawaban yang tepat di sini adalah dengan menggunakan semacam penyaringan / validasi seperti:
Catatan: Anda tidak dapat menggunakan PDO untuk mengikat data yang keluar dari DDL (Data Definition Language), artinya ini tidak berfungsi:
Alasan mengapa hal di atas tidak berhasil adalah karena
DESC
danASC
bukan data . PDO hanya dapat melarikan diri untuk data . Kedua, Anda bahkan tidak bisa memberi'
tanda kutip di sekitarnya. Satu-satunya cara untuk memungkinkan pemilahan yang dipilih pengguna adalah dengan memfilter secara manual dan memeriksa apakah adaDESC
atau tidakASC
.sumber
SELECT * FROM 'table'
bisa salah seperti seharusnyaSELECT * FROM `table`
atau tanpa backsticks. Maka beberapa hal seperti dariORDER BY DESC
manaDESC
datangnya pengguna tidak bisa diloloskan begitu saja. Jadi, skenario praktis agak tidak terbatas.switch
untuk mendapatkan nama tabel.Ya, itu sudah cukup. Cara serangan tipe injeksi bekerja, adalah dengan cara mendapatkan penerjemah (Database) untuk mengevaluasi sesuatu, yang seharusnya berupa data, seolah-olah itu adalah kode. Ini hanya mungkin jika Anda mencampur kode dan data dalam media yang sama (mis. Saat Anda membuat kueri sebagai string).
Kueri yang diparameterisasi bekerja dengan mengirimkan kode dan data secara terpisah, sehingga tidak akan mungkin menemukan lubang di dalamnya .
Anda masih bisa rentan terhadap serangan tipe injeksi lainnya. Misalnya, jika Anda menggunakan data dalam halaman HTML, Anda bisa terkena serangan tipe XSS.
sumber
Tidak, ini tidak cukup (dalam beberapa kasus tertentu)! Secara default PDO menggunakan pernyataan disiapkan emulasi ketika menggunakan MySQL sebagai driver database. Anda harus selalu menonaktifkan pernyataan disiapkan emulasi saat menggunakan MySQL dan PDO:
Hal lain yang selalu harus dilakukan adalah mengatur pengkodean database yang benar:
Lihat juga pertanyaan terkait ini: Bagaimana saya bisa mencegah injeksi SQL dalam PHP?
Perhatikan juga bahwa hanya tentang sisi database dari hal-hal yang masih harus Anda perhatikan sendiri saat menampilkan data. Misalnya dengan menggunakan
htmlspecialchars()
lagi dengan gaya pengkodean dan kutipan yang benar.sumber
Secara pribadi saya akan selalu menjalankan beberapa bentuk sanitasi pada data terlebih dahulu karena Anda tidak pernah bisa mempercayai input pengguna, namun ketika menggunakan placeholder / parameter yang mengikat data yang diinput dikirim ke server secara terpisah ke pernyataan sql dan kemudian diikat bersama. Kuncinya di sini adalah bahwa ini mengikat data yang diberikan ke tipe tertentu dan penggunaan tertentu dan menghilangkan peluang untuk mengubah logika pernyataan SQL.
sumber
Eaven jika Anda akan mencegah front-end sql injection, menggunakan html atau js, Anda harus mempertimbangkan bahwa pemeriksaan front-end "bypassable".
Anda dapat menonaktifkan js atau mengedit pola dengan alat pengembangan front-end (built-in dengan firefox atau chrome saat ini).
Jadi, untuk mencegah injeksi SQL, akan menjadi hak untuk membersihkan backend tanggal input di dalam controller Anda.
Saya ingin menyarankan Anda untuk menggunakan fungsi PHP asli filter_input () untuk membersihkan nilai GET dan INPUT.
Jika Anda ingin melanjutkan keamanan, untuk permintaan basis data yang masuk akal, saya ingin menyarankan Anda untuk menggunakan ekspresi reguler untuk memvalidasi format data. preg_match () akan membantu Anda dalam hal ini! Tapi hati-hati! Mesin regex tidak begitu ringan. Gunakan hanya jika perlu, jika tidak, kinerja aplikasi Anda akan berkurang.
Keamanan memiliki biaya, tetapi jangan sia-siakan kinerja Anda!
Contoh mudah:
jika Anda ingin memeriksa ulang apakah suatu nilai, yang diterima dari GET adalah angka, kurang dari 99 jika (! preg_match ('/ [0-9] {1,2} /')) {...} lebih berat dari
Jadi, jawaban akhirnya adalah: "Tidak! Pernyataan Disiapkan PDO tidak mencegah semua jenis injeksi sql"; Itu tidak mencegah nilai-nilai tak terduga, hanya gabungan tak terduga
sumber