Bagaimana pernyataan yang disiapkan dapat melindungi dari serangan injeksi SQL?

172

Bagaimana pernyataan yang disiapkan membantu kita mencegah serangan injeksi SQL ?

Wikipedia mengatakan:

Pernyataan yang disiapkan tahan terhadap injeksi SQL, karena nilai parameter, yang dikirim kemudian menggunakan protokol yang berbeda, tidak perlu melarikan diri dengan benar. Jika templat pernyataan asli tidak berasal dari input eksternal, injeksi SQL tidak dapat terjadi.

Saya tidak bisa melihat alasannya dengan baik. Apa penjelasan sederhana dalam bahasa Inggris yang mudah dan beberapa contoh?

Aan
sumber

Jawaban:

290

Idenya sangat sederhana - permintaan dan data dikirim ke server database secara terpisah .
Itu saja.

Akar masalah injeksi SQL adalah dalam pencampuran kode dan data.

Faktanya, permintaan SQL kami adalah program yang sah . Dan kami membuat program semacam itu secara dinamis, menambahkan beberapa data dengan cepat. Dengan demikian, data dapat mengganggu kode program dan bahkan mengubahnya, karena setiap contoh injeksi SQL menunjukkannya (semua contoh dalam PHP / Mysql):

$expected_data = 1;
$query = "SELECT * FROM users where id=$expected_data";

akan menghasilkan permintaan reguler

SELECT * FROM users where id=1

sementara kode ini

$spoiled_data = "1; DROP TABLE users;"
$query        = "SELECT * FROM users where id=$spoiled_data";

akan menghasilkan urutan berbahaya

SELECT * FROM users where id=1; DROP TABLE users;

Ini berfungsi karena kita menambahkan data secara langsung ke badan program dan itu menjadi bagian dari program, sehingga data dapat mengubah program, dan tergantung pada data yang dikirimkan, kita akan memiliki output reguler atau tabel usersdihapus.

Sementara dalam hal pernyataan yang disiapkan kami tidak mengubah program kami, itu tetap utuh
Itulah intinya.

Kami mengirim program ke server terlebih dahulu

$db->prepare("SELECT * FROM users where id=?");

di mana data diganti oleh beberapa variabel yang disebut parameter atau placeholder.

Perhatikan bahwa permintaan yang sama persis dikirimkan ke server, tanpa ada data di dalamnya! Dan kemudian kami mengirimkan data dengan permintaan kedua , yang pada dasarnya dipisahkan dari permintaan itu sendiri:

$db->execute($data);

jadi itu tidak dapat mengubah program kami dan membahayakan.
Cukup sederhana - bukan?

Satu-satunya hal yang harus saya tambahkan adalah selalu dihilangkan dalam setiap manual:

Pernyataan yang disiapkan hanya dapat melindungi literal data , tetapi tidak dapat digunakan dengan bagian kueri lainnya.
Jadi, begitu kita harus menambahkan, katakanlah, pengidentifikasi dinamis - nama bidang, misalnya - pernyataan yang disiapkan tidak dapat membantu kita. Saya sudah menjelaskan masalah ini baru-baru ini , jadi saya tidak akan mengulangi lagi.

Akal Sehat Anda
sumber
2
"misalnya, secara default PDO tidak menggunakan pernyataan yang disiapkan" - itu tidak sepenuhnya benar, karena PDO meniru pernyataan yang disiapkan hanya untuk driver yang tidak mendukung fitur tersebut.
pinepain
3
@ zaq178miami: "PDO mengemulasi pernyataan yang disiapkan hanya untuk driver yang tidak mendukung fitur" - tidak sepenuhnya benar. MySQL telah mendukung pernyataan yang disiapkan untuk beberapa waktu sekarang. Pengemudi PDO juga. Namun, permintaan MySQL masih disiapkan oleh PDO secara default, terakhir kali saya memeriksa.
cHao
9
Apa yang berbeda antara $spoiled_data = "1; DROP TABLE users;"-> $query = "SELECT * FROM users where id=$spoiled_data";, dibandingkan dengan: $db->prepare("SELECT * FROM users where id=?");-> $data = "1; DROP TABLE users;"-> $db->execute($data);. Bukankah mereka akan melakukan hal yang sama?
Juha Untinen
14
@Juha Untinen Data bisa berupa apa saja. Itu tidak akan mem-parsing data. Itu DATA bukan perintahnya. Jadi, bahkan jika $ data berisi perintah sql, itu tidak akan dieksekusi. Juga, jika id adalah angka, maka konten string akan menghasilkan laporan atau nilai nol.
Soley
21

