Neden SQL deyimlerinde her zaman parametreleri kullanmayı tercih ediyoruz?


114

Veritabanları ile çalışma konusunda çok yeniyim. Şimdi yazabilir SELECT, UPDATE, DELETE, ve INSERTkomutları. Ancak yazmayı tercih ettiğimiz birçok forum gördüm:

SELECT empSalary from employee where salary = @salary

...onun yerine:

SELECT empSalary from employee where salary = txtSalary.Text

Neden her zaman parametreleri kullanmayı tercih ediyoruz ve bunları nasıl kullanacağım?

İlk yöntemin kullanımını ve faydalarını bilmek istedim. SQL enjeksiyonunu bile duydum ama tam olarak anlamıyorum. SQL enjeksiyonunun sorumla ilgili olup olmadığını bile bilmiyorum.


2
Haklısın, bu SQL enjeksiyonu ile ilgili. Parametrelerle nasıl başa çıkılacağı genellikle programınızın çalıştırdığı dilin / çerçevenin sorumluluğudur ve dile bağlı olabilir. Lütfen hem RDBMS'nizi (yararlı) hem de ORM çerçevenizi (gerekli) gönderin.
Clockwork-Muse

1
Programlama dili olarak C # ve veritabanı olarak Sql Server 2008 kullanıyorum. Microsoft dotNet framework 4.0 kullanıyorum. Gerçekten üzgünüm, ne sorduğunuzdan emin değilim (RDBMS veya ORM), belki bana RDBMS ve ORM çerçeve sürümlerimi şimdi verebilirsiniz :-). Çok teşekkürler
Sandy

1
RDBMS, sizin veritabanınızdır, sizin durumunuzda SQL Server 2008. ORM'niz, veritabanınıza, bu durumda ADO.NET'e erişme yönteminizdir. Diğerleri LINQ to SQL ve Entity Framework'ü içerir . Aslında, ADO.NET ve SQL'in temellerini öğrendikten sonra, SQL yazarak karşılaşabileceğiniz birçok sorunu çözdükleri için LINQ veya EF gibi bir ORM kullanmanızı öneririm.
Chad Levy

Yanıtlar:


129

Parametrelerin kullanılması, veritabanı bir masaüstü programı veya web sitesi gibi bir program arabirimi ile birlikte kullanıldığında SQL Enjeksiyon saldırılarını önlemeye yardımcı olur .

Örneğinizde, bir kullanıcı içinde ifadeler oluşturarak SQL kodunu doğrudan veritabanınızda çalıştırabilir txtSalary.

Örneğin, yazacaklarsa 0 OR 1=1, yürütülen SQL

 SELECT empSalary from employee where salary = 0 or 1=1

böylece tüm maaşlar iade edilecektir.

Dahası, bir kullanıcı veritabanınıza karşı çok daha kötü komutlar uygulayabilir, bunu silmek de dahil olmak üzere 0; Drop Table employee:

SELECT empSalary from employee where salary = 0; Drop Table employee

Tablo employeedaha sonra silinecektir.


Sizin durumunuzda, .NET kullanıyormuşsunuz gibi görünüyor. Parametreleri kullanmak şu kadar kolaydır:

C #

string sql = "SELECT empSalary from employee where salary = @salary";

using (SqlConnection connection = new SqlConnection(/* connection info */))
using (SqlCommand command = new SqlCommand(sql, connection))
{
    var salaryParam = new SqlParameter("salary", SqlDbType.Money);
    salaryParam.Value = txtMoney.Text;

    command.Parameters.Add(salaryParam);
    var results = command.ExecuteReader();
}

VB.NET

Dim sql As String = "SELECT empSalary from employee where salary = @salary"
Using connection As New SqlConnection("connectionString")
    Using command As New SqlCommand(sql, connection)
        Dim salaryParam = New SqlParameter("salary", SqlDbType.Money)
        salaryParam.Value = txtMoney.Text

        command.Parameters.Add(salaryParam)

        Dim results = command.ExecuteReader()
    End Using
End Using

2016-4-25'i düzenleyin:

