Mysql
 sql >> Teknologi Basis Data >  >> RDS >> Mysql

Injeksi SQL yang mengatasi mysql_real_escape_string()

Jawaban singkatnya adalah ya, ya, ada cara untuk menyiasati mysql_real_escape_string() .#Untuk KASUS EDGE yang Sangat samar!!!

Jawaban panjangnya tidak begitu mudah. Ini didasarkan pada serangan ditunjukkan di sini .

Serangan

Jadi, mari kita mulai dengan menunjukkan serangannya...

mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Dalam keadaan tertentu, itu akan mengembalikan lebih dari 1 baris. Mari kita bedah apa yang terjadi di sini:

  1. Memilih Kumpulan Karakter

    mysql_query('SET NAMES gbk');
    

    Agar serangan ini berfungsi, kita memerlukan penyandian yang diharapkan server pada sambungan untuk menyandikan ' seperti dalam ASCII yaitu 0x27 dan untuk memiliki beberapa karakter yang byte terakhirnya adalah \ ASCII yaitu 0x5c . Ternyata, ada 5 pengkodean yang didukung di MySQL 5.6 secara default:big5 , cp932 , gb2312 , gbk dan sjis . Kami akan memilih gbk di sini.

    Sekarang, sangat penting untuk memperhatikan penggunaan SET NAMES di sini. Ini menetapkan set karakter ON THE SERVER . Jika kita menggunakan panggilan ke fungsi C API mysql_set_charset() , kami akan baik-baik saja (pada rilis MySQL sejak 2006). Tapi lebih lanjut tentang mengapa dalam satu menit...

  2. Muatannya

    Payload yang akan kita gunakan untuk injeksi ini dimulai dengan urutan byte 0xbf27 . Di gbk , itu adalah karakter multibyte yang tidak valid; dalam latin1 , ini adalah string ¿' . Perhatikan bahwa dalam latin1 dan gbk , 0x27 sendiri adalah ' liter literal karakter.

    Kami memilih payload ini karena, jika kami memanggil addslashes() di atasnya, kami akan memasukkan \ ASCII yaitu 0x5c , sebelum ' karakter. Jadi kita akan berakhir dengan 0xbf5c27 , yang di gbk adalah urutan dua karakter:0xbf5c diikuti oleh 0x27 . Atau dengan kata lain, valid karakter diikuti oleh ' . yang tidak terhapus . Tapi kami tidak menggunakan addslashes() . Jadi ke langkah berikutnya...

  3. mysql_real_escape_string()

    Panggilan API C ke mysql_real_escape_string() berbeda dari addslashes() karena ia mengetahui set karakter koneksi. Sehingga dapat melakukan pelolosan dengan benar untuk set karakter yang diharapkan server. Namun, hingga saat ini, klien menganggap kami masih menggunakan latin1 untuk koneksi, karena kami tidak pernah mengatakan sebaliknya. Kami memang memberi tahu server kami menggunakan gbk , tetapi klien masih berpikir itu latin1 .

    Oleh karena itu panggilan ke mysql_real_escape_string() menyisipkan garis miring terbalik, dan kami memiliki ' . gantung gratis karakter dalam konten "lolos" kami! Faktanya, jika kita melihat $var di gbk set karakter, kita akan melihat:

    縗' OR 1=1 /*

    Yang mana persis apa serangan itu membutuhkan.

  4. Permintaan

    Bagian ini hanya formalitas, tetapi inilah kueri yang diberikan:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
    

Selamat, Anda baru saja berhasil menyerang sebuah program menggunakan mysql_real_escape_string() ...

Yang Buruk

Ini menjadi lebih buruk. PDO default untuk meniru pernyataan yang disiapkan dengan MySQL. Itu berarti bahwa di sisi klien, pada dasarnya melakukan sprintf melalui mysql_real_escape_string() (di pustaka C), yang berarti berikut ini akan menghasilkan injeksi yang berhasil:

$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Sekarang, perlu diperhatikan bahwa Anda dapat mencegahnya dengan menonaktifkan pernyataan yang telah disiapkan yang ditiru:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Ini akan biasanya menghasilkan pernyataan yang disiapkan dengan benar (yaitu data yang dikirim dalam paket terpisah dari kueri). Namun, perlu diketahui bahwa PDO akan diam-diam fallback untuk meniru pernyataan yang tidak dapat disiapkan MySQL secara asli:pernyataan yang dapat dibuat adalah terdaftar di manual, tetapi berhati-hatilah untuk memilih versi server yang sesuai).

Si Jelek

Saya katakan di awal bahwa kita bisa mencegah semua ini jika kita menggunakan mysql_set_charset('gbk') bukannya SET 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 menghindari tujuan bahkan jika klien telah diberi tahu dengan benar tentang penyandian koneksi dan serangan ini masih akan berhasil. Bug telah diperbaiki di MySQL 4.1.20 , 5.0.22 dan 5.1.11 .

Tapi bagian terburuknya adalah PDO tidak mengekspos C API untuk mysql_set_charset() sampai 5.3.6, jadi di versi sebelumnya tidak bisa mencegah serangan ini untuk setiap perintah yang mungkin! Sekarang ditampilkan sebagai Parameter DSN .

Kasih Karunia yang Menyelamatkan

Seperti yang kami katakan di awal, agar serangan ini berfungsi, koneksi database harus dikodekan menggunakan rangkaian karakter yang rentan. utf8mb4 tidak rentan namun dapat mendukung setiap Karakter unicode:jadi Anda bisa memilih untuk menggunakannya—tetapi itu hanya tersedia sejak MySQL 5.5.3. Alternatifnya adalah utf8 , yang juga tidak rentan dan dapat mendukung seluruh Unicode Pesawat Multibahasa Dasar .

Atau, Anda dapat mengaktifkan NO_BACKSLASH_ESCAPES Mode SQL, yang (antara lain) mengubah operasi mysql_real_escape_string() . Dengan mengaktifkan mode ini, 0x27 akan diganti dengan 0x2727 daripada 0x5c27 dan dengan demikian proses meloloskan diri tidak bisa buat karakter yang valid di salah satu penyandian rentan yang sebelumnya tidak ada (yaitu 0xbf27 masih 0xbf27 dll.)—sehingga server akan tetap menolak string sebagai tidak valid. Namun, lihat jawaban @eggyal untuk kerentanan berbeda yang dapat muncul dari penggunaan mode SQL ini.

Contoh Aman

Contoh berikut aman:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Karena server mengharapkan utf8 ...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Karena kami telah mengatur set karakter dengan benar sehingga klien dan server cocok.

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Karena kami telah menonaktifkan pernyataan siap yang ditiru.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Karena kami telah mengatur set karakter dengan benar.

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

Karena MySQLi selalu melakukan pernyataan yang disiapkan dengan benar.

Menutup

Jika Anda:

  • Gunakan Versi Modern MySQL (akhir 5.1, semua 5.5, 5.6, dll) DAN mysql_set_charset() / $mysqli->set_charset() / Parameter rangkaian karakter DSN PDO (dalam PHP 5.3.6)

ATAU

  • Jangan gunakan rangkaian karakter yang rentan untuk penyandian koneksi (Anda hanya menggunakan utf8 / latin1 / ascii / dll)

Anda 100% aman.

Jika tidak, Anda rentan meskipun Anda menggunakan mysql_real_escape_string() ...



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bagaimana saya bisa menggunakan Variabel Buatan Pengguna MySql di .NET MySqlCommand?

  2. Bagaimana cara mengetahui apakah MySQLnd adalah driver aktif?

  3. Tingkatkan MySQL ke MariaDB 10 (Bagian 1 – Instal MariaDB 5.5)

  4. Cara terbaik menyimpan informasi pengguna dan login pengguna dan kata sandi

  5. Bagaimana cara mengisi lubang di bidang kenaikan otomatis?