Berikut ini SQL untuk menyiapkan contoh:

CREATE TABLE employee(name varchar, paymentType varchar, amount bigint);

INSERT INTO employee VALUES('Aaron', 'salary', 100);
INSERT INTO employee VALUES('Aaron', 'bonus', 50);
INSERT INTO employee VALUES('Bob', 'salary', 50);
INSERT INTO employee VALUES('Bob', 'bonus', 0);

Kelas Inject rentan terhadap injeksi SQL. Kueri ditempelkan secara dinamis bersama dengan input pengguna. Maksud kueri adalah untuk menunjukkan informasi tentang Bob. Baik gaji atau bonus, berdasarkan input pengguna. Tetapi pengguna jahat memanipulasi input yang merusak kueri dengan menempel pada persamaan 'atau benar' dengan klausa where sehingga semuanya dikembalikan, termasuk informasi tentang Aaron yang seharusnya disembunyikan.

import java.sql.*;

public class Inject {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=user&password=pwd";
        Connection conn = DriverManager.getConnection(url);

        Statement stmt = conn.createStatement();
        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='" + args[0] + "'";
        System.out.println(sql);
        ResultSet rs = stmt.executeQuery(sql);

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

Menjalankan ini, kasus pertama adalah dengan penggunaan normal, dan yang kedua dengan suntikan berbahaya:

c:\temp>java Inject salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary'
salary 50

c:\temp>java Inject "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary' OR 'a'!='b'
salary 100
bonus 50
salary 50
bonus 0

Anda seharusnya tidak membangun pernyataan SQL Anda dengan rangkaian input pengguna. Tidak hanya itu rentan terhadap injeksi, tetapi memiliki implikasi caching pada server juga (pernyataan berubah, jadi lebih kecil kemungkinannya untuk mendapatkan cache pernyataan SQL hit sedangkan contoh bind selalu menjalankan pernyataan yang sama).

Berikut adalah contoh Binding untuk menghindari injeksi semacam ini:

import java.sql.*;

public class Bind {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=postgres&password=postgres";
        Connection conn = DriverManager.getConnection(url);

        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?";
        System.out.println(sql);

        PreparedStatement stmt = conn.prepareStatement(sql);
        stmt.setString(1, args[0]);

        ResultSet rs = stmt.executeQuery();

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

Menjalankan ini dengan input yang sama seperti contoh sebelumnya menunjukkan kode berbahaya tidak berfungsi karena tidak ada jenis pembayaran yang cocok dengan string itu:

c:\temp>java Bind salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
salary 50

c:\temp>java Bind "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
Glenn
sumber
Apakah menggunakan pernyataan yang disiapkan dari program yang terhubung ke database memiliki efek yang sama dengan menggunakan pernyataan yang disiapkan yang merupakan bagian dari db? Sebagai contoh, Postgres memiliki pernyataan yang disiapkan sendiri dan apakah menggunakannya mencegah injeksi SQL? postgresql.org/docs/9.2/static/sql-prepare.html
Celeritas
@Celeritas Saya tidak punya jawaban pasti untuk Postgresql. Melihat dokumen, tampaknya efeknya sama. PREPAREmembuat pernyataan bernama tetap yang sudah diuraikan (yaitu pernyataan tidak akan berubah lagi terlepas dari input) sementara EXECUTEakan menjalankan pernyataan bernama mengikat parameter. Karena PREPAREhanya memiliki durasi sesi, sepertinya memang dimaksudkan untuk alasan kinerja, bukan untuk mencegah injeksi melalui skrip psql. Untuk akses psql, bisa memberikan izin ke prosedur tersimpan dan mengikat parameter dalam procs.
Glenn
@Celeritas Saya mencoba kode di atas menggunakan PostgreSQL 11.1 pada x86_64 dan contoh SQLi di atas berhasil.
Krishna Pandey
15

Pada dasarnya, dengan pernyataan yang sudah disiapkan, data yang masuk dari peretas potensial diperlakukan sebagai data - dan tidak mungkin dicampur dengan SQL aplikasi Anda dan / atau ditafsirkan sebagai SQL (yang dapat terjadi ketika data yang disalurkan ditempatkan langsung ke dalam Anda aplikasi SQL).

Ini karena pernyataan yang disiapkan "menyiapkan" kueri SQL terlebih dahulu untuk menemukan rencana kueri yang efisien, dan mengirimkan nilai aktual yang mungkin datang dari formulir nanti - pada saat itu kueri sebenarnya dieksekusi.

Info hebat lainnya di sini:

Pernyataan yang disiapkan dan Injeksi SQL

Jose
sumber
6

Saya membaca jawaban dan masih merasa perlu menekankan poin kunci yang menerangi esensi Pernyataan Disiapkan. Pertimbangkan dua cara untuk menanyakan basis data seseorang di mana input pengguna terlibat:

Pendekatan Naif

Satu menggabungkan input pengguna dengan beberapa string SQL parsial untuk menghasilkan pernyataan SQL. Dalam hal ini pengguna dapat menanamkan perintah SQL berbahaya, yang kemudian akan dikirim ke database untuk dieksekusi.

String SQLString = "SELECT * FROM CUSTOMERS WHERE NAME='"+userInput+"'"

Misalnya, input pengguna jahat dapat menyebabkan SQLStringsama dengan"SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

Karena pengguna jahat, SQLStringberisi 2 pernyataan, di mana pernyataan ke-2 ( "DROP TABLE CUSTOMERS") akan membahayakan.

Pernyataan Disiapkan

Dalam hal ini, karena pemisahan kueri & data, input pengguna tidak pernah diperlakukan sebagai pernyataan SQL, dan karenanya tidak pernah dieksekusi . Karena alasan inilah, setiap kode SQL berbahaya yang disuntikkan tidak akan membahayakan. Jadi "DROP TABLE CUSTOMERS"tidak akan pernah dieksekusi dalam kasus di atas.

Singkatnya, dengan pernyataan yang dipersiapkan kode berbahaya yang diperkenalkan melalui input pengguna tidak akan dieksekusi!

N.Vegeta
sumber
Betulkah? Jawaban yang diterima tidak mengatakan hal itu?
Your Common Sense
@Rasa Akal Sehat Anda Jawaban yang diterima diisi dengan banyak informasi berharga tetapi itu membuat saya bertanya-tanya seperti apa detail implementasi dari pemisahan data & kueri. Sedangkan fokus pada titik bahwa data yang disuntikkan jahat (jika ada) tidak akan pernah dieksekusi memukul paku di kepala.
N.Vegeta
Dan "detail implementasi" mana yang disediakan dalam jawaban Anda yang tidak ada di sana?
Akal Sehat Anda
jika Anda mencoba melihat dari mana saya berasal, Anda akan menyadari bahwa poin saya adalah sebagai berikut: Keinginan singkat untuk melihat detail implementasi berasal dari kebutuhan untuk memahami alasan eksplisit mengapa input pengguna jahat tidak akan menyebabkan apa pun. membahayakan. Tidak perlu melihat detail implementasi. Itulah sebabnya menyadari bahwa detail implementasi sedemikian rupa sehingga, tidak ada titik jahat akan memasukkan SQL dieksekusi, mengirim pesan ke rumah. Respons Anda menjawab pertanyaan, bagaimana (seperti yang diminta) ?, tetapi saya membayangkan orang lain (seperti saya) akan puas dengan jawaban yang ringkas mengapa?
N.Vegeta
Anggap ini pengayaan yang menjelaskan inti masalahnya, dan bukan sebagai kritik tersirat (baru menyadari siapa penulis tanggapan yang diterima).
N.Vegeta
5

Saat Anda membuat dan mengirim pernyataan yang disiapkan ke DBMS, itu disimpan sebagai query SQL untuk dieksekusi.

Anda kemudian mengikat data Anda ke kueri sehingga DBMS menggunakan data itu sebagai parameter kueri untuk eksekusi (parameterisasi). DBMS tidak menggunakan data yang Anda ikat sebagai tambahan untuk permintaan SQL yang sudah dikompilasi; itu hanya data.

Ini berarti pada dasarnya tidak mungkin untuk melakukan injeksi SQL menggunakan pernyataan yang disiapkan. Sifat pernyataan yang disiapkan dan hubungannya dengan DBMS mencegah hal ini.

wulfgarpro
sumber
4

Dalam SQL Server , menggunakan pernyataan yang disiapkan pasti injeksi-bukti karena parameter input tidak membentuk kueri. Ini berarti bahwa permintaan yang dieksekusi bukanlah permintaan yang dinamis. Contoh pernyataan rentan injeksi SQL.

string sqlquery = "select * from table where username='" + inputusername +"' and password='" + pass + "'";

Sekarang jika nilai dalam variabel nama pengguna adalah sesuatu seperti 'atau 1 = 1 -, kueri ini sekarang menjadi:

select * from table where username='a' or 1=1 -- and password=asda

Dan sisanya dikomentari setelah itu --, sehingga tidak pernah dieksekusi dan dilewati dengan menggunakan contoh pernyataan yang disiapkan seperti di bawah ini.

Sqlcommand command = new sqlcommand("select * from table where username = @userinput and password=@pass");
command.Parameters.Add(new SqlParameter("@userinput", 100));
command.Parameters.Add(new SqlParameter("@pass", 100));
command.prepare();

Jadi sebenarnya Anda tidak dapat mengirim parameter lain, sehingga menghindari injeksi SQL ...

Lloydom
sumber
3

Frasa kuncinya adalah need not be correctly escaped. Itu berarti Anda tidak perlu khawatir tentang orang yang mencoba melempar tanda hubung, apostrof, kutipan, dll ...

Itu semua ditangani untuk Anda.

Mangga feisty
sumber
2
ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter");

Mari kita asumsikan Anda memilikinya di Servlet Anda benar. Jika seseorang yang jahat memberikan nilai buruk untuk 'filter', Anda mungkin meretas basis data Anda.

MeBigFatGuy
sumber
0

Root Cause # 1 - Masalah Pembatas

Sql injeksi dimungkinkan karena kami menggunakan tanda kutip untuk membatasi string dan juga menjadi bagian dari string, sehingga tidak mungkin untuk menafsirkannya kadang-kadang. Jika kami memiliki pembatas yang tidak dapat digunakan dalam data string, injeksi sql tidak akan pernah terjadi. Memecahkan masalah pembatas menghilangkan masalah injeksi sql. Query struktur melakukan itu.

Root Cause # 2 - Sifat Manusia, Orang Licik dan Beberapa Orang Licik Berbahaya Dan Semua Orang Membuat Kesalahan

Penyebab utama injeksi sql adalah sifat manusia. Orang, termasuk programmer, membuat kesalahan. Ketika Anda membuat kesalahan pada kueri terstruktur, itu tidak membuat sistem Anda rentan terhadap injeksi sql. Jika Anda tidak menggunakan kueri terstruktur, kesalahan dapat menghasilkan kerentanan injeksi sql.

Bagaimana kueri terstruktur menyelesaikan akar penyebab SQL Injection

Pertanyaan Terstruktur Memecahkan Masalah Pembatas, dengan menempatkan perintah sql dalam satu pernyataan dan menempatkan data dalam pernyataan pemrograman terpisah. Pernyataan pemrograman menciptakan pemisahan yang dibutuhkan.

Kueri terstruktur membantu mencegah kesalahan manusia membuat lubang keamanan kritis. Berkenaan dengan manusia membuat kesalahan, injeksi sql tidak dapat terjadi ketika permintaan struktur digunakan. Ada beberapa cara untuk mencegah injeksi sql yang tidak melibatkan kueri terstruktur, tetapi kesalahan manusia normal dalam pendekatan itu biasanya mengarah pada setidaknya beberapa paparan injeksi sql. Kueri Terstruktur gagal aman dari injeksi sql. Anda dapat membuat semua kesalahan di dunia, hampir, dengan kueri terstruktur, sama seperti pemrograman lainnya, tetapi tidak ada yang dapat Anda buat dapat diubah menjadi sistem yang diambil alih dengan injeksi sql. Itu sebabnya orang suka mengatakan ini adalah cara yang tepat untuk mencegah injeksi sql.

Jadi, begitulah, penyebab injeksi sql dan kueri terstruktur alam yang membuatnya tidak mungkin ketika digunakan.

DanAllen
sumber