George Stocker'in yorumuna göre, örnek kodu kullanmamak için değiştirdim AddWithValue. Ayrıca, genellikle sarmak önerilir IDisposableiçinde s usingifadeleri.


harika çözüm. Ama biraz daha açıklayabilir misiniz, parametrelerin neden ve nasıl güvenli olduğunu. Demek istediğim, hala sql komutu aynı olacak gibi görünüyor
Sandy

sql komutuna birden fazla parametre ekleyebilir miyiz? Bir INSERT Komutunda isteyebileceğimiz gibi mi?
Sandy

2
SQL Server, parametrelerin içindeki metni yalnızca girdi olarak değerlendirir ve asla yürütmez.
Chad Levy

3
Evet, birden fazla parametre ekleyebilirsiniz: Insert Into table (Col1, Col2) Values (@Col1, @Col2). Kodunuza birden çok URL eklersiniz AddWithValue.
Chad Levy

1
Lütfen AddWithValue kullanmayın! Örtük dönüştürme sorunlarına neden olabilir. Her zaman boyutu açıkça ayarlayın ve parametre değerini ile ekleyin parameter.Value = someValue.
George Stocker

75

Haklısınız, bu, bir malicioius kullanıcısının veritabanınıza karşı rastgele ifadeler yürütmesine izin veren bir güvenlik açığı olan SQL enjeksiyonu ile ilgilidir . Eski zamanların en sevilen XKCD çizgi romanı , konsepti gösteriyor:

Kızının adı, ehliyet fabrikasında mahsur kalmama yardım et.


Örneğinizde, yalnızca şunları kullanırsanız:

var query = "SELECT empSalary from employee where salary = " + txtSalary.Text;
// and proceed to execute this query

SQL enjeksiyonuna açıksınız. Örneğin, birinin txtSalary girdiğini varsayalım:

1; UPDATE employee SET salary = 9999999 WHERE empID = 10; --
1; DROP TABLE employee; --
// etc.

Bu sorguyu çalıştırdığınızda, a SELECTve bir UPDATEveya DROPveya istedikleri her şeyi gerçekleştirecektir. --Sonunda sadece sen sonra bir şey birleştirerek sanki saldırıda yararlı olacağını Sorgunuzun kalanını dışarı yorumlar txtSalary.Text.


Doğru yol, parametreleştirilmiş sorgular kullanmaktır, örneğin (C #):

SqlCommand query =  new SqlCommand("SELECT empSalary FROM employee 
                                    WHERE salary = @sal;");
query.Parameters.AddWithValue("@sal", txtSalary.Text);

Bununla, sorguyu güvenle yürütebilirsiniz.

Diğer birkaç dilde SQL enjeksiyonundan nasıl kaçınılacağına dair referans için , SO kullanıcısı tarafından sağlanan bir web sitesi olan bobby-tables.com adresini kontrol edin .


1
harika çözüm. Ama biraz daha açıklayabilir misiniz? Parametrelerin neden ve nasıl güvenli olduğunu. Demek istediğim hala sql komutu aynı olacak gibi görünüyor.
Sandy

1
@ user815600: yaygın bir yanılgı - yine de parametreli sorgunun değeri alacağına ve gerçek değerlerin yerine parametreleri değiştireceğine inanıyorsunuz - değil mi? Hayır bu olmuyor! - Bunun yerine, parametreleri ile SQL deyimi bir parametre listesi ve değerleriyle birlikte, SQL Server iletilecek - SQL deyimi olduğu değil aynı olacak
Marc_s

1
yani sql enjeksiyonu sql server iç mekanizması veya güvenliği ile izleniyor. Teşekkürler.
Sandy

4
Çizgi filmleri sevdiğim kadarıyla, kodunuzu tabloları düşürmek için yeterli ayrıcalıkla çalıştırıyorsanız, muhtemelen daha geniş sorunlarınız vardır.
philw

9

Diğer yanıtlara ek olarak, bu parametrelerin yalnızca sql enjeksiyonunu önlemeye yardımcı olmakla kalmayıp , aynı zamanda sorguların performansını artırabileceğini de eklemeniz gerekir . Sql server parametreleştirilmiş sorgu planlarını önbelleğe alır ve bunları tekrarlanan sorgu yürütmede yeniden kullanır. Sorgunuzu parametreleştirmediyseniz, sorgu metni farklıysa, sql sunucusu her sorgu için (bir miktar dışlama ile) yeni plan derler .

Sorgu planı önbelleğe alma hakkında daha fazla bilgi


1
Bu, birinin düşündüğünden daha uygun. "Küçük" bir sorgu bile binlerce veya milyonlarca kez çalıştırılabilir ve tüm sorgu önbelleğini etkin bir şekilde temizler.
James

5

İlk gitmemden iki yıl sonra , yeniden canlandırıyorum ...

Neden parametreleri tercih ediyoruz? SQL enjeksiyon tabii ki büyük bir nedendir, ancak bu olabilir biz gizlice SQL geri almak için özlem konum bir dil olarak . Dize değişmezlerinde SQL zaten garip bir kültürel uygulamadır, ancak en azından isteğinizi kopyalayıp yönetim stüdyosuna yapıştırabilirsiniz. SQL koşullu ve kontrol yapılarına sahip olduğunda, ana bilgisayar dili koşullu ve kontrol yapılarıyla dinamik olarak inşa edilmiş SQL, sadece seviye 0 barbarlıktır. Hangi SQL'i oluşturduğunu görmek için uygulamanızı hata ayıklamada veya bir izleme ile çalıştırmanız gerekir.

Sadece parametrelerle durmayın. Tamamen gidin ve QueryFirst'i kullanın (feragatname: yazdığım). Sizin SQL yaşayan bir .sql dosyası. Tablo ve sütunlarınız için sözdizimi doğrulaması ve Intellisense ile muhteşem TSQL düzenleyici penceresinde düzenlersiniz. Özel yorumlar bölümünde test verilerini atayabilir ve sorgunuzu pencerede çalıştırmak için "oynat" düğmesini tıklayabilirsiniz. Bir parametre oluşturmak, SQL'inize "@myParam" koymak kadar kolaydır. Daha sonra, her kaydettiğinizde QueryFirst, sorgunuz için C # sarıcı oluşturur. Parametreleriniz, Execute () yöntemlerine argümanlar olarak güçlü bir şekilde yazılır. Sonuçlarınız, sorgunuz tarafından döndürülen gerçek şemadan üretilen türler olan bir IEnumerable veya güçlü bir şekilde yazılmış POCO'ların Listesinde döndürülür. Sorgunuz çalışmazsa, uygulamanız derlenmez. DB şemanız değişirse ve sorgunuz çalışırsa ancak bazı sütunlar kaybolursa, derleme hatası kodunuzdaki satıra işaret edereksik verilere erişmeye çalışan. Ve çok sayıda başka avantaj var. Verilere neden başka bir şekilde erişmek istersiniz?


4

Sql'de herhangi bir kelime @ işareti içerdiğinde değişken olduğu anlamına gelir ve bu değişkeni içinde değer ayarlamak için kullanırız ve aynı sql betiğindeki sayı alanında kullanırız çünkü çok sayıda değişkeni tanımlayabilirken yalnızca tek bir betik üzerinde kısıtlanmıştır. birçok komut dosyasında aynı tür ve ada sahip. Bu değişkeni saklı yordam lotunda kullanıyoruz çünkü saklı yordam önceden derlenmiş sorgulardır ve bu değişkendeki değerleri daha fazla bilgi için betik, masaüstü ve web sitelerinden geçirebiliriz. Yerel Değişken Bildirme , Sql Depolanan Yordam ve sql enjeksiyonları .

Ayrıca , SQL enjeksiyonundan koruyun, veritabanınızı nasıl koruyabileceğinize rehberlik edecektir.

Umarım herhangi bir soruyu anlamanıza yardımcı olur.


3

Diğer yanıtlar, parametrelerin neden önemli olduğunu kapsar, ancak bir dezavantajı vardır! .Net'te, parametre oluşturmak için çeşitli yöntemler vardır (Add, AddWithValue), ancak bunların tümü, gereksiz yere parametre adı hakkında endişelenmenizi gerektirir ve hepsi koddaki SQL'in okunabilirliğini azaltır. Tam SQL üzerinde meditasyon yapmaya çalışırken, parametrede hangi değerin kullanıldığını görmek için yukarıda veya aşağıda araştırmanız gerekir.

Küçük SqlBuilder sınıfımın parametreleştirilmiş sorgular yazmanın en zarif yolu olduğunu alçakgönüllülükle iddia ediyorum . Kodunuz şöyle görünecek ...

C #

var bldr = new SqlBuilder( myCommand );
bldr.Append("SELECT * FROM CUSTOMERS WHERE ID = ").Value(myId);
//or
bldr.Append("SELECT * FROM CUSTOMERS WHERE NAME LIKE ").FuzzyValue(myName);
myCommand.CommandText = bldr.ToString();

Kodunuz daha kısa ve çok daha okunaklı olacaktır. Fazladan satırlara bile ihtiyacınız yok ve tekrar okurken, parametrelerin değeri için etrafta dolaşmanıza gerek yok. İhtiyacınız olan sınıf burada ...

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

public class SqlBuilder
{
private StringBuilder _rq;
private SqlCommand _cmd;
private int _seq;
public SqlBuilder(SqlCommand cmd)
{
    _rq = new StringBuilder();
    _cmd = cmd;
    _seq = 0;
}
public SqlBuilder Append(String str)
{
    _rq.Append(str);
    return this;
}
public SqlBuilder Value(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append(paramName);
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public SqlBuilder FuzzyValue(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append("'%' + " + paramName + " + '%'");
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public override string ToString()
{
    return _rq.ToString();
}
}

Parametrelerinizi adlandırmak, sunucunun çalıştırdığı sorguların profilini çıkarırken kesinlikle yardımcı olur.
Dave R.

Patronum da aynı şeyi söyledi. Anlamlı parametre adları sizin için önemliyse, değer yöntemine bir paramName bağımsız değişkeni ekleyin. İşleri gereksiz yere karmaşıklaştırdığından şüpheleniyorum.
bbsimonbb

Kötü bir fikir. Daha önce söylendiği gibi, AddWithValueörtük dönüştürme sorunlarına neden olabilir.
Adam Calvet Bohl

@Adam haklısınız, ancak bu AddWithValue () 'nun çok yaygın olarak kullanılmasını engellemiyor ve fikri geçersiz kıldığını düşünmüyorum. Ancak bu arada, parametreleştirilmiş sorgular yazmak için çok daha iyi bir yol buldum ve bu AddWithValue () :-)
kullanmıyor

Sağ! Söz veriyorum yakında buna bakacağım!
Adam Calvet Bohl

3

Eski gönderi, ancak yeni gelenlerin Saklanan prosedürlerden haberdar olmasını sağlamak istiyordu .

Buradaki 10 ¢ değerim, SQL ifadenizi saklı yordam olarak yazabilirseniz, bence en uygun yaklaşım budur. Ben DAİMA benim ana kod kayıtlarından saklanan procs ve asla döngü kullanıyoruz. Örneğin: SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class.

Depolanan prosedürleri kullandığınızda, kullanıcıyı yalnızca ÇALIŞTIR izniyle sınırlandırabilir , böylece güvenlik risklerini azaltabilirsiniz .

Depolanan yordamınız doğası gereği ayrıştırılır ve giriş ve çıkış parametrelerini belirtebilirsiniz.

Depolanan yordama (veriyi ifade yoluyla döndürüyorsa SELECT), SELECTkodunuzdaki normal bir ifade ile aynı şekilde erişilebilir ve okunabilir .

Ayrıca SQL Server'da derlendiği için daha hızlı çalışır.

Ayrıca, updatebir tablo gibi birden çok adımı uygulayabileceğinizi, başka bir DB sunucusundaki değerleri kontrol edebileceğinizi ve sonunda bittiğinde, verileri istemciye geri gönderebileceğinizi, hepsi aynı sunucuda ve istemciyle etkileşimde bulunmayabileceğinizi de söylemiş miydim ? Yani bu, bu mantığı kodunuzda kodlamaktan ÇOK daha hızlıